mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-27 10:32:37 +00:00
Various UI fixes
This commit is contained in:
parent
cbdda47b3c
commit
871dbf7107
@ -586,8 +586,8 @@ final class SharedApplicationContext {
|
|||||||
self.window?.rootViewController?.dismiss(animated: true, completion: nil)
|
self.window?.rootViewController?.dismiss(animated: true, completion: nil)
|
||||||
}, getAvailableAlternateIcons: {
|
}, getAvailableAlternateIcons: {
|
||||||
if #available(iOS 10.3, *) {
|
if #available(iOS 10.3, *) {
|
||||||
var icons = [PresentationAppIcon(name: "Blue", imageName: "BlueIcon", isDefault: false),
|
var icons = [PresentationAppIcon(name: "Blue", imageName: "BlueIcon", isDefault: buildConfig.isAppStoreBuild),
|
||||||
PresentationAppIcon(name: "Black", imageName: "BlackIcon", isDefault: false),
|
PresentationAppIcon(name: "Black", imageName: "BlackIcon", isDefault: buildConfig.isInternalBuild),
|
||||||
PresentationAppIcon(name: "BlueClassic", imageName: "BlueClassicIcon", isDefault: false),
|
PresentationAppIcon(name: "BlueClassic", imageName: "BlueClassicIcon", isDefault: false),
|
||||||
PresentationAppIcon(name: "BlackClassic", imageName: "BlackClassicIcon", isDefault: false),
|
PresentationAppIcon(name: "BlackClassic", imageName: "BlackClassicIcon", isDefault: false),
|
||||||
PresentationAppIcon(name: "BlueFilled", imageName: "BlueFilledIcon", isDefault: false),
|
PresentationAppIcon(name: "BlueFilled", imageName: "BlueFilledIcon", isDefault: false),
|
||||||
|
|||||||
@ -312,13 +312,13 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
|||||||
arguments.tapAvatarAction()
|
arguments.tapAvatarAction()
|
||||||
}, context: arguments.avatarAndNameInfoContext, updatingImage: updatingAvatar)
|
}, context: arguments.avatarAndNameInfoContext, updatingImage: updatingAvatar)
|
||||||
case let .about(theme, text, value):
|
case let .about(theme, text, value):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntitiyTypes: [.url, .mention, .hashtag], multiline: true, sectionId: self.section, action: nil, longTapAction: {
|
return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntityTypes: [.url, .mention, .hashtag], multiline: true, sectionId: self.section, action: nil, longTapAction: {
|
||||||
arguments.displayContextMenu(ChannelInfoEntryTag.about, value)
|
arguments.displayContextMenu(ChannelInfoEntryTag.about, value)
|
||||||
}, linkItemAction: { action, itemLink in
|
}, linkItemAction: { action, itemLink in
|
||||||
arguments.aboutLinkAction(action, itemLink)
|
arguments.aboutLinkAction(action, itemLink)
|
||||||
}, tag: ChannelInfoEntryTag.about)
|
}, tag: ChannelInfoEntryTag.about)
|
||||||
case let .addressName(theme, text, value):
|
case let .addressName(theme, text, value):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: {
|
||||||
arguments.displayAddressNameContextMenu("https://t.me/\(value)")
|
arguments.displayAddressNameContextMenu("https://t.me/\(value)")
|
||||||
}, longTapAction: {
|
}, longTapAction: {
|
||||||
arguments.displayContextMenu(ChannelInfoEntryTag.link, "https://t.me/\(value)")
|
arguments.displayContextMenu(ChannelInfoEntryTag.link, "https://t.me/\(value)")
|
||||||
@ -1157,7 +1157,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
|||||||
}
|
}
|
||||||
aboutLinkActionImpl = { [weak controller] action, itemLink in
|
aboutLinkActionImpl = { [weak controller] action, itemLink in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
handlePeerInfoAboutTextAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
handleTextLinkAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endEditingImpl = {
|
endEditingImpl = {
|
||||||
|
|||||||
@ -2954,7 +2954,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
|||||||
self?.deleteMediaRecording()
|
self?.deleteMediaRecording()
|
||||||
}, sendRecordedMedia: { [weak self] in
|
}, sendRecordedMedia: { [weak self] in
|
||||||
self?.sendMediaRecording()
|
self?.sendMediaRecording()
|
||||||
}, displayRestrictedInfo: { [weak self] subject in
|
}, displayRestrictedInfo: { [weak self] subject, displayType in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3006,37 +3006,42 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
|||||||
|
|
||||||
strongSelf.recordingModeFeedback?.error()
|
strongSelf.recordingModeFeedback?.error()
|
||||||
|
|
||||||
var rect: CGRect?
|
switch displayType {
|
||||||
let isStickers: Bool = subject == .stickers
|
case .tooltip:
|
||||||
switch subject {
|
var rect: CGRect?
|
||||||
case .stickers:
|
let isStickers: Bool = subject == .stickers
|
||||||
rect = strongSelf.chatDisplayNode.frameForStickersButton()
|
switch subject {
|
||||||
if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() {
|
case .stickers:
|
||||||
rectValue.origin.y = actionRect.minY
|
rect = strongSelf.chatDisplayNode.frameForStickersButton()
|
||||||
rect = rectValue
|
if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() {
|
||||||
|
rectValue.origin.y = actionRect.minY
|
||||||
|
rect = rectValue
|
||||||
|
}
|
||||||
|
case .mediaRecording:
|
||||||
|
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||||
}
|
}
|
||||||
case .mediaRecording:
|
|
||||||
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
|
||||||
}
|
tooltipController.content = .text(banDescription)
|
||||||
|
} else if let rect = rect {
|
||||||
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
|
strongSelf.mediaRestrictedTooltipController?.dismiss()
|
||||||
tooltipController.content = .text(banDescription)
|
let tooltipController = TooltipController(content: .text(banDescription))
|
||||||
} else if let rect = rect {
|
strongSelf.mediaRestrictedTooltipController = tooltipController
|
||||||
strongSelf.mediaRestrictedTooltipController?.dismiss()
|
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
|
||||||
let tooltipController = TooltipController(content: .text(banDescription))
|
tooltipController.dismissed = { [weak tooltipController] in
|
||||||
strongSelf.mediaRestrictedTooltipController = tooltipController
|
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
|
||||||
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
|
strongSelf.mediaRestrictedTooltipController = nil
|
||||||
tooltipController.dismissed = { [weak tooltipController] in
|
}
|
||||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
|
}
|
||||||
strongSelf.mediaRestrictedTooltipController = nil
|
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||||
|
if let strongSelf = self {
|
||||||
|
return (strongSelf.chatDisplayNode, rect)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
case .alert:
|
||||||
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: banDescription, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
if let strongSelf = self {
|
|
||||||
return (strongSelf.chatDisplayNode, rect)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, displayVideoUnmuteTip: { [weak self] location in
|
}, displayVideoUnmuteTip: { [weak self] location in
|
||||||
@ -3607,6 +3612,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
|||||||
self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId)
|
self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] reason in
|
|> deliverOnMainQueue).start(next: { [weak self] reason in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let subjectFlags: TelegramChatBannedRightsFlags = .banSendMedia
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
let moreInfo: Bool
|
let moreInfo: Bool
|
||||||
switch reason {
|
switch reason {
|
||||||
@ -3617,8 +3624,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
|||||||
text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted
|
text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted
|
||||||
moreInfo = true
|
moreInfo = true
|
||||||
case .mediaRestricted:
|
case .mediaRestricted:
|
||||||
text = strongSelf.presentationData.strings.Conversation_DefaultRestrictedMedia
|
strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert)
|
||||||
moreInfo = false
|
return
|
||||||
}
|
}
|
||||||
let actions: [TextAlertAction]
|
let actions: [TextAlertAction]
|
||||||
if moreInfo {
|
if moreInfo {
|
||||||
@ -3631,27 +3638,6 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
|
|||||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions), in: .window(.root))
|
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions), in: .window(.root))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
/*case let .group(groupId):
|
|
||||||
let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.group(groupId), .total(nil)])
|
|
||||||
self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey]) |> deliverOnMainQueue).start(next: { [weak self] views in
|
|
||||||
if let strongSelf = self {
|
|
||||||
var unreadCount: Int32 = 0
|
|
||||||
var totalCount: Int32 = 0
|
|
||||||
|
|
||||||
if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView {
|
|
||||||
if let count = view.count(for: .group(groupId)) {
|
|
||||||
unreadCount = count
|
|
||||||
}
|
|
||||||
if let (_, state) = view.total() {
|
|
||||||
let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 }
|
|
||||||
let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state)
|
|
||||||
totalCount = count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interfaceInteraction = interfaceInteraction
|
self.interfaceInteraction = interfaceInteraction
|
||||||
|
|||||||
@ -37,6 +37,11 @@ enum ChatPanelRestrictionInfoSubject {
|
|||||||
case stickers
|
case stickers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ChatPanelRestrictionInfoDisplayType {
|
||||||
|
case tooltip
|
||||||
|
case alert
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatPanelInterfaceInteraction {
|
final class ChatPanelInterfaceInteraction {
|
||||||
let setupReplyMessage: (MessageId) -> Void
|
let setupReplyMessage: (MessageId) -> Void
|
||||||
let setupEditMessage: (MessageId?) -> Void
|
let setupEditMessage: (MessageId?) -> Void
|
||||||
@ -73,7 +78,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
let lockMediaRecording: () -> Void
|
let lockMediaRecording: () -> Void
|
||||||
let deleteRecordedMedia: () -> Void
|
let deleteRecordedMedia: () -> Void
|
||||||
let sendRecordedMedia: () -> Void
|
let sendRecordedMedia: () -> Void
|
||||||
let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject) -> Void
|
let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void
|
||||||
let displayVideoUnmuteTip: (CGPoint?) -> Void
|
let displayVideoUnmuteTip: (CGPoint?) -> Void
|
||||||
let switchMediaRecordingMode: () -> Void
|
let switchMediaRecordingMode: () -> Void
|
||||||
let setupMessageAutoremoveTimeout: () -> Void
|
let setupMessageAutoremoveTimeout: () -> Void
|
||||||
@ -102,7 +107,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
let reportPeerIrrelevantGeoLocation: () -> Void
|
let reportPeerIrrelevantGeoLocation: () -> Void
|
||||||
let statuses: ChatPanelInterfaceInteractionStatuses?
|
let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||||
|
|
||||||
init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
|
init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
|
||||||
self.setupReplyMessage = setupReplyMessage
|
self.setupReplyMessage = setupReplyMessage
|
||||||
self.setupEditMessage = setupEditMessage
|
self.setupEditMessage = setupEditMessage
|
||||||
self.beginMessageSelection = beginMessageSelection
|
self.beginMessageSelection = beginMessageSelection
|
||||||
|
|||||||
@ -74,7 +74,7 @@ final class ChatRecentActionsController: TelegramController {
|
|||||||
}, lockMediaRecording: {
|
}, lockMediaRecording: {
|
||||||
}, deleteRecordedMedia: {
|
}, deleteRecordedMedia: {
|
||||||
}, sendRecordedMedia: {
|
}, sendRecordedMedia: {
|
||||||
}, displayRestrictedInfo: { _ in
|
}, displayRestrictedInfo: { _, _ in
|
||||||
}, displayVideoUnmuteTip: { _ in
|
}, displayVideoUnmuteTip: { _ in
|
||||||
}, switchMediaRecordingMode: {
|
}, switchMediaRecordingMode: {
|
||||||
}, setupMessageAutoremoveTimeout: {
|
}, setupMessageAutoremoveTimeout: {
|
||||||
|
|||||||
@ -348,7 +348,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.addSubnode(self.actionButtons)
|
self.addSubnode(self.actionButtons)
|
||||||
|
|
||||||
self.actionButtons.micButton.recordingDisabled = { [weak self] in
|
self.actionButtons.micButton.recordingDisabled = { [weak self] in
|
||||||
self?.interfaceInteraction?.displayRestrictedInfo(.mediaRecording)
|
self?.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .tooltip)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.actionButtons.micButton.beginRecording = { [weak self] in
|
self.actionButtons.micButton.beginRecording = { [weak self] in
|
||||||
@ -1578,7 +1578,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
if enabled {
|
if enabled {
|
||||||
self.interfaceInteraction?.openStickers()
|
self.interfaceInteraction?.openStickers()
|
||||||
} else {
|
} else {
|
||||||
self.interfaceInteraction?.displayRestrictedInfo(.stickers)
|
self.interfaceInteraction?.displayRestrictedInfo(.stickers, .tooltip)
|
||||||
}
|
}
|
||||||
case .keyboard:
|
case .keyboard:
|
||||||
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
|
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
|
||||||
|
|||||||
@ -387,10 +387,10 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
arguments.performAction(.addToExisting)
|
arguments.performAction(.addToExisting)
|
||||||
})
|
})
|
||||||
case let .company(_, theme, title, value, selected):
|
case let .company(_, theme, title, value, selected):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
||||||
}, tag: nil)
|
}, tag: nil)
|
||||||
case let .phoneNumber(_, index, theme, title, label, value, selected, isInteractionEnabled):
|
case let .phoneNumber(_, index, theme, title, label, value, selected, isInteractionEnabled):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: isInteractionEnabled ? {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: false, selected: selected, sectionId: self.section, action: isInteractionEnabled ? {
|
||||||
if selected != nil {
|
if selected != nil {
|
||||||
arguments.toggleSelection(.phoneNumber(label, value))
|
arguments.toggleSelection(.phoneNumber(label, value))
|
||||||
} else {
|
} else {
|
||||||
@ -424,7 +424,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
arguments.addPhoneNumber()
|
arguments.addPhoneNumber()
|
||||||
})
|
})
|
||||||
case let .email(_, index, theme, title, label, value, selected):
|
case let .email(_, index, theme, title, label, value, selected):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: false, selected: selected, sectionId: self.section, action: {
|
||||||
if selected != nil {
|
if selected != nil {
|
||||||
arguments.toggleSelection(.email(label, value))
|
arguments.toggleSelection(.email(label, value))
|
||||||
} else {
|
} else {
|
||||||
@ -436,7 +436,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}, tag: DeviceContactInfoEntryTag.info(index))
|
}, tag: DeviceContactInfoEntryTag.info(index))
|
||||||
case let .url(_, index, theme, title, label, value, selected):
|
case let .url(_, index, theme, title, label, value, selected):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: value, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: false, selected: selected, sectionId: self.section, action: {
|
||||||
if selected != nil {
|
if selected != nil {
|
||||||
arguments.toggleSelection(.url(label, value))
|
arguments.toggleSelection(.url(label, value))
|
||||||
} else {
|
} else {
|
||||||
@ -475,7 +475,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}, tag: DeviceContactInfoEntryTag.info(index))
|
}, tag: DeviceContactInfoEntryTag.info(index))
|
||||||
case let .birthday(_, theme, title, value, text, selected):
|
case let .birthday(_, theme, title, value, text, selected):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
||||||
if selected != nil {
|
if selected != nil {
|
||||||
arguments.toggleSelection(.birthday)
|
arguments.toggleSelection(.birthday)
|
||||||
} else {
|
} else {
|
||||||
@ -504,7 +504,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}, tag: DeviceContactInfoEntryTag.birthday)
|
}, tag: DeviceContactInfoEntryTag.birthday)
|
||||||
case let .socialProfile(_, index, theme, title, value, text, selected):
|
case let .socialProfile(_, index, theme, title, value, text, selected):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
||||||
if selected != nil {
|
if selected != nil {
|
||||||
arguments.toggleSelection(.socialProfile(value))
|
arguments.toggleSelection(.socialProfile(value))
|
||||||
} else if value.url.count > 0 {
|
} else if value.url.count > 0 {
|
||||||
@ -516,7 +516,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}, tag: DeviceContactInfoEntryTag.info(index))
|
}, tag: DeviceContactInfoEntryTag.info(index))
|
||||||
case let .instantMessenger(_, index, theme, title, value, text, selected):
|
case let .instantMessenger(_, index, theme, title, value, text, selected):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: title, text: text, style: arguments.isPlain ? .plain : .blocks, textColor: .accent, enabledEntityTypes: [], multiline: true, selected: selected, sectionId: self.section, action: {
|
||||||
if selected != nil {
|
if selected != nil {
|
||||||
arguments.toggleSelection(.instantMessenger(value))
|
arguments.toggleSelection(.instantMessenger(value))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -461,7 +461,7 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
arguments.changeProfilePhoto()
|
arguments.changeProfilePhoto()
|
||||||
})
|
})
|
||||||
case let .about(theme, text):
|
case let .about(theme, text):
|
||||||
return ItemListMultilineTextItem(theme: theme, text: foldMultipleLineBreaks(text), enabledEntitiyTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: {
|
return ItemListMultilineTextItem(theme: theme, text: foldMultipleLineBreaks(text), enabledEntityTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: {
|
||||||
arguments.displayAboutContextMenu(text)
|
arguments.displayAboutContextMenu(text)
|
||||||
}, linkItemAction: { action, itemLink in
|
}, linkItemAction: { action, itemLink in
|
||||||
arguments.aboutLinkAction(action, itemLink)
|
arguments.aboutLinkAction(action, itemLink)
|
||||||
@ -2362,7 +2362,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
|||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { peerView in
|
|> deliverOnMainQueue).start(next: { peerView in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
handlePeerInfoAboutTextAction(context: context, peerId: peerView.peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
handleTextLinkAction(context: context, peerId: peerView.peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2416,149 +2416,3 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
|||||||
}
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePeerInfoAboutTextAction(context: AccountContext, peerId: PeerId, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) {
|
|
||||||
let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in
|
|
||||||
controller.present(controllerToPresent, in: .window(.root))
|
|
||||||
}
|
|
||||||
|
|
||||||
let openResolvedPeerImpl: (PeerId?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peerId, navigation in
|
|
||||||
openResolvedUrl(.peer(peerId, navigation), context: context, navigationController: (controller?.navigationController as? NavigationController), openPeer: { (peerId, navigation) in
|
|
||||||
switch navigation {
|
|
||||||
case let .chat(_, messageId):
|
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
|
||||||
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId, keepStack: .always)
|
|
||||||
}
|
|
||||||
case .info:
|
|
||||||
let peerSignal: Signal<Peer?, NoError>
|
|
||||||
peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init)
|
|
||||||
navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in
|
|
||||||
if let controller = controller, let peer = peer {
|
|
||||||
if let infoController = peerInfoController(context: context, peer: peer) {
|
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(infoController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}, present: presentImpl, dismissInput: {})
|
|
||||||
}
|
|
||||||
|
|
||||||
let openLinkImpl: (String) -> Void = { [weak controller] url in
|
|
||||||
navigateDisposable.set((resolveUrl(account: context.account, url: url) |> deliverOnMainQueue).start(next: { result in
|
|
||||||
if let controller = controller {
|
|
||||||
switch result {
|
|
||||||
case let .externalUrl(url):
|
|
||||||
context.sharedContext.applicationBindings.openUrl(url)
|
|
||||||
case let .peer(peerId, _):
|
|
||||||
openResolvedPeerImpl(peerId, .default)
|
|
||||||
case let .channelMessage(peerId, messageId):
|
|
||||||
if let navigationController = controller.navigationController as? NavigationController {
|
|
||||||
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId)
|
|
||||||
}
|
|
||||||
case let .stickerPack(name):
|
|
||||||
controller.present(StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
|
|
||||||
case let .instantView(webpage, anchor):
|
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor))
|
|
||||||
case let .join(link):
|
|
||||||
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId in
|
|
||||||
openResolvedPeerImpl(peerId, .chat(textInputState: nil, messageId: nil))
|
|
||||||
}), in: .window(.root))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
let openPeerMentionImpl: (String) -> Void = { mention in
|
|
||||||
navigateDisposable.set((resolvePeerByName(account: context.account, name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peerId in
|
|
||||||
openResolvedPeerImpl(peerId, .default)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
switch action {
|
|
||||||
case .tap:
|
|
||||||
switch itemLink {
|
|
||||||
case let .url(url):
|
|
||||||
openLinkImpl(url)
|
|
||||||
case let .mention(mention):
|
|
||||||
openPeerMentionImpl(mention)
|
|
||||||
case let .hashtag(_, hashtag):
|
|
||||||
let peerSignal = context.account.postbox.loadedPeerWithId(peerId)
|
|
||||||
let _ = (peerSignal
|
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
|
||||||
let searchController = HashtagSearchController(context: context, peer: peer, query: hashtag)
|
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(searchController)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case .longTap:
|
|
||||||
switch itemLink {
|
|
||||||
case let .url(url):
|
|
||||||
let canOpenIn = availableOpenInOptions(context: context, item: .url(url: url)).count > 1
|
|
||||||
let openText = canOpenIn ? presentationData.strings.Conversation_FileOpenIn : presentationData.strings.Conversation_LinkDialogOpen
|
|
||||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetTextItem(title: url),
|
|
||||||
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
openLinkImpl(url)
|
|
||||||
}),
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
UIPasteboard.general.string = url
|
|
||||||
}),
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
if let link = URL(string: url) {
|
|
||||||
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]), ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
|
||||||
])])
|
|
||||||
controller.present(actionSheet, in: .window(.root))
|
|
||||||
case let .mention(mention):
|
|
||||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetTextItem(title: mention),
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
openPeerMentionImpl(mention)
|
|
||||||
}),
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
UIPasteboard.general.string = mention
|
|
||||||
})
|
|
||||||
]), ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
|
||||||
])])
|
|
||||||
controller.present(actionSheet, in: .window(.root))
|
|
||||||
case let .hashtag(_, hashtag):
|
|
||||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetTextItem(title: hashtag),
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
let searchController = HashtagSearchController(context: context, peer: nil, query: hashtag)
|
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(searchController)
|
|
||||||
}),
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
UIPasteboard.general.string = hashtag
|
|
||||||
})
|
|
||||||
]), ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
|
||||||
])])
|
|
||||||
controller.present(actionSheet, in: .window(.root))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ enum TextLinkItem {
|
|||||||
class ItemListMultilineTextItem: ListViewItem, ItemListItem {
|
class ItemListMultilineTextItem: ListViewItem, ItemListItem {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let text: String
|
let text: String
|
||||||
let enabledEntitiyTypes: EnabledEntityTypes
|
let enabledEntityTypes: EnabledEntityTypes
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
@ -30,10 +30,10 @@ class ItemListMultilineTextItem: ListViewItem, ItemListItem {
|
|||||||
|
|
||||||
let selectable: Bool
|
let selectable: Bool
|
||||||
|
|
||||||
init(theme: PresentationTheme, text: String, enabledEntitiyTypes: EnabledEntityTypes, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
|
init(theme: PresentationTheme, text: String, enabledEntityTypes: EnabledEntityTypes, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.text = text
|
self.text = text
|
||||||
self.enabledEntitiyTypes = enabledEntitiyTypes
|
self.enabledEntityTypes = enabledEntityTypes
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
self.action = action
|
self.action = action
|
||||||
@ -184,7 +184,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode {
|
|||||||
leftInset = 16.0 + params.rightInset
|
leftInset = 16.0 + params.rightInset
|
||||||
}
|
}
|
||||||
|
|
||||||
let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntitiyTypes)
|
let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes)
|
||||||
let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont)
|
let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont)
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|||||||
@ -18,7 +18,7 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem {
|
|||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let labelColor: ItemListTextWithLabelItemTextColor
|
let labelColor: ItemListTextWithLabelItemTextColor
|
||||||
let textColor: ItemListTextWithLabelItemTextColor
|
let textColor: ItemListTextWithLabelItemTextColor
|
||||||
let enabledEntitiyTypes: EnabledEntityTypes
|
let enabledEntityTypes: EnabledEntityTypes
|
||||||
let multiline: Bool
|
let multiline: Bool
|
||||||
let selected: Bool?
|
let selected: Bool?
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
@ -28,14 +28,14 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem {
|
|||||||
|
|
||||||
let tag: Any?
|
let tag: Any?
|
||||||
|
|
||||||
init(theme: PresentationTheme, label: String, text: String, style: ItemListStyle = .plain, labelColor: ItemListTextWithLabelItemTextColor = .primary, textColor: ItemListTextWithLabelItemTextColor = .primary, enabledEntitiyTypes: EnabledEntityTypes, multiline: Bool, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
|
init(theme: PresentationTheme, label: String, text: String, style: ItemListStyle = .plain, labelColor: ItemListTextWithLabelItemTextColor = .primary, textColor: ItemListTextWithLabelItemTextColor = .primary, enabledEntityTypes: EnabledEntityTypes, multiline: Bool, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.label = label
|
self.label = label
|
||||||
self.text = text
|
self.text = text
|
||||||
self.style = style
|
self.style = style
|
||||||
self.labelColor = labelColor
|
self.labelColor = labelColor
|
||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
self.enabledEntitiyTypes = enabledEntitiyTypes
|
self.enabledEntityTypes = enabledEntityTypes
|
||||||
self.multiline = multiline
|
self.multiline = multiline
|
||||||
self.selected = selected
|
self.selected = selected
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
@ -199,7 +199,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntitiyTypes)
|
let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes)
|
||||||
let baseColor: UIColor
|
let baseColor: UIColor
|
||||||
switch item.textColor {
|
switch item.textColor {
|
||||||
case .primary:
|
case .primary:
|
||||||
|
|||||||
@ -346,7 +346,7 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
}, lockMediaRecording: {
|
}, lockMediaRecording: {
|
||||||
}, deleteRecordedMedia: {
|
}, deleteRecordedMedia: {
|
||||||
}, sendRecordedMedia: {
|
}, sendRecordedMedia: {
|
||||||
}, displayRestrictedInfo: { _ in
|
}, displayRestrictedInfo: { _, _ in
|
||||||
}, displayVideoUnmuteTip: { _ in
|
}, displayVideoUnmuteTip: { _ in
|
||||||
}, switchMediaRecordingMode: {
|
}, switchMediaRecordingMode: {
|
||||||
}, setupMessageAutoremoveTimeout: {
|
}, setupMessageAutoremoveTimeout: {
|
||||||
|
|||||||
157
submodules/TelegramUI/TelegramUI/TextLinkHandling.swift
Normal file
157
submodules/TelegramUI/TelegramUI/TextLinkHandling.swift
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramUIPreferences
|
||||||
|
|
||||||
|
import SafariServices
|
||||||
|
|
||||||
|
func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) {
|
||||||
|
let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in
|
||||||
|
controller.present(controllerToPresent, in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
|
let openResolvedPeerImpl: (PeerId?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peerId, navigation in
|
||||||
|
openResolvedUrl(.peer(peerId, navigation), context: context, navigationController: (controller?.navigationController as? NavigationController), openPeer: { (peerId, navigation) in
|
||||||
|
switch navigation {
|
||||||
|
case let .chat(_, messageId):
|
||||||
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
|
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId, keepStack: .always)
|
||||||
|
}
|
||||||
|
case .info:
|
||||||
|
let peerSignal: Signal<Peer?, NoError>
|
||||||
|
peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init)
|
||||||
|
navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in
|
||||||
|
if let controller = controller, let peer = peer {
|
||||||
|
if let infoController = peerInfoController(context: context, peer: peer) {
|
||||||
|
(controller.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, present: presentImpl, dismissInput: {})
|
||||||
|
}
|
||||||
|
|
||||||
|
let openLinkImpl: (String) -> Void = { [weak controller] url in
|
||||||
|
navigateDisposable.set((resolveUrl(account: context.account, url: url) |> deliverOnMainQueue).start(next: { result in
|
||||||
|
if let controller = controller {
|
||||||
|
switch result {
|
||||||
|
case let .externalUrl(url):
|
||||||
|
context.sharedContext.applicationBindings.openUrl(url)
|
||||||
|
case let .peer(peerId, _):
|
||||||
|
openResolvedPeerImpl(peerId, .default)
|
||||||
|
case let .channelMessage(peerId, messageId):
|
||||||
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
|
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peerId), messageId: messageId)
|
||||||
|
}
|
||||||
|
case let .stickerPack(name):
|
||||||
|
controller.present(StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
|
||||||
|
case let .instantView(webpage, anchor):
|
||||||
|
(controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor))
|
||||||
|
case let .join(link):
|
||||||
|
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId in
|
||||||
|
openResolvedPeerImpl(peerId, .chat(textInputState: nil, messageId: nil))
|
||||||
|
}), in: .window(.root))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
let openPeerMentionImpl: (String) -> Void = { mention in
|
||||||
|
navigateDisposable.set((resolvePeerByName(account: context.account, name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peerId in
|
||||||
|
openResolvedPeerImpl(peerId, .default)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
switch action {
|
||||||
|
case .tap:
|
||||||
|
switch itemLink {
|
||||||
|
case let .url(url):
|
||||||
|
openLinkImpl(url)
|
||||||
|
case let .mention(mention):
|
||||||
|
openPeerMentionImpl(mention)
|
||||||
|
case let .hashtag(_, hashtag):
|
||||||
|
if let peerId = peerId {
|
||||||
|
let peerSignal = context.account.postbox.loadedPeerWithId(peerId)
|
||||||
|
let _ = (peerSignal
|
||||||
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
let searchController = HashtagSearchController(context: context, peer: peer, query: hashtag)
|
||||||
|
(controller.navigationController as? NavigationController)?.pushViewController(searchController)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .longTap:
|
||||||
|
switch itemLink {
|
||||||
|
case let .url(url):
|
||||||
|
let canOpenIn = availableOpenInOptions(context: context, item: .url(url: url)).count > 1
|
||||||
|
let openText = canOpenIn ? presentationData.strings.Conversation_FileOpenIn : presentationData.strings.Conversation_LinkDialogOpen
|
||||||
|
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: url),
|
||||||
|
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
openLinkImpl(url)
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
UIPasteboard.general.string = url
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let link = URL(string: url) {
|
||||||
|
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]), ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])])
|
||||||
|
controller.present(actionSheet, in: .window(.root))
|
||||||
|
case let .mention(mention):
|
||||||
|
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: mention),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
openPeerMentionImpl(mention)
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
UIPasteboard.general.string = mention
|
||||||
|
})
|
||||||
|
]), ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])])
|
||||||
|
controller.present(actionSheet, in: .window(.root))
|
||||||
|
case let .hashtag(_, hashtag):
|
||||||
|
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: hashtag),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
let searchController = HashtagSearchController(context: context, peer: nil, query: hashtag)
|
||||||
|
(controller.navigationController as? NavigationController)?.pushViewController(searchController)
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
UIPasteboard.general.string = hashtag
|
||||||
|
})
|
||||||
|
]), ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])])
|
||||||
|
controller.present(actionSheet, in: .window(.root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -322,9 +322,17 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||||||
|
|
||||||
let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start()
|
let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start()
|
||||||
|
|
||||||
let availableAppIcons: Signal<[PresentationAppIcon], NoError> = .single(context.sharedContext.applicationBindings.getAvailableAlternateIcons())
|
let currentAppIcon: PresentationAppIcon?
|
||||||
|
let appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons()
|
||||||
|
if let alternateIconName = context.sharedContext.applicationBindings.getAlternateIconName() {
|
||||||
|
currentAppIcon = appIcons.filter { $0.name == alternateIconName }.first
|
||||||
|
} else {
|
||||||
|
currentAppIcon = appIcons.filter { $0.isDefault }.first
|
||||||
|
}
|
||||||
|
|
||||||
|
let availableAppIcons: Signal<[PresentationAppIcon], NoError> = .single(appIcons)
|
||||||
let currentAppIconName = ValuePromise<String?>()
|
let currentAppIconName = ValuePromise<String?>()
|
||||||
currentAppIconName.set(context.sharedContext.applicationBindings.getAlternateIconName() ?? "Blue")
|
currentAppIconName.set(currentAppIcon?.name ?? "Blue")
|
||||||
|
|
||||||
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
|
let arguments = ThemeSettingsControllerArguments(context: context, selectTheme: { theme in
|
||||||
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in
|
let _ = (context.sharedContext.accountManager.transaction { transaction -> Void in
|
||||||
|
|||||||
@ -8,9 +8,11 @@ import TelegramPresentationData
|
|||||||
|
|
||||||
private final class UpdateInfoControllerArguments {
|
private final class UpdateInfoControllerArguments {
|
||||||
let openAppStorePage: () -> Void
|
let openAppStorePage: () -> Void
|
||||||
|
let linkAction: (TextLinkItemActionType, TextLinkItem) -> Void
|
||||||
|
|
||||||
init(openAppStorePage: @escaping () -> Void) {
|
init(openAppStorePage: @escaping () -> Void, linkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void) {
|
||||||
self.openAppStorePage = openAppStorePage
|
self.openAppStorePage = openAppStorePage
|
||||||
|
self.linkAction = linkAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +22,7 @@ private enum UpdateInfoControllerSection: Int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum UpdateInfoControllerEntry: ItemListNodeEntry {
|
private enum UpdateInfoControllerEntry: ItemListNodeEntry {
|
||||||
case info(PresentationTheme, String, String, [MessageTextEntity])
|
case info(PresentationTheme, PresentationAppIcon?, String, String, [MessageTextEntity])
|
||||||
case update(PresentationTheme, String)
|
case update(PresentationTheme, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
@ -43,8 +45,8 @@ private enum UpdateInfoControllerEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
static func ==(lhs: UpdateInfoControllerEntry, rhs: UpdateInfoControllerEntry) -> Bool {
|
static func ==(lhs: UpdateInfoControllerEntry, rhs: UpdateInfoControllerEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .info(lhsTheme, lhsTitle, lhsText, lhsEntities):
|
case let .info(lhsTheme, lhsIcon, lhsTitle, lhsText, lhsEntities):
|
||||||
if case let .info(rhsTheme, rhsTitle, rhsText, rhsEntities) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText, lhsEntities == rhsEntities {
|
if case let .info(rhsTheme, rhsIcon, rhsTitle, rhsText, rhsEntities) = rhs, lhsTheme === rhsTheme, lhsIcon == rhsIcon, lhsTitle == rhsTitle, lhsText == rhsText, lhsEntities == rhsEntities {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -64,9 +66,10 @@ private enum UpdateInfoControllerEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
func item(_ arguments: UpdateInfoControllerArguments) -> ListViewItem {
|
func item(_ arguments: UpdateInfoControllerArguments) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .info(theme, title, text, entities):
|
case let .info(theme, icon, title, text, entities):
|
||||||
let text = stringWithAppliedEntities(text, entities: entities, baseColor: theme.list.itemPrimaryTextColor, linkColor: theme.list.itemAccentColor, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.bold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0))
|
return UpdateInfoItem(theme: theme, appIcon: icon, title: title, text: text, entities: entities, sectionId: self.section, style: .blocks, linkItemAction: { action, itemLink in
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text.string, sectionId: self.section)
|
arguments.linkAction(action, itemLink)
|
||||||
|
})
|
||||||
case let .update(theme, title):
|
case let .update(theme, title):
|
||||||
return ItemListActionItem(theme: theme, title: title, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(theme: theme, title: title, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openAppStorePage()
|
arguments.openAppStorePage()
|
||||||
@ -75,10 +78,10 @@ private enum UpdateInfoControllerEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateInfoControllerEntries(theme: PresentationTheme, strings: PresentationStrings, appUpdateInfo: AppUpdateInfo) -> [UpdateInfoControllerEntry] {
|
private func updateInfoControllerEntries(theme: PresentationTheme, strings: PresentationStrings, appIcon: PresentationAppIcon?, appUpdateInfo: AppUpdateInfo) -> [UpdateInfoControllerEntry] {
|
||||||
var entries: [UpdateInfoControllerEntry] = []
|
var entries: [UpdateInfoControllerEntry] = []
|
||||||
|
|
||||||
entries.append(.info(theme, strings.Update_AppVersion(appUpdateInfo.version).0, appUpdateInfo.text, appUpdateInfo.entities))
|
entries.append(.info(theme, appIcon, strings.Update_AppVersion(appUpdateInfo.version).0, appUpdateInfo.text, appUpdateInfo.entities))
|
||||||
entries.append(.update(theme, strings.Update_UpdateApp))
|
entries.append(.update(theme, strings.Update_UpdateApp))
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
@ -86,24 +89,48 @@ private func updateInfoControllerEntries(theme: PresentationTheme, strings: Pres
|
|||||||
|
|
||||||
public func updateInfoController(context: AccountContext, appUpdateInfo: AppUpdateInfo) -> ViewController {
|
public func updateInfoController(context: AccountContext, appUpdateInfo: AppUpdateInfo) -> ViewController {
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
|
var linkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)?
|
||||||
|
|
||||||
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
|
let navigateDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(navigateDisposable)
|
||||||
|
|
||||||
let arguments = UpdateInfoControllerArguments(openAppStorePage: {
|
let arguments = UpdateInfoControllerArguments(openAppStorePage: {
|
||||||
context.sharedContext.applicationBindings.openAppStorePage()
|
context.sharedContext.applicationBindings.openAppStorePage()
|
||||||
|
}, linkAction: { action, itemLink in
|
||||||
|
linkActionImpl?(action, itemLink)
|
||||||
})
|
})
|
||||||
|
|
||||||
let signal = context.sharedContext.presentationData
|
let signal = context.sharedContext.presentationData
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData -> (ItemListControllerState, (ItemListNodeState<UpdateInfoControllerEntry>, UpdateInfoControllerEntry.ItemGenerationArguments)) in
|
|> map { presentationData -> (ItemListControllerState, (ItemListNodeState<UpdateInfoControllerEntry>, UpdateInfoControllerEntry.ItemGenerationArguments)) in
|
||||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Update_Skip), style: .regular, enabled: true, action: {
|
let appIcon: PresentationAppIcon?
|
||||||
|
let appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons()
|
||||||
|
if let alternateIconName = context.sharedContext.applicationBindings.getAlternateIconName() {
|
||||||
|
appIcon = appIcons.filter { $0.name == alternateIconName }.first
|
||||||
|
} else {
|
||||||
|
appIcon = appIcons.filter { $0.isDefault }.first
|
||||||
|
}
|
||||||
|
|
||||||
|
let leftNavigationButton = appUpdateInfo.popup ? ItemListNavigationButton(content: .text(presentationData.strings.Update_Skip), style: .regular, enabled: true, action: {
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
})
|
}) : nil
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Update_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Update_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||||
let listState = ItemListNodeState(entries: updateInfoControllerEntries(theme: presentationData.theme, strings: presentationData.strings, appUpdateInfo: appUpdateInfo), style: .blocks, animateChanges: false)
|
let listState = ItemListNodeState(entries: updateInfoControllerEntries(theme: presentationData.theme, strings: presentationData.strings, appIcon: appIcon, appUpdateInfo: appUpdateInfo), style: .blocks, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
actionsDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
let controller = ItemListController(sharedContext: context.sharedContext, state: signal)
|
let controller = ItemListController(sharedContext: context.sharedContext, state: signal)
|
||||||
|
linkActionImpl = { [weak controller] action, itemLink in
|
||||||
|
if let strongController = controller {
|
||||||
|
handleTextLinkAction(context: context, peerId: nil, navigateDisposable: navigateDisposable, controller: strongController, action: action, itemLink: itemLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
dismissImpl = { [weak controller] in
|
dismissImpl = { [weak controller] in
|
||||||
controller?.view.endEditing(true)
|
controller?.view.endEditing(true)
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
|
|||||||
443
submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift
Normal file
443
submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
private func generateBorderImage(theme: PresentationTheme, bordered: Bool) -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
context.fillEllipse(in: bounds)
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
|
||||||
|
if bordered {
|
||||||
|
let lineWidth: CGFloat = 1.0
|
||||||
|
context.setLineWidth(lineWidth)
|
||||||
|
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||||
|
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||||
|
}
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 15, topCapHeight: 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateInfoItem: ListViewItem, ItemListItem {
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let appIcon: PresentationAppIcon?
|
||||||
|
let title: String
|
||||||
|
let text: String
|
||||||
|
let entities: [MessageTextEntity]
|
||||||
|
let sectionId: ItemListSectionId
|
||||||
|
let style: ItemListStyle
|
||||||
|
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
|
||||||
|
|
||||||
|
let tag: Any?
|
||||||
|
|
||||||
|
let selectable: Bool = false
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, appIcon: PresentationAppIcon?, title: String, text: String, entities: [MessageTextEntity], sectionId: ItemListSectionId, style: ItemListStyle, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
|
||||||
|
self.theme = theme
|
||||||
|
self.appIcon = appIcon
|
||||||
|
self.title = title
|
||||||
|
self.text = text
|
||||||
|
self.entities = entities
|
||||||
|
self.sectionId = sectionId
|
||||||
|
self.style = style
|
||||||
|
self.linkItemAction = linkItemAction
|
||||||
|
self.tag = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
|
async {
|
||||||
|
let node = UpdateInfoItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { _ in apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
if let nodeValue = node() as? UpdateInfoItemNode {
|
||||||
|
let makeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, { _ in
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let titleFont = Font.bold(17.0)
|
||||||
|
private let textFont = Font.regular(16.0)
|
||||||
|
private let textBoldFont = Font.medium(16.0)
|
||||||
|
private let textItalicFont = Font.italic(16.0)
|
||||||
|
private let textBoldItalicFont = Font.semiboldItalic(16.0)
|
||||||
|
private let textFixedFont = Font.regular(16.0)
|
||||||
|
|
||||||
|
class UpdateInfoItemNode: ListViewItemNode {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let topStripeNode: ASDisplayNode
|
||||||
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
private var linkHighlightingNode: LinkHighlightingNode?
|
||||||
|
|
||||||
|
private let iconNode: ASImageNode
|
||||||
|
private let overlayNode: ASImageNode
|
||||||
|
private let titleNode: TextNode
|
||||||
|
private let textNode: TextNode
|
||||||
|
|
||||||
|
private let activateArea: AccessibilityAreaNode
|
||||||
|
|
||||||
|
private var item: UpdateInfoItem?
|
||||||
|
|
||||||
|
var tag: Any? {
|
||||||
|
return self.item?.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
|
self.topStripeNode = ASDisplayNode()
|
||||||
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.bottomStripeNode = ASDisplayNode()
|
||||||
|
self.bottomStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
self.iconNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.overlayNode = ASImageNode()
|
||||||
|
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
self.overlayNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.titleNode = TextNode()
|
||||||
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.textNode = TextNode()
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode.contentMode = .left
|
||||||
|
self.textNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
|
self.highlightedBackgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.activateArea = AccessibilityAreaNode()
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
|
self.addSubnode(self.overlayNode)
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.activateArea)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||||
|
recognizer.tapActionAtPoint = { [weak self] point in
|
||||||
|
if let strongSelf = self, strongSelf.linkItemAtPoint(point) != nil {
|
||||||
|
return .waitForSingleTap
|
||||||
|
}
|
||||||
|
return .fail
|
||||||
|
}
|
||||||
|
recognizer.highlight = { [weak self] point in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateTouchesAtPoint(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.view.addGestureRecognizer(recognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: UpdateInfoItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
|
||||||
|
let currentItem = self.item
|
||||||
|
|
||||||
|
return { item, params, neighbors in
|
||||||
|
var updatedTheme: PresentationTheme?
|
||||||
|
var updatedAppIcon: PresentationAppIcon?
|
||||||
|
|
||||||
|
if currentItem?.theme !== item.theme {
|
||||||
|
updatedTheme = item.theme
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentItem?.appIcon != item.appIcon {
|
||||||
|
updatedAppIcon = item.appIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
let textColor: UIColor = item.theme.list.itemPrimaryTextColor
|
||||||
|
|
||||||
|
let inset: CGFloat
|
||||||
|
let itemBackgroundColor: UIColor
|
||||||
|
let itemSeparatorColor: UIColor
|
||||||
|
|
||||||
|
switch item.style {
|
||||||
|
case .plain:
|
||||||
|
itemBackgroundColor = item.theme.list.plainBackgroundColor
|
||||||
|
itemSeparatorColor = item.theme.list.itemPlainSeparatorColor
|
||||||
|
inset = 14.0 + params.leftInset
|
||||||
|
case .blocks:
|
||||||
|
itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||||
|
itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
|
inset = 14.0 + params.rightInset
|
||||||
|
}
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 88.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let string = stringWithAppliedEntities(item.text, entities: item.entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont)
|
||||||
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let contentSize: CGSize
|
||||||
|
let insets: UIEdgeInsets
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
|
switch item.style {
|
||||||
|
case .plain:
|
||||||
|
contentSize = CGSize(width: params.width, height: 88.0 + textLayout.size.height + inset)
|
||||||
|
insets = itemListNeighborsPlainInsets(neighbors)
|
||||||
|
case .blocks:
|
||||||
|
contentSize = CGSize(width: params.width, height: 88.0 + textLayout.size.height + inset)
|
||||||
|
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
let layoutSize = layout.size
|
||||||
|
|
||||||
|
return (layout, { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
|
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||||
|
strongSelf.activateArea.accessibilityLabel = item.text
|
||||||
|
|
||||||
|
if let _ = updatedTheme {
|
||||||
|
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
|
||||||
|
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||||
|
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||||
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
|
var bordered = true
|
||||||
|
if let appIcon = item.appIcon {
|
||||||
|
switch appIcon.name {
|
||||||
|
case "BlueFilled":
|
||||||
|
bordered = false
|
||||||
|
case "BlackFilled":
|
||||||
|
bordered = false
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: bordered)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let appIcon = updatedAppIcon, let image = UIImage(named: appIcon.imageName, in: Bundle.main, compatibleWith: nil) {
|
||||||
|
strongSelf.iconNode.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = titleApply()
|
||||||
|
let _ = textApply()
|
||||||
|
|
||||||
|
switch item.style {
|
||||||
|
case .plain:
|
||||||
|
if strongSelf.backgroundNode.supernode != nil {
|
||||||
|
strongSelf.backgroundNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if strongSelf.topStripeNode.supernode != nil {
|
||||||
|
strongSelf.topStripeNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: inset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - inset, height: separatorHeight))
|
||||||
|
case .blocks:
|
||||||
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
|
}
|
||||||
|
if strongSelf.topStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||||
|
}
|
||||||
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||||
|
}
|
||||||
|
switch neighbors.top {
|
||||||
|
case .sameSection(false):
|
||||||
|
strongSelf.topStripeNode.isHidden = true
|
||||||
|
default:
|
||||||
|
strongSelf.topStripeNode.isHidden = false
|
||||||
|
}
|
||||||
|
let bottomStripeInset: CGFloat
|
||||||
|
let bottomStripeOffset: CGFloat
|
||||||
|
switch neighbors.bottom {
|
||||||
|
case .sameSection(false):
|
||||||
|
bottomStripeInset = 16.0
|
||||||
|
bottomStripeOffset = -separatorHeight
|
||||||
|
default:
|
||||||
|
bottomStripeInset = 0.0
|
||||||
|
bottomStripeOffset = 0.0
|
||||||
|
}
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||||
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: 62.0, height: 62.0))
|
||||||
|
strongSelf.iconNode.frame = iconFrame
|
||||||
|
strongSelf.overlayNode.frame = iconFrame
|
||||||
|
|
||||||
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: iconFrame.maxX + inset, y: iconFrame.minY + ceil((iconFrame.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||||
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: inset, y: iconFrame.maxY + inset), size: textLayout.size)
|
||||||
|
|
||||||
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||||
|
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||||
|
|
||||||
|
if highlighted && self.linkItemAtPoint(point) == nil {
|
||||||
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
if self.highlightedBackgroundNode.supernode == nil {
|
||||||
|
var anchorNode: ASDisplayNode?
|
||||||
|
if self.bottomStripeNode.supernode != nil {
|
||||||
|
anchorNode = self.bottomStripeNode
|
||||||
|
} else if self.topStripeNode.supernode != nil {
|
||||||
|
anchorNode = self.topStripeNode
|
||||||
|
} else if self.backgroundNode.supernode != nil {
|
||||||
|
anchorNode = self.backgroundNode
|
||||||
|
}
|
||||||
|
if let anchorNode = anchorNode {
|
||||||
|
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||||
|
} else {
|
||||||
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.highlightedBackgroundNode.supernode != nil {
|
||||||
|
if animated {
|
||||||
|
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if completed {
|
||||||
|
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
} else {
|
||||||
|
self.highlightedBackgroundNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
|
switch recognizer.state {
|
||||||
|
case .ended:
|
||||||
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
|
switch gesture {
|
||||||
|
case .tap, .longTap:
|
||||||
|
if let item = self.item, let linkItem = self.linkItemAtPoint(location) {
|
||||||
|
item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? {
|
||||||
|
let textNodeFrame = self.textNode.frame
|
||||||
|
if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
|
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
|
return .url(url)
|
||||||
|
} else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||||
|
return .mention(peerName)
|
||||||
|
} else if let hashtag = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||||
|
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||||
|
if let item = self.item {
|
||||||
|
var rects: [CGRect]?
|
||||||
|
if let point = point {
|
||||||
|
let textNodeFrame = self.textNode.frame
|
||||||
|
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
|
let possibleNames: [String] = [
|
||||||
|
TelegramTextAttributes.URL,
|
||||||
|
TelegramTextAttributes.PeerMention,
|
||||||
|
TelegramTextAttributes.PeerTextMention,
|
||||||
|
TelegramTextAttributes.BotCommand,
|
||||||
|
TelegramTextAttributes.Hashtag
|
||||||
|
]
|
||||||
|
for name in possibleNames {
|
||||||
|
if let _ = attributes[NSAttributedStringKey(rawValue: name)] {
|
||||||
|
rects = self.textNode.attributeRects(name: name, at: index)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let rects = rects {
|
||||||
|
let linkHighlightingNode: LinkHighlightingNode
|
||||||
|
if let current = self.linkHighlightingNode {
|
||||||
|
linkHighlightingNode = current
|
||||||
|
} else {
|
||||||
|
linkHighlightingNode = LinkHighlightingNode(color: item.theme.list.itemAccentColor.withAlphaComponent(0.5))
|
||||||
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||||
|
}
|
||||||
|
linkHighlightingNode.frame = self.textNode.frame
|
||||||
|
linkHighlightingNode.updateRects(rects)
|
||||||
|
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
||||||
|
self.linkHighlightingNode = nil
|
||||||
|
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||||
|
linkHighlightingNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -386,27 +386,27 @@ private enum UserInfoEntry: ItemListNodeEntry {
|
|||||||
case let .calls(theme, strings, dateTimeFormat, messages):
|
case let .calls(theme, strings, dateTimeFormat, messages):
|
||||||
return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain)
|
return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain)
|
||||||
case let .about(theme, peer, text, value):
|
case let .about(theme, peer, text, value):
|
||||||
var enabledEntitiyTypes: EnabledEntityTypes = []
|
var enabledEntityTypes: EnabledEntityTypes = []
|
||||||
if let peer = peer as? TelegramUser, let _ = peer.botInfo {
|
if let peer = peer as? TelegramUser, let _ = peer.botInfo {
|
||||||
enabledEntitiyTypes = [.url, .mention, .hashtag]
|
enabledEntityTypes = [.url, .mention, .hashtag]
|
||||||
}
|
}
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntitiyTypes: enabledEntitiyTypes, multiline: true, sectionId: self.section, action: nil, longTapAction: {
|
return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntityTypes: enabledEntityTypes, multiline: true, sectionId: self.section, action: nil, longTapAction: {
|
||||||
arguments.displayAboutContextMenu(value)
|
arguments.displayAboutContextMenu(value)
|
||||||
}, linkItemAction: { action, itemLink in
|
}, linkItemAction: { action, itemLink in
|
||||||
arguments.aboutLinkAction(action, itemLink)
|
arguments.aboutLinkAction(action, itemLink)
|
||||||
}, tag: UserInfoEntryTag.about)
|
}, tag: UserInfoEntryTag.about)
|
||||||
case let .phoneNumber(theme, _, label, value, isMain):
|
case let .phoneNumber(theme, _, label, value, isMain):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: isMain ? .highlighted : .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: isMain ? .highlighted : .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: {
|
||||||
arguments.openCallMenu(value)
|
arguments.openCallMenu(value)
|
||||||
}, longTapAction: {
|
}, longTapAction: {
|
||||||
arguments.displayCopyContextMenu(.phoneNumber, value)
|
arguments.displayCopyContextMenu(.phoneNumber, value)
|
||||||
}, tag: UserInfoEntryTag.phoneNumber)
|
}, tag: UserInfoEntryTag.phoneNumber)
|
||||||
case let .requestPhoneNumber(theme, label, value):
|
case let .requestPhoneNumber(theme, label, value):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: {
|
||||||
arguments.requestPhoneNumber()
|
arguments.requestPhoneNumber()
|
||||||
})
|
})
|
||||||
case let .userName(theme, text, value):
|
case let .userName(theme, text, value):
|
||||||
return ItemListTextWithLabelItem(theme: theme, label: text, text: "@\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
|
return ItemListTextWithLabelItem(theme: theme, label: text, text: "@\(value)", textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: {
|
||||||
arguments.displayUsernameContextMenu("@\(value)")
|
arguments.displayUsernameContextMenu("@\(value)")
|
||||||
}, longTapAction: {
|
}, longTapAction: {
|
||||||
arguments.displayCopyContextMenu(.username, "@\(value)")
|
arguments.displayCopyContextMenu(.username, "@\(value)")
|
||||||
@ -1178,113 +1178,114 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
|
|||||||
|
|
||||||
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peerView.get(), deviceContacts, context.account.postbox.combinedView(keys: [.peerChatState(peerId: peerId), globalNotificationsKey]))
|
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peerView.get(), deviceContacts, context.account.postbox.combinedView(keys: [.peerChatState(peerId: peerId), globalNotificationsKey]))
|
||||||
|> map { presentationData, state, view, deviceContacts, combinedView -> (ItemListControllerState, (ItemListNodeState<UserInfoEntry>, UserInfoEntry.ItemGenerationArguments)) in
|
|> map { presentationData, state, view, deviceContacts, combinedView -> (ItemListControllerState, (ItemListNodeState<UserInfoEntry>, UserInfoEntry.ItemGenerationArguments)) in
|
||||||
let peer = peerViewMainPeer(view.0)
|
let peer = peerViewMainPeer(view.0)
|
||||||
|
|
||||||
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
|
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
|
||||||
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
|
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
|
||||||
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
|
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
|
||||||
globalNotificationSettings = settings
|
globalNotificationSettings = settings
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let peer = peer {
|
|
||||||
let _ = cachedAvatarEntries.modify { value in
|
if let peer = peer {
|
||||||
if value != nil {
|
let _ = cachedAvatarEntries.modify { value in
|
||||||
return value
|
if value != nil {
|
||||||
} else {
|
return value
|
||||||
let promise = Promise<[AvatarGalleryEntry]>()
|
|
||||||
promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer))
|
|
||||||
return promise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var leftNavigationButton: ItemListNavigationButton?
|
|
||||||
let rightNavigationButton: ItemListNavigationButton
|
|
||||||
if let editingState = state.editingState {
|
|
||||||
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
|
||||||
updateState {
|
|
||||||
$0.withUpdatedEditingState(nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var doneEnabled = true
|
|
||||||
if let editingName = editingState.editingName, editingName.isEmpty {
|
|
||||||
doneEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if state.savingData {
|
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: doneEnabled, action: {})
|
|
||||||
} else {
|
} else {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: doneEnabled, action: {
|
let promise = Promise<[AvatarGalleryEntry]>()
|
||||||
var updateName: ItemListAvatarAndNameInfoItemName?
|
promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer))
|
||||||
updateState { state in
|
return promise
|
||||||
if let editingState = state.editingState, let editingName = editingState.editingName {
|
}
|
||||||
if let user = peer {
|
}
|
||||||
if ItemListAvatarAndNameInfoItemName(user) != editingName {
|
}
|
||||||
updateName = editingName
|
var leftNavigationButton: ItemListNavigationButton?
|
||||||
}
|
let rightNavigationButton: ItemListNavigationButton
|
||||||
|
if let editingState = state.editingState {
|
||||||
|
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||||
|
updateState {
|
||||||
|
$0.withUpdatedEditingState(nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var doneEnabled = true
|
||||||
|
if let editingName = editingState.editingName, editingName.isEmpty {
|
||||||
|
doneEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.savingData {
|
||||||
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: doneEnabled, action: {})
|
||||||
|
} else {
|
||||||
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: doneEnabled, action: {
|
||||||
|
var updateName: ItemListAvatarAndNameInfoItemName?
|
||||||
|
updateState { state in
|
||||||
|
if let editingState = state.editingState, let editingName = editingState.editingName {
|
||||||
|
if let user = peer {
|
||||||
|
if ItemListAvatarAndNameInfoItemName(user) != editingName {
|
||||||
|
updateName = editingName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if updateName != nil {
|
|
||||||
return state.withUpdatedSavingData(true)
|
|
||||||
} else {
|
|
||||||
return state.withUpdatedEditingState(nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if updateName != nil {
|
||||||
if let updateName = updateName, case let .personName(firstName, lastName) = updateName {
|
return state.withUpdatedSavingData(true)
|
||||||
updatePeerNameDisposable.set((updateContactName(account: context.account, peerId: peerId, firstName: firstName, lastName: lastName)
|
} else {
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
return state.withUpdatedEditingState(nil)
|
||||||
updateState { state in
|
}
|
||||||
return state.withUpdatedSavingData(false)
|
}
|
||||||
|
|
||||||
|
if let updateName = updateName, case let .personName(firstName, lastName) = updateName {
|
||||||
|
updatePeerNameDisposable.set((updateContactName(account: context.account, peerId: peerId, firstName: firstName, lastName: lastName)
|
||||||
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedSavingData(false)
|
||||||
|
}
|
||||||
|
}, completed: {
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedSavingData(false).withUpdatedEditingState(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId)
|
||||||
|
|> mapToSignal { peer, _ -> Signal<Void, NoError> in
|
||||||
|
guard let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty else {
|
||||||
|
return .complete()
|
||||||
}
|
}
|
||||||
}, completed: {
|
return (context.sharedContext.contactDataManager?.basicDataForNormalizedPhoneNumber(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) ?? .single([]))
|
||||||
updateState { state in
|
|> take(1)
|
||||||
return state.withUpdatedSavingData(false).withUpdatedEditingState(nil)
|
|> mapToSignal { records -> Signal<Void, NoError> in
|
||||||
}
|
var signals: [Signal<DeviceContactExtendedData?, NoError>] = []
|
||||||
|
if let contactDataManager = context.sharedContext.contactDataManager {
|
||||||
let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId)
|
for (id, basicData) in records {
|
||||||
|> mapToSignal { peer, _ -> Signal<Void, NoError> in
|
signals.append(contactDataManager.appendContactData(DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: basicData.phoneNumbers), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []), to: id))
|
||||||
guard let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty else {
|
}
|
||||||
|
}
|
||||||
|
return combineLatest(signals)
|
||||||
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
return (context.sharedContext.contactDataManager?.basicDataForNormalizedPhoneNumber(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) ?? .single([]))
|
}
|
||||||
|> take(1)
|
}).start()
|
||||||
|> mapToSignal { records -> Signal<Void, NoError> in
|
}))
|
||||||
var signals: [Signal<DeviceContactExtendedData?, NoError>] = []
|
|
||||||
if let contactDataManager = context.sharedContext.contactDataManager {
|
|
||||||
for (id, basicData) in records {
|
|
||||||
signals.append(contactDataManager.appendContactData(DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: basicData.phoneNumbers), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []), to: id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return combineLatest(signals)
|
|
||||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
|
||||||
if let user = peer {
|
|
||||||
updateState { state in
|
|
||||||
return state.withUpdatedEditingState(UserInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(user)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.UserInfo_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: nil)
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
||||||
let listState = ItemListNodeState(entries: userInfoEntries(account: context.account, presentationData: presentationData, view: view.0, cachedPeerData: view.1, deviceContacts: deviceContacts, mode: mode, state: state, peerChatState: (combinedView.views[.peerChatState(peerId: peerId)] as? PeerChatStateView)?.chatState, globalNotificationSettings: globalNotificationSettings), style: .plain)
|
if let user = peer {
|
||||||
|
updateState { state in
|
||||||
return (controllerState, (listState, arguments))
|
return state.withUpdatedEditingState(UserInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(user)))
|
||||||
} |> afterDisposed {
|
}
|
||||||
actionsDisposable.dispose()
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.UserInfo_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: nil)
|
||||||
|
let listState = ItemListNodeState(entries: userInfoEntries(account: context.account, presentationData: presentationData, view: view.0, cachedPeerData: view.1, deviceContacts: deviceContacts, mode: mode, state: state, peerChatState: (combinedView.views[.peerChatState(peerId: peerId)] as? PeerChatStateView)?.chatState, globalNotificationSettings: globalNotificationSettings), style: .plain)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
actionsDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
let controller = ItemListController(context: context, state: signal)
|
let controller = ItemListController(context: context, state: signal)
|
||||||
|
|
||||||
@ -1449,7 +1450,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
|
|||||||
}
|
}
|
||||||
aboutLinkActionImpl = { [weak controller] action, itemLink in
|
aboutLinkActionImpl = { [weak controller] action, itemLink in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
handlePeerInfoAboutTextAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
handleTextLinkAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayAboutContextMenuImpl = { [weak controller] text in
|
displayAboutContextMenuImpl = { [weak controller] text in
|
||||||
|
|||||||
@ -152,6 +152,8 @@
|
|||||||
09E4A805223D4A5A0038140F /* OpenSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A804223D4A5A0038140F /* OpenSettings.swift */; };
|
09E4A805223D4A5A0038140F /* OpenSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A804223D4A5A0038140F /* OpenSettings.swift */; };
|
||||||
09E4A807223D4B860038140F /* AccountUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A806223D4B860038140F /* AccountUtils.swift */; };
|
09E4A807223D4B860038140F /* AccountUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A806223D4B860038140F /* AccountUtils.swift */; };
|
||||||
09EC0DE722C67FB100E7185B /* UpdateInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */; };
|
09EC0DE722C67FB100E7185B /* UpdateInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */; };
|
||||||
|
09EC0DEB22CAFF1400E7185B /* UpdateInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DEA22CAFF1400E7185B /* UpdateInfoItem.swift */; };
|
||||||
|
09EC0DED22CB583C00E7185B /* TextLinkHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DEC22CB583C00E7185B /* TextLinkHandling.swift */; };
|
||||||
09EDAD26220D30980012A50B /* AutodownloadConnectionTypeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */; };
|
09EDAD26220D30980012A50B /* AutodownloadConnectionTypeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */; };
|
||||||
09EDAD2A220DA6A40012A50B /* VolumeButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD29220DA6A40012A50B /* VolumeButtons.swift */; };
|
09EDAD2A220DA6A40012A50B /* VolumeButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD29220DA6A40012A50B /* VolumeButtons.swift */; };
|
||||||
09EDAD2C2211552F0012A50B /* AutodownloadMediaCategoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */; };
|
09EDAD2C2211552F0012A50B /* AutodownloadMediaCategoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */; };
|
||||||
@ -1372,6 +1374,8 @@
|
|||||||
09E4A804223D4A5A0038140F /* OpenSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSettings.swift; sourceTree = "<group>"; };
|
09E4A804223D4A5A0038140F /* OpenSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSettings.swift; sourceTree = "<group>"; };
|
||||||
09E4A806223D4B860038140F /* AccountUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUtils.swift; sourceTree = "<group>"; };
|
09E4A806223D4B860038140F /* AccountUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUtils.swift; sourceTree = "<group>"; };
|
||||||
09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateInfoController.swift; sourceTree = "<group>"; };
|
09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateInfoController.swift; sourceTree = "<group>"; };
|
||||||
|
09EC0DEA22CAFF1400E7185B /* UpdateInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateInfoItem.swift; sourceTree = "<group>"; };
|
||||||
|
09EC0DEC22CB583C00E7185B /* TextLinkHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkHandling.swift; sourceTree = "<group>"; };
|
||||||
09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadConnectionTypeController.swift; sourceTree = "<group>"; };
|
09EDAD25220D30980012A50B /* AutodownloadConnectionTypeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadConnectionTypeController.swift; sourceTree = "<group>"; };
|
||||||
09EDAD29220DA6A40012A50B /* VolumeButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtons.swift; sourceTree = "<group>"; };
|
09EDAD29220DA6A40012A50B /* VolumeButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtons.swift; sourceTree = "<group>"; };
|
||||||
09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadMediaCategoryController.swift; sourceTree = "<group>"; };
|
09EDAD2B2211552F0012A50B /* AutodownloadMediaCategoryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadMediaCategoryController.swift; sourceTree = "<group>"; };
|
||||||
@ -2743,6 +2747,7 @@
|
|||||||
D0FC194C201F82A000FEDBB2 /* OpenResolvedUrl.swift */,
|
D0FC194C201F82A000FEDBB2 /* OpenResolvedUrl.swift */,
|
||||||
D023836F1DDF0462004018B6 /* UrlHandling.swift */,
|
D023836F1DDF0462004018B6 /* UrlHandling.swift */,
|
||||||
09E4A804223D4A5A0038140F /* OpenSettings.swift */,
|
09E4A804223D4A5A0038140F /* OpenSettings.swift */,
|
||||||
|
09EC0DEC22CB583C00E7185B /* TextLinkHandling.swift */,
|
||||||
);
|
);
|
||||||
name = Routing;
|
name = Routing;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2777,6 +2782,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */,
|
09EC0DE622C67FB100E7185B /* UpdateInfoController.swift */,
|
||||||
|
09EC0DEA22CAFF1400E7185B /* UpdateInfoItem.swift */,
|
||||||
);
|
);
|
||||||
name = Update;
|
name = Update;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -6037,6 +6043,7 @@
|
|||||||
09F664C821EB4A2600AB7E26 /* ThemeGridSearchItem.swift in Sources */,
|
09F664C821EB4A2600AB7E26 /* ThemeGridSearchItem.swift in Sources */,
|
||||||
09A218D9229EE1B600DE6898 /* HorizontalStickerGridItem.swift in Sources */,
|
09A218D9229EE1B600DE6898 /* HorizontalStickerGridItem.swift in Sources */,
|
||||||
D07E413B208A432100FCA8F0 /* ChatListTitleProxyNode.swift in Sources */,
|
D07E413B208A432100FCA8F0 /* ChatListTitleProxyNode.swift in Sources */,
|
||||||
|
09EC0DED22CB583C00E7185B /* TextLinkHandling.swift in Sources */,
|
||||||
D080B27F1F4C7C6000AA3847 /* InstantPageManagedMediaId.swift in Sources */,
|
D080B27F1F4C7C6000AA3847 /* InstantPageManagedMediaId.swift in Sources */,
|
||||||
D08984F02114AE0C00918162 /* DataPrivacySettingsController.swift in Sources */,
|
D08984F02114AE0C00918162 /* DataPrivacySettingsController.swift in Sources */,
|
||||||
D087BFAD1F741B9D003FD209 /* ShareContentContainerNode.swift in Sources */,
|
D087BFAD1F741B9D003FD209 /* ShareContentContainerNode.swift in Sources */,
|
||||||
@ -6171,6 +6178,7 @@
|
|||||||
D0E9BA651F055B4500F079A4 /* BotCheckoutNativeCardEntryController.swift in Sources */,
|
D0E9BA651F055B4500F079A4 /* BotCheckoutNativeCardEntryController.swift in Sources */,
|
||||||
D02D60B3206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift in Sources */,
|
D02D60B3206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift in Sources */,
|
||||||
D00ACA5A2022897D0045D427 /* ProcessedPeerRestrictionText.swift in Sources */,
|
D00ACA5A2022897D0045D427 /* ProcessedPeerRestrictionText.swift in Sources */,
|
||||||
|
09EC0DEB22CAFF1400E7185B /* UpdateInfoItem.swift in Sources */,
|
||||||
D0192D3C210A44D00005FA10 /* DeviceContactData.swift in Sources */,
|
D0192D3C210A44D00005FA10 /* DeviceContactData.swift in Sources */,
|
||||||
D0EC6E391EB9F58900EBF1C3 /* ItemListCheckboxItem.swift in Sources */,
|
D0EC6E391EB9F58900EBF1C3 /* ItemListCheckboxItem.swift in Sources */,
|
||||||
D0EC6E3A1EB9F58900EBF1C3 /* ItemListSwitchItem.swift in Sources */,
|
D0EC6E3A1EB9F58900EBF1C3 /* ItemListSwitchItem.swift in Sources */,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user