mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Folder updates
This commit is contained in:
parent
33ffaffc8d
commit
dfdde96d63
@ -9118,3 +9118,5 @@ Sorry for the inconvenience.";
|
|||||||
"Premium.MaxSharedFolderLinksFinalText" = "Sorry, you can only create **%1$@** invite links";
|
"Premium.MaxSharedFolderLinksFinalText" = "Sorry, you can only create **%1$@** invite links";
|
||||||
|
|
||||||
"Premium.GiftedTitle.Someone" = "Someone";
|
"Premium.GiftedTitle.Someone" = "Someone";
|
||||||
|
|
||||||
|
"Channel.AdminLog.JoinedViaFolderInviteLink" = "%1$@ joined via invite link %2$@ (community)";
|
||||||
|
|||||||
@ -20,4 +20,6 @@ public protocol ChatListController: ViewController {
|
|||||||
func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
|
func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
|
||||||
|
|
||||||
func playSignUpCompletedAnimation()
|
func playSignUpCompletedAnimation()
|
||||||
|
|
||||||
|
func navigateToFolder(folderId: Int32, completion: @escaping () -> Void)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1546,7 +1546,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
for filter in filters {
|
for filter in filters {
|
||||||
if filter.id == filterId, case let .filter(_, title, _, data) = filter {
|
if filter.id == filterId, case let .filter(_, title, _, data) = filter {
|
||||||
if !data.includePeers.peers.isEmpty {
|
if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, badge: ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, badge: ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { c, f in
|
}, action: { c, f in
|
||||||
@ -2709,6 +2709,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func navigateToFolder(folderId: Int32, completion: @escaping () -> Void) {
|
||||||
|
let _ = (self.chatListDisplayNode.mainContainerNode.availableFiltersSignal
|
||||||
|
|> filter { filters in
|
||||||
|
return filters.contains(where: { item in
|
||||||
|
if case let .filter(filter) = item, filter.id == folderId {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|> map { _ -> Bool in
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|> timeout(1.0, queue: .mainQueue(), alternate: .single(false))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id != folderId {
|
||||||
|
self.chatListDisplayNode.mainContainerNode.switchToFilter(id: .filter(folderId), completion: {
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func askForFilterRemoval(id: Int32) {
|
private func askForFilterRemoval(id: Int32) {
|
||||||
let apply: () -> Void = { [weak self] in
|
let apply: () -> Void = { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -2753,18 +2784,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
if case let .filter(_, title, _, data) = filter, data.isShared {
|
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||||
let _ = (combineLatest(
|
let _ = (combineLatest(
|
||||||
self.context.engine.data.get(
|
self.context.engine.data.get(
|
||||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||||
|
EngineDataMap(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init(id:)))
|
||||||
),
|
),
|
||||||
self.context.engine.peers.getExportedChatFolderLinks(id: id),
|
self.context.engine.peers.getExportedChatFolderLinks(id: id),
|
||||||
self.context.engine.peers.requestLeaveChatFolderSuggestions(folderId: id)
|
self.context.engine.peers.requestLeaveChatFolderSuggestions(folderId: id)
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peers, links, defaultSelectedPeerIds in
|
|> deliverOnMainQueue).start(next: { [weak self] peerData, links, defaultSelectedPeerIds in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = self.presentationData
|
let presentationData = self.presentationData
|
||||||
|
|
||||||
|
let peers = peerData.0
|
||||||
|
|
||||||
|
var memberCounts: [EnginePeer.Id: Int] = [:]
|
||||||
|
for (id, count) in peerData.1 {
|
||||||
|
if let count {
|
||||||
|
memberCounts[id] = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hasLinks = false
|
var hasLinks = false
|
||||||
if let links, !links.isEmpty {
|
if let links, !links.isEmpty {
|
||||||
hasLinks = true
|
hasLinks = true
|
||||||
@ -2788,7 +2829,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alreadyMemberPeerIds: Set()
|
alreadyMemberPeerIds: Set(),
|
||||||
|
memberCounts: memberCounts
|
||||||
),
|
),
|
||||||
completion: { [weak self] in
|
completion: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
|||||||
@ -513,7 +513,16 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
|||||||
|
|
||||||
private var itemNodes: [ChatListFilterTabEntryId: ChatListContainerItemNode] = [:]
|
private var itemNodes: [ChatListFilterTabEntryId: ChatListContainerItemNode] = [:]
|
||||||
private var pendingItemNode: (ChatListFilterTabEntryId, ChatListContainerItemNode, Disposable)?
|
private var pendingItemNode: (ChatListFilterTabEntryId, ChatListContainerItemNode, Disposable)?
|
||||||
private(set) var availableFilters: [ChatListContainerNodeFilter] = [.all]
|
private(set) var availableFilters: [ChatListContainerNodeFilter] = [.all] {
|
||||||
|
didSet {
|
||||||
|
self.availableFiltersPromise.set(self.availableFilters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private let availableFiltersPromise = ValuePromise<[ChatListContainerNodeFilter]>([.all], ignoreRepeated: true)
|
||||||
|
var availableFiltersSignal: Signal<[ChatListContainerNodeFilter], NoError> {
|
||||||
|
return self.availableFiltersPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
private var filtersLimit: Int32? = nil
|
private var filtersLimit: Int32? = nil
|
||||||
private var selectedId: ChatListFilterTabEntryId
|
private var selectedId: ChatListFilterTabEntryId
|
||||||
|
|
||||||
|
|||||||
@ -31,10 +31,12 @@ private final class ChatListFilterPresetControllerArguments {
|
|||||||
let setItemIdWithRevealedOptions: (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void
|
let setItemIdWithRevealedOptions: (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void
|
||||||
let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void
|
let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void
|
||||||
let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void
|
let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void
|
||||||
|
let clearFocus: () -> Void
|
||||||
let focusOnName: () -> Void
|
let focusOnName: () -> Void
|
||||||
let expandSection: (FilterSection) -> Void
|
let expandSection: (FilterSection) -> Void
|
||||||
let createLink: () -> Void
|
let createLink: () -> Void
|
||||||
let openLink: (ExportedChatFolderLink) -> Void
|
let openLink: (ExportedChatFolderLink) -> Void
|
||||||
|
let removeLink: (ExportedChatFolderLink) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -46,10 +48,12 @@ private final class ChatListFilterPresetControllerArguments {
|
|||||||
setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void,
|
setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void,
|
||||||
deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void,
|
deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void,
|
||||||
deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void,
|
deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void,
|
||||||
|
clearFocus: @escaping () -> Void,
|
||||||
focusOnName: @escaping () -> Void,
|
focusOnName: @escaping () -> Void,
|
||||||
expandSection: @escaping (FilterSection) -> Void,
|
expandSection: @escaping (FilterSection) -> Void,
|
||||||
createLink: @escaping () -> Void,
|
createLink: @escaping () -> Void,
|
||||||
openLink: @escaping (ExportedChatFolderLink) -> Void
|
openLink: @escaping (ExportedChatFolderLink) -> Void,
|
||||||
|
removeLink: @escaping (ExportedChatFolderLink) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updateState = updateState
|
self.updateState = updateState
|
||||||
@ -60,10 +64,12 @@ private final class ChatListFilterPresetControllerArguments {
|
|||||||
self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions
|
self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions
|
||||||
self.deleteIncludeCategory = deleteIncludeCategory
|
self.deleteIncludeCategory = deleteIncludeCategory
|
||||||
self.deleteExcludeCategory = deleteExcludeCategory
|
self.deleteExcludeCategory = deleteExcludeCategory
|
||||||
|
self.clearFocus = clearFocus
|
||||||
self.focusOnName = focusOnName
|
self.focusOnName = focusOnName
|
||||||
self.expandSection = expandSection
|
self.expandSection = expandSection
|
||||||
self.createLink = createLink
|
self.createLink = createLink
|
||||||
self.openLink = openLink
|
self.openLink = openLink
|
||||||
|
self.removeLink = removeLink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,10 +322,10 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
case excludePeerInfo(String)
|
case excludePeerInfo(String)
|
||||||
case includeExpand(String)
|
case includeExpand(String)
|
||||||
case excludeExpand(String)
|
case excludeExpand(String)
|
||||||
case inviteLinkHeader
|
case inviteLinkHeader(hasLinks: Bool)
|
||||||
case inviteLinkCreate(hasLinks: Bool)
|
case inviteLinkCreate(hasLinks: Bool)
|
||||||
case inviteLink(Int, ExportedChatFolderLink)
|
case inviteLink(Int, ExportedChatFolderLink)
|
||||||
case inviteLinkInfo
|
case inviteLinkInfo(text: String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -434,14 +440,16 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
case let .nameHeader(title):
|
case let .nameHeader(title):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||||
case let .name(placeholder, value):
|
case let .name(placeholder, value):
|
||||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), clearType: .always, maxLength: 12, sectionId: self.section, textUpdated: { value in
|
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .done, clearType: .always, maxLength: 12, sectionId: self.section, textUpdated: { value in
|
||||||
arguments.updateState { current in
|
arguments.updateState { current in
|
||||||
var state = current
|
var state = current
|
||||||
state.name = value
|
state.name = value
|
||||||
state.changedName = true
|
state.changedName = true
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}, action: {}, cleared: {
|
}, action: {
|
||||||
|
arguments.clearFocus()
|
||||||
|
}, cleared: {
|
||||||
arguments.focusOnName()
|
arguments.focusOnName()
|
||||||
})
|
})
|
||||||
case .includePeersHeader(let text), .excludePeersHeader(let text):
|
case .includePeersHeader(let text), .excludePeersHeader(let text):
|
||||||
@ -516,23 +524,23 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
|
||||||
arguments.expandSection(.exclude)
|
arguments.expandSection(.exclude)
|
||||||
})
|
})
|
||||||
case .inviteLinkHeader:
|
case let .inviteLinkHeader(hasLinks):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", badge: "NEW", sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: "SHARE FOLDER", badge: hasLinks ? nil : "NEW", sectionId: self.section)
|
||||||
case let .inviteLinkCreate(hasLinks):
|
case let .inviteLinkCreate(hasLinks):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? "Create a New Link" : "Share Folder", sectionId: self.section, editing: false, action: {
|
let _ = hasLinks
|
||||||
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "Create an Invite Link", sectionId: self.section, editing: false, action: {
|
||||||
arguments.createLink()
|
arguments.createLink()
|
||||||
})
|
})
|
||||||
case let .inviteLink(_, link):
|
case let .inviteLink(_, link):
|
||||||
return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks) { invite in
|
return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||||
arguments.openLink(invite)
|
arguments.openLink(invite)
|
||||||
} contextAction: { invite, node, gesture in
|
}, removeAction: { invite in
|
||||||
//arguments.linkContextAction(invite, canEdit, node, gesture)
|
arguments.removeLink(invite)
|
||||||
}
|
}, contextAction: nil)
|
||||||
case .inviteLinkInfo:
|
case let .inviteLinkInfo(text):
|
||||||
//TODO:localize
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("Share access to some of this folder's groups and channels with others."), sectionId: self.section)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -654,13 +662,12 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
|||||||
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isNewFilter {
|
|
||||||
entries.append(.inviteLinkHeader)
|
|
||||||
|
|
||||||
var hasLinks = false
|
var hasLinks = false
|
||||||
if let inviteLinks, !inviteLinks.isEmpty {
|
if let inviteLinks, !inviteLinks.isEmpty {
|
||||||
hasLinks = true
|
hasLinks = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries.append(.inviteLinkHeader(hasLinks: hasLinks))
|
||||||
entries.append(.inviteLinkCreate(hasLinks: hasLinks))
|
entries.append(.inviteLinkCreate(hasLinks: hasLinks))
|
||||||
|
|
||||||
if let inviteLinks {
|
if let inviteLinks {
|
||||||
@ -671,8 +678,8 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.inviteLinkInfo)
|
//TODO:localize
|
||||||
}
|
entries.append(.inviteLinkInfo(text: hasLinks ? "Create more links to set up different access levels for different people." : "Share access to some of this folder's groups and channels with others."))
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
@ -1072,6 +1079,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var focusOnNameImpl: (() -> Void)?
|
var focusOnNameImpl: (() -> Void)?
|
||||||
|
var clearFocusImpl: (() -> Void)?
|
||||||
var applyImpl: ((@escaping () -> Void) -> Void)?
|
var applyImpl: ((@escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
||||||
@ -1270,6 +1278,9 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
clearFocus: {
|
||||||
|
clearFocusImpl?()
|
||||||
|
},
|
||||||
focusOnName: {
|
focusOnName: {
|
||||||
focusOnNameImpl?()
|
focusOnNameImpl?()
|
||||||
},
|
},
|
||||||
@ -1281,14 +1292,23 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createLink: {
|
createLink: {
|
||||||
|
if currentPreset == nil {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let text = "Please finish creating this folder to share it."
|
||||||
|
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
} else {
|
||||||
applyImpl?({
|
applyImpl?({
|
||||||
let state = stateValue.with({ $0 })
|
let state = stateValue.with({ $0 })
|
||||||
|
|
||||||
if let currentPreset, let data = currentPreset.data {
|
if let currentPreset, let data = currentPreset.data {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
var unavailableText: String?
|
var unavailableText: String?
|
||||||
if !data.categories.isEmpty || data.excludeArchived || data.excludeRead || data.excludeMuted || !data.excludePeers.isEmpty {
|
if !data.categories.isEmpty {
|
||||||
unavailableText = "You can't share a link to this folder."
|
unavailableText = "You can’t share folders with include chat types."
|
||||||
|
} else if data.excludeArchived || data.excludeRead || data.excludeMuted {
|
||||||
|
unavailableText = "You can only share folders without chat types and excluded chats."
|
||||||
|
} else if !data.excludePeers.isEmpty {
|
||||||
|
unavailableText = "You can’t share folders with excluded chats"
|
||||||
}
|
}
|
||||||
if let unavailableText {
|
if let unavailableText {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -1297,11 +1317,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var previousLink: ExportedChatFolderLink?
|
||||||
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, checkIfExists: false, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
|
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, checkIfExists: false, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
|
||||||
pushControllerImpl?(c)
|
pushControllerImpl?(c)
|
||||||
}, presentController: { c in
|
}, presentController: { c in
|
||||||
presentControllerImpl?(c, nil)
|
presentControllerImpl?(c, nil)
|
||||||
}, linkUpdated: { updatedLink in
|
}, linkUpdated: { updatedLink in
|
||||||
|
let previousLinkValue = previousLink
|
||||||
|
previousLink = updatedLink
|
||||||
|
|
||||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||||
guard var links else {
|
guard var links else {
|
||||||
return
|
return
|
||||||
@ -1313,12 +1337,17 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
} else {
|
} else {
|
||||||
links.insert(updatedLink, at: 0)
|
links.insert(updatedLink, at: 0)
|
||||||
}
|
}
|
||||||
|
} else if let previousLinkValue {
|
||||||
|
if let index = links.firstIndex(where: { $0.link == previousLinkValue.link }) {
|
||||||
|
links.remove(at: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
sharedLinks.set(.single(links))
|
sharedLinks.set(.single(links))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}, openLink: { link in
|
}, openLink: { link in
|
||||||
if let currentPreset, let _ = currentPreset.data {
|
if let currentPreset, let _ = currentPreset.data {
|
||||||
applyImpl?({
|
applyImpl?({
|
||||||
@ -1331,13 +1360,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let updatedLink {
|
if let updatedLink {
|
||||||
if let index = links.firstIndex(where: { $0 == link }) {
|
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||||
links.remove(at: index)
|
links.remove(at: index)
|
||||||
}
|
}
|
||||||
links.insert(updatedLink, at: 0)
|
links.insert(updatedLink, at: 0)
|
||||||
sharedLinks.set(.single(links))
|
sharedLinks.set(.single(links))
|
||||||
} else {
|
} else {
|
||||||
if let index = links.firstIndex(where: { $0 == link }) {
|
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||||
links.remove(at: index)
|
links.remove(at: index)
|
||||||
sharedLinks.set(.single(links))
|
sharedLinks.set(.single(links))
|
||||||
}
|
}
|
||||||
@ -1347,6 +1376,22 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
removeLink: { link in
|
||||||
|
if let currentPreset {
|
||||||
|
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||||
|
guard var links else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||||
|
links.remove(at: index)
|
||||||
|
}
|
||||||
|
sharedLinks.set(.single(links))
|
||||||
|
|
||||||
|
actionsDisposable.add(context.engine.peers.deleteChatFolderLink(filterId: currentPreset.id, link: link).start())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1462,6 +1507,12 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clearFocusImpl = { [weak controller] in
|
||||||
|
guard let controller = controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.view.endEditing(true)
|
||||||
|
}
|
||||||
controller.attemptNavigation = { _ in
|
controller.attemptNavigation = { _ in
|
||||||
return attemptNavigationImpl?() ?? true
|
return attemptNavigationImpl?() ?? true
|
||||||
}
|
}
|
||||||
@ -1493,7 +1544,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if state.isComplete {
|
if currentPreset != nil, state.isComplete {
|
||||||
displaySaveAlert()
|
displaySaveAlert()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -393,14 +393,23 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
if case let .filter(_, title, _, data) = filter, data.isShared {
|
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||||
let _ = (combineLatest(
|
let _ = (combineLatest(
|
||||||
context.engine.data.get(
|
context.engine.data.get(
|
||||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||||
|
EngineDataMap(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init(id:)))
|
||||||
),
|
),
|
||||||
context.engine.peers.getExportedChatFolderLinks(id: id),
|
context.engine.peers.getExportedChatFolderLinks(id: id),
|
||||||
context.engine.peers.requestLeaveChatFolderSuggestions(folderId: id)
|
context.engine.peers.requestLeaveChatFolderSuggestions(folderId: id)
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { peers, links, defaultSelectedPeerIds in
|
|> deliverOnMainQueue).start(next: { peerData, links, defaultSelectedPeerIds in
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let peers = peerData.0
|
||||||
|
var memberCounts: [EnginePeer.Id: Int] = [:]
|
||||||
|
for (id, count) in peerData.1 {
|
||||||
|
if let count {
|
||||||
|
memberCounts[id] = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hasLinks = false
|
var hasLinks = false
|
||||||
if let links, !links.isEmpty {
|
if let links, !links.isEmpty {
|
||||||
hasLinks = true
|
hasLinks = true
|
||||||
@ -420,7 +429,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alreadyMemberPeerIds: Set()
|
alreadyMemberPeerIds: Set(),
|
||||||
|
memberCounts: memberCounts
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pushControllerImpl?(previewScreen)
|
pushControllerImpl?(previewScreen)
|
||||||
|
|||||||
@ -215,7 +215,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
} else {
|
} else {
|
||||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
updatedSharedIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Share"), color: item.presentationData.theme.list.disclosureArrowColor)
|
updatedSharedIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/SharedFolderListIcon"), color: item.presentationData.theme.list.disclosureArrowColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerRevealOptions: [ItemListRevealOption]
|
let peerRevealOptions: [ItemListRevealOption]
|
||||||
|
|||||||
@ -69,7 +69,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
|
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
|
||||||
|
|
||||||
case peersHeader(String)
|
case peersHeader(String)
|
||||||
case peer(index: Int, peer: EnginePeer, isSelected: Bool, isEnabled: Bool)
|
case peer(index: Int, peer: EnginePeer, isSelected: Bool, disabledReasonText: String?)
|
||||||
case peersInfo(String)
|
case peersInfo(String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
@ -149,8 +149,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .peer(index, peer, isSelected, isEnabled):
|
case let .peer(index, peer, isSelected, disabledReasonText):
|
||||||
if case .peer(index, peer, isSelected, isEnabled) = rhs {
|
if case .peer(index, peer, isSelected, disabledReasonText) = rhs {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -195,7 +195,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .peersInfo(text):
|
case let .peersInfo(text):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||||
case let .peer(_, peer, isSelected, isEnabled):
|
case let .peer(_, peer, isSelected, disabledReasonText):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListPeerItem(
|
return ItemListPeerItem(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
@ -204,16 +204,16 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
context: arguments.context,
|
context: arguments.context,
|
||||||
peer: peer,
|
peer: peer,
|
||||||
presence: nil,
|
presence: nil,
|
||||||
text: .text(isEnabled ? "you can invite others here" : "you can't invite others here", .secondary),
|
text: .text(disabledReasonText ?? "you can invite others here", .secondary),
|
||||||
label: .none,
|
label: .none,
|
||||||
editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false),
|
editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||||
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: isEnabled),
|
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: disabledReasonText == nil),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
highlightable: false,
|
highlightable: false,
|
||||||
sectionId: self.section,
|
sectionId: self.section,
|
||||||
action: {
|
action: {
|
||||||
arguments.peerAction(peer, isEnabled)
|
arguments.peerAction(peer, disabledReasonText == nil)
|
||||||
},
|
},
|
||||||
setPeerIdWithRevealedOptions: { _, _ in
|
setPeerIdWithRevealedOptions: { _, _ in
|
||||||
},
|
},
|
||||||
@ -274,8 +274,19 @@ private func folderInviteLinkListControllerEntries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for peer in sortedPeers {
|
for peer in sortedPeers {
|
||||||
let isEnabled = canShareLinkToPeer(peer: peer)
|
var disabledReasonText: String?
|
||||||
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), isEnabled: isEnabled))
|
if !canShareLinkToPeer(peer: peer) {
|
||||||
|
if case let .user(user) = peer {
|
||||||
|
if user.botInfo != nil {
|
||||||
|
disabledReasonText = "you can't share chats with bots"
|
||||||
|
} else {
|
||||||
|
disabledReasonText = "you can't share private chats"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disabledReasonText = "you can't invite other here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), disabledReasonText: disabledReasonText))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let infoString {
|
if let infoString {
|
||||||
@ -391,7 +402,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
|
|
||||||
let state = stateValue.with({ $0 })
|
let state = stateValue.with({ $0 })
|
||||||
|
|
||||||
let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Link title", value: state.title ?? "", apply: { value in
|
let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Name This Link", titleFont: .bold, value: state.title ?? "", apply: { value in
|
||||||
if let value {
|
if let value {
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
@ -458,8 +469,11 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
state.selectedPeerIds.remove(peer.id)
|
state.selectedPeerIds.remove(peer.id)
|
||||||
} else {
|
} else {
|
||||||
state.selectedPeerIds.insert(peer.id)
|
state.selectedPeerIds.insert(peer.id)
|
||||||
|
|
||||||
|
if let currentInvitation, !currentInvitation.peerIds.contains(peer.id) {
|
||||||
added = true
|
added = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
@ -469,7 +483,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
|
|
||||||
dismissTooltipsImpl?()
|
dismissTooltipsImpl?()
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats."))
|
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats.", timeout: 8))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
@ -549,7 +563,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
dismissTooltipsImpl?()
|
dismissTooltipsImpl?()
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred.", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
}, completed: {
|
}, completed: {
|
||||||
linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false))
|
linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false))
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
@ -587,6 +601,8 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let previousState = Atomic<FolderInviteLinkListControllerState?>(value: nil)
|
||||||
|
|
||||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||||
let signal = combineLatest(queue: .mainQueue(),
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
presentationData,
|
presentationData,
|
||||||
@ -595,7 +611,13 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
)
|
)
|
||||||
|> map { presentationData, state, allPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, allPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
let crossfade = false
|
let crossfade = false
|
||||||
let animateChanges = false
|
|
||||||
|
var animateChanges = false
|
||||||
|
|
||||||
|
let previousStateValue = previousState.swap(state)
|
||||||
|
if let previousStateValue, previousStateValue.selectedPeerIds != state.selectedPeerIds {
|
||||||
|
animateChanges = true
|
||||||
|
}
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let title: ItemListControllerTitle
|
let title: ItemListControllerTitle
|
||||||
@ -610,7 +632,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
if state.isSaving {
|
if state.isSaving {
|
||||||
doneButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
doneButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
} else {
|
} else {
|
||||||
doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Save), style: .bold, enabled: !state.selectedPeerIds.isEmpty, action: {
|
||||||
applyChangesImpl?()
|
applyChangesImpl?()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
case requestHeader(PresentationTheme, String, String, Bool)
|
case requestHeader(PresentationTheme, String, String, Bool)
|
||||||
case request(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
case request(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
||||||
case importerHeader(PresentationTheme, String, String, Bool)
|
case importerHeader(PresentationTheme, String, String, Bool)
|
||||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool, Bool)
|
||||||
|
|
||||||
var stableId: InviteLinkViewEntryId {
|
var stableId: InviteLinkViewEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -82,7 +82,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
return .request(peer.id)
|
return .request(peer.id)
|
||||||
case .importerHeader:
|
case .importerHeader:
|
||||||
return .importerHeader
|
return .importerHeader
|
||||||
case let .importer(_, _, _, peer, _, _):
|
case let .importer(_, _, _, peer, _, _, _):
|
||||||
return .importer(peer.id)
|
return .importer(peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,8 +125,8 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsLoading):
|
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsJoinedViaFolderLink, lhsLoading):
|
||||||
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsLoading == rhsLoading {
|
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsJoinedViaFolderLink, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsJoinedViaFolderLink == rhsJoinedViaFolderLink, lhsLoading == rhsLoading {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -180,11 +180,11 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
case .importer:
|
case .importer:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .importer(lhsIndex, _, _, _, _, _):
|
case let .importer(lhsIndex, _, _, _, _, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .link, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
case .link, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
||||||
return false
|
return false
|
||||||
case let .importer(rhsIndex, _, _, _, _, _):
|
case let .importer(rhsIndex, _, _, _, _, _, _):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,7 +224,18 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
|||||||
additionalText = .none
|
additionalText = .none
|
||||||
}
|
}
|
||||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title, additionalText: additionalText)
|
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title, additionalText: additionalText)
|
||||||
case let .importer(_, _, dateTimeFormat, peer, date, loading), let .request(_, _, dateTimeFormat, peer, date, loading):
|
case let .importer(_, _, dateTimeFormat, peer, date, joinedViaFolderLink, loading):
|
||||||
|
let dateString: String
|
||||||
|
if joinedViaFolderLink {
|
||||||
|
//TODO:localize
|
||||||
|
dateString = "joined via a folder invite link"
|
||||||
|
} else {
|
||||||
|
dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||||
|
}
|
||||||
|
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||||
|
interaction.openPeer(peer.id)
|
||||||
|
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||||
|
case let .request(_, _, dateTimeFormat, peer, date, loading):
|
||||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||||
interaction.openPeer(peer.id)
|
interaction.openPeer(peer.id)
|
||||||
@ -753,14 +764,14 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
loading = true
|
loading = true
|
||||||
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
|
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
|
||||||
for i in 0 ..< count {
|
for i in 0 ..< count {
|
||||||
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, true))
|
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, false, true))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
count = min(4, Int32(state.importers.count))
|
count = min(4, Int32(state.importers.count))
|
||||||
loading = false
|
loading = false
|
||||||
for importer in state.importers {
|
for importer in state.importers {
|
||||||
if let peer = importer.peer.peer {
|
if let peer = importer.peer.peer {
|
||||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, false))
|
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, importer.joinedViaFolderLink, false))
|
||||||
}
|
}
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
|||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let tapAction: ((ExportedChatFolderLink) -> Void)?
|
let tapAction: ((ExportedChatFolderLink) -> Void)?
|
||||||
|
let removeAction: ((ExportedChatFolderLink) -> Void)?
|
||||||
let contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?
|
let contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
|||||||
sectionId: ItemListSectionId,
|
sectionId: ItemListSectionId,
|
||||||
style: ItemListStyle,
|
style: ItemListStyle,
|
||||||
tapAction: ((ExportedChatFolderLink) -> Void)?,
|
tapAction: ((ExportedChatFolderLink) -> Void)?,
|
||||||
|
removeAction: ((ExportedChatFolderLink) -> Void)?,
|
||||||
contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?,
|
contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?,
|
||||||
tag: ItemListItemTag? = nil
|
tag: ItemListItemTag? = nil
|
||||||
) {
|
) {
|
||||||
@ -57,6 +59,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
|||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
self.tapAction = tapAction
|
self.tapAction = tapAction
|
||||||
|
self.removeAction = removeAction
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
}
|
}
|
||||||
@ -125,7 +128,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListItemNode {
|
public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode, ItemListItemNode {
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
@ -506,6 +509,15 @@ public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListIte
|
|||||||
strongSelf.placeholderNode = nil
|
strongSelf.placeholderNode = nil
|
||||||
shimmerNode.removeFromSupernode()
|
shimmerNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||||
|
|
||||||
|
if item.removeAction != nil {
|
||||||
|
//TODO:localize
|
||||||
|
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Delete", icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
|
||||||
|
} else {
|
||||||
|
strongSelf.setRevealOptions((left: [], right: []))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -565,6 +577,21 @@ public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListIte
|
|||||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.containerNode.layer, offset: CGPoint(x: offset, y: 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||||
|
if let item = self.layoutParams?.0, let invite = item.invite {
|
||||||
|
item.removeAction?(invite)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setRevealOptionsOpened(false, animated: true)
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ContentParticle {
|
private struct ContentParticle {
|
||||||
|
|||||||
@ -541,7 +541,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
|
|||||||
let data = try Data(contentsOf: url)
|
let data = try Data(contentsOf: url)
|
||||||
|
|
||||||
if data.count > settings.maxSize {
|
if data.count > settings.maxSize {
|
||||||
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string))
|
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string, timeout: nil))
|
||||||
|
|
||||||
souceUrl.stopAccessingSecurityScopedResource()
|
souceUrl.stopAccessingSecurityScopedResource()
|
||||||
TempBox.shared.dispose(tempFile)
|
TempBox.shared.dispose(tempFile)
|
||||||
@ -594,7 +594,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
|
|||||||
if duration > Double(settings.maxDuration) {
|
if duration > Double(settings.maxDuration) {
|
||||||
souceUrl.stopAccessingSecurityScopedResource()
|
souceUrl.stopAccessingSecurityScopedResource()
|
||||||
|
|
||||||
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
|
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string, timeout: nil))
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("NotificationSoundSelection", "Uploading sound")
|
Logger.shared.log("NotificationSoundSelection", "Uploading sound")
|
||||||
|
|
||||||
|
|||||||
@ -150,6 +150,7 @@ private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDeleg
|
|||||||
private final class PromptAlertContentNode: AlertContentNode {
|
private final class PromptAlertContentNode: AlertContentNode {
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let text: String
|
private let text: String
|
||||||
|
private let titleFont: PromptControllerTitleFont
|
||||||
|
|
||||||
private let textNode: ASTextNode
|
private let textNode: ASTextNode
|
||||||
let inputFieldNode: PromptInputFieldNode
|
let inputFieldNode: PromptInputFieldNode
|
||||||
@ -174,9 +175,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||||||
return self.isUserInteractionEnabled
|
return self.isUserInteractionEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, value: String?) {
|
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?) {
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.titleFont = titleFont
|
||||||
|
|
||||||
self.textNode = ASTextNode()
|
self.textNode = ASTextNode()
|
||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
@ -244,7 +246,14 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
let titleFontValue: UIFont
|
||||||
|
switch self.titleFont {
|
||||||
|
case .regular:
|
||||||
|
titleFontValue = Font.regular(13.0)
|
||||||
|
case .bold:
|
||||||
|
titleFontValue = Font.semibold(17.0)
|
||||||
|
}
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.text, font: titleFontValue, textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
|
|
||||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||||
for actionNode in self.actionNodes {
|
for actionNode in self.actionNodes {
|
||||||
@ -379,7 +388,12 @@ private final class PromptAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
public enum PromptControllerTitleFont {
|
||||||
|
case regular
|
||||||
|
case bold
|
||||||
|
}
|
||||||
|
|
||||||
|
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||||
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var dismissImpl: ((Bool) -> Void)?
|
var dismissImpl: ((Bool) -> Void)?
|
||||||
@ -393,7 +407,7 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent
|
|||||||
applyImpl?()
|
applyImpl?()
|
||||||
})]
|
})]
|
||||||
|
|
||||||
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, value: value)
|
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value)
|
||||||
contentNode.complete = {
|
contentNode.complete = {
|
||||||
applyImpl?()
|
applyImpl?()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -238,6 +238,10 @@ public final class QrCodeScreen: ViewController {
|
|||||||
case let .invite(_, isGroup):
|
case let .invite(_, isGroup):
|
||||||
title = self.presentationData.strings.InviteLink_QRCode_Title
|
title = self.presentationData.strings.InviteLink_QRCode_Title
|
||||||
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
||||||
|
case .chatFolder:
|
||||||
|
//TODO:localize
|
||||||
|
title = "Invite by QR Code"
|
||||||
|
text = "Everyone on Telegram can scan this code to join your folder."
|
||||||
default:
|
default:
|
||||||
title = ""
|
title = ""
|
||||||
text = ""
|
text = ""
|
||||||
|
|||||||
@ -470,7 +470,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat
|
|||||||
let presentGlobalController = context.sharedContext.presentGlobalController
|
let presentGlobalController = context.sharedContext.presentGlobalController
|
||||||
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: false).start(completed: {
|
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: false).start(completed: {
|
||||||
Queue.mainQueue().after(0.1) {
|
Queue.mainQueue().after(0.1) {
|
||||||
presentGlobalController(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.DeleteAccount_Success), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
presentGlobalController(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.DeleteAccount_Success, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -920,7 +920,7 @@ public func privacyAndSecurityController(
|
|||||||
hapticFeedback.impact()
|
hapticFeedback.impact()
|
||||||
|
|
||||||
var alreadyPresented = false
|
var alreadyPresented = false
|
||||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||||
if action == .info {
|
if action == .info {
|
||||||
if !alreadyPresented {
|
if !alreadyPresented {
|
||||||
let controller = PremiumIntroScreen(context: context, source: .settings)
|
let controller = PremiumIntroScreen(context: context, source: .settings)
|
||||||
|
|||||||
@ -1487,7 +1487,7 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
badgeNode = current
|
badgeNode = current
|
||||||
} else {
|
} else {
|
||||||
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
|
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
|
||||||
badgeNode.alpha = self.titleNode.alpha
|
badgeNode.alpha = self.titleNode.alpha == 0.0 ? 0.0 : 1.0
|
||||||
self.badgeNode = badgeNode
|
self.badgeNode = badgeNode
|
||||||
self.addSubnode(badgeNode)
|
self.addSubnode(badgeNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,7 +118,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1091179342] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteRevoke($0) }
|
dict[1091179342] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteRevoke($0) }
|
||||||
dict[-484690728] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantInvite($0) }
|
dict[-484690728] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantInvite($0) }
|
||||||
dict[405815507] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoin($0) }
|
dict[405815507] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoin($0) }
|
||||||
dict[1557846647] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByInvite($0) }
|
dict[-23084712] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByInvite($0) }
|
||||||
dict[-1347021750] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByRequest($0) }
|
dict[-1347021750] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByRequest($0) }
|
||||||
dict[-124291086] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantLeave($0) }
|
dict[-124291086] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantLeave($0) }
|
||||||
dict[-115071790] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantMute($0) }
|
dict[-115071790] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantMute($0) }
|
||||||
|
|||||||
@ -700,7 +700,7 @@ public extension Api {
|
|||||||
case channelAdminLogEventActionExportedInviteRevoke(invite: Api.ExportedChatInvite)
|
case channelAdminLogEventActionExportedInviteRevoke(invite: Api.ExportedChatInvite)
|
||||||
case channelAdminLogEventActionParticipantInvite(participant: Api.ChannelParticipant)
|
case channelAdminLogEventActionParticipantInvite(participant: Api.ChannelParticipant)
|
||||||
case channelAdminLogEventActionParticipantJoin
|
case channelAdminLogEventActionParticipantJoin
|
||||||
case channelAdminLogEventActionParticipantJoinByInvite(invite: Api.ExportedChatInvite)
|
case channelAdminLogEventActionParticipantJoinByInvite(flags: Int32, invite: Api.ExportedChatInvite)
|
||||||
case channelAdminLogEventActionParticipantJoinByRequest(invite: Api.ExportedChatInvite, approvedBy: Int64)
|
case channelAdminLogEventActionParticipantJoinByRequest(invite: Api.ExportedChatInvite, approvedBy: Int64)
|
||||||
case channelAdminLogEventActionParticipantLeave
|
case channelAdminLogEventActionParticipantLeave
|
||||||
case channelAdminLogEventActionParticipantMute(participant: Api.GroupCallParticipant)
|
case channelAdminLogEventActionParticipantMute(participant: Api.GroupCallParticipant)
|
||||||
@ -878,10 +878,11 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
case .channelAdminLogEventActionParticipantJoinByInvite(let invite):
|
case .channelAdminLogEventActionParticipantJoinByInvite(let flags, let invite):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1557846647)
|
buffer.appendInt32(-23084712)
|
||||||
}
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
invite.serialize(buffer, true)
|
invite.serialize(buffer, true)
|
||||||
break
|
break
|
||||||
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
||||||
@ -1059,8 +1060,8 @@ public extension Api {
|
|||||||
return ("channelAdminLogEventActionParticipantInvite", [("participant", participant as Any)])
|
return ("channelAdminLogEventActionParticipantInvite", [("participant", participant as Any)])
|
||||||
case .channelAdminLogEventActionParticipantJoin:
|
case .channelAdminLogEventActionParticipantJoin:
|
||||||
return ("channelAdminLogEventActionParticipantJoin", [])
|
return ("channelAdminLogEventActionParticipantJoin", [])
|
||||||
case .channelAdminLogEventActionParticipantJoinByInvite(let invite):
|
case .channelAdminLogEventActionParticipantJoinByInvite(let flags, let invite):
|
||||||
return ("channelAdminLogEventActionParticipantJoinByInvite", [("invite", invite as Any)])
|
return ("channelAdminLogEventActionParticipantJoinByInvite", [("flags", flags as Any), ("invite", invite as Any)])
|
||||||
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
||||||
return ("channelAdminLogEventActionParticipantJoinByRequest", [("invite", invite as Any), ("approvedBy", approvedBy as Any)])
|
return ("channelAdminLogEventActionParticipantJoinByRequest", [("invite", invite as Any), ("approvedBy", approvedBy as Any)])
|
||||||
case .channelAdminLogEventActionParticipantLeave:
|
case .channelAdminLogEventActionParticipantLeave:
|
||||||
@ -1431,13 +1432,16 @@ public extension Api {
|
|||||||
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoin
|
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoin
|
||||||
}
|
}
|
||||||
public static func parse_channelAdminLogEventActionParticipantJoinByInvite(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
public static func parse_channelAdminLogEventActionParticipantJoinByInvite(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
||||||
var _1: Api.ExportedChatInvite?
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Api.ExportedChatInvite?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
_2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
||||||
}
|
}
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
if _c1 {
|
let _c2 = _2 != nil
|
||||||
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByInvite(invite: _1!)
|
if _c1 && _c2 {
|
||||||
|
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByInvite(flags: _1!, invite: _2!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1574,7 +1574,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
|||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess, timeout: nil), action: { _ in return false })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
@ -1595,7 +1595,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
|||||||
if let strongSelf = self, let (firstName, lastName) = firstAndLastName {
|
if let strongSelf = self, let (firstName, lastName) = firstAndLastName {
|
||||||
let _ = context.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).start()
|
let _ = context.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).start()
|
||||||
|
|
||||||
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false })
|
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess, timeout: nil), action: { _ in return false })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
|
|||||||
@ -73,7 +73,7 @@ public enum AdminLogEventAction {
|
|||||||
case deleteExportedInvitation(ExportedInvitation)
|
case deleteExportedInvitation(ExportedInvitation)
|
||||||
case revokeExportedInvitation(ExportedInvitation)
|
case revokeExportedInvitation(ExportedInvitation)
|
||||||
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
|
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
|
||||||
case participantJoinedViaInvite(ExportedInvitation)
|
case participantJoinedViaInvite(invitation: ExportedInvitation, joinedViaFolderLink: Bool)
|
||||||
case changeHistoryTTL(previousValue: Int32?, updatedValue: Int32?)
|
case changeHistoryTTL(previousValue: Int32?, updatedValue: Int32?)
|
||||||
case changeTheme(previous: String?, updated: String?)
|
case changeTheme(previous: String?, updated: String?)
|
||||||
case participantJoinByRequest(invitation: ExportedInvitation, approvedBy: PeerId)
|
case participantJoinByRequest(invitation: ExportedInvitation, approvedBy: PeerId)
|
||||||
@ -268,8 +268,8 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m
|
|||||||
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
|
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
|
||||||
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
|
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
|
||||||
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
|
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
|
||||||
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
|
case let .channelAdminLogEventActionParticipantJoinByInvite(flags, invite):
|
||||||
action = .participantJoinedViaInvite(ExportedInvitation(apiExportedInvite: invite))
|
action = .participantJoinedViaInvite(invitation: ExportedInvitation(apiExportedInvite: invite), joinedViaFolderLink: (flags & (1 << 0)) != 0)
|
||||||
case let .channelAdminLogEventActionParticipantVolume(participant):
|
case let .channelAdminLogEventActionParticipantVolume(participant):
|
||||||
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
||||||
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
||||||
|
|||||||
@ -389,6 +389,21 @@ extension ChatListFilter {
|
|||||||
case .allChats:
|
case .allChats:
|
||||||
return nil
|
return nil
|
||||||
case let .filter(id, title, emoticon, data):
|
case let .filter(id, title, emoticon, data):
|
||||||
|
if data.isShared {
|
||||||
|
var flags: Int32 = 0
|
||||||
|
if emoticon != nil {
|
||||||
|
flags |= 1 << 25
|
||||||
|
}
|
||||||
|
|
||||||
|
return .dialogFilterCommunity(flags: flags, id: id, title: title, emoticon: emoticon, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
||||||
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
|
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||||
|
if data.includePeers.pinnedPeers.contains(peerId) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if data.excludeMuted {
|
if data.excludeMuted {
|
||||||
flags |= 1 << 11
|
flags |= 1 << 11
|
||||||
@ -415,6 +430,7 @@ extension ChatListFilter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RequestUpdateChatListFilterError {
|
public enum RequestUpdateChatListFilterError {
|
||||||
@ -873,14 +889,21 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
|||||||
|
|
||||||
struct ChatListFiltersState: Codable, Equatable {
|
struct ChatListFiltersState: Codable, Equatable {
|
||||||
struct ChatListFilterUpdates: Codable, Equatable {
|
struct ChatListFilterUpdates: Codable, Equatable {
|
||||||
|
struct MemberCount: Codable, Equatable {
|
||||||
|
var id: PeerId
|
||||||
|
var count: Int32
|
||||||
|
}
|
||||||
|
|
||||||
var folderId: Int32
|
var folderId: Int32
|
||||||
var timestamp: Int32
|
var timestamp: Int32
|
||||||
var peerIds: [PeerId]
|
var peerIds: [PeerId]
|
||||||
|
var memberCounts: [MemberCount]
|
||||||
|
|
||||||
init(folderId: Int32, timestamp: Int32, peerIds: [PeerId]) {
|
init(folderId: Int32, timestamp: Int32, peerIds: [PeerId], memberCounts: [MemberCount]) {
|
||||||
self.folderId = folderId
|
self.folderId = folderId
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.peerIds = peerIds
|
self.peerIds = peerIds
|
||||||
|
self.memberCounts = memberCounts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
|
|||||||
var isEnabled = false
|
var isEnabled = false
|
||||||
switch peer {
|
switch peer {
|
||||||
case let .channel(channel):
|
case let .channel(channel):
|
||||||
if channel.adminRights != nil && channel.hasPermission(.inviteMembers) {
|
if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
} else if channel.username != nil {
|
} else if channel.username != nil {
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
@ -237,17 +237,20 @@ public final class ChatFolderLinkContents {
|
|||||||
public let title: String?
|
public let title: String?
|
||||||
public let peers: [EnginePeer]
|
public let peers: [EnginePeer]
|
||||||
public let alreadyMemberPeerIds: Set<EnginePeer.Id>
|
public let alreadyMemberPeerIds: Set<EnginePeer.Id>
|
||||||
|
public let memberCounts: [EnginePeer.Id: Int]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
localFilterId: Int32?,
|
localFilterId: Int32?,
|
||||||
title: String?,
|
title: String?,
|
||||||
peers: [EnginePeer],
|
peers: [EnginePeer],
|
||||||
alreadyMemberPeerIds: Set<EnginePeer.Id>
|
alreadyMemberPeerIds: Set<EnginePeer.Id>,
|
||||||
|
memberCounts: [EnginePeer.Id: Int]
|
||||||
) {
|
) {
|
||||||
self.localFilterId = localFilterId
|
self.localFilterId = localFilterId
|
||||||
self.title = title
|
self.title = title
|
||||||
self.peers = peers
|
self.peers = peers
|
||||||
self.alreadyMemberPeerIds = alreadyMemberPeerIds
|
self.alreadyMemberPeerIds = alreadyMemberPeerIds
|
||||||
|
self.memberCounts = memberCounts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +267,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
|
|
||||||
var allPeers: [Peer] = []
|
var allPeers: [Peer] = []
|
||||||
var peerPresences: [PeerId: Api.User] = [:]
|
var peerPresences: [PeerId: Api.User] = [:]
|
||||||
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
let telegramUser = TelegramUser(user: user)
|
||||||
@ -274,6 +278,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
allPeers.append(peer)
|
allPeers.append(peer)
|
||||||
}
|
}
|
||||||
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||||
|
if let participantsCount = participantsCount {
|
||||||
|
memberCounts[chat.peerId] = Int(participantsCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePeers(transaction: transaction, peers: allPeers, update: { _, updated -> Peer in
|
updatePeers(transaction: transaction, peers: allPeers, update: { _, updated -> Peer in
|
||||||
@ -294,12 +303,13 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
}
|
}
|
||||||
alreadyMemberPeerIds.removeAll()
|
alreadyMemberPeerIds.removeAll()
|
||||||
|
|
||||||
return ChatFolderLinkContents(localFilterId: nil, title: title, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
return ChatFolderLinkContents(localFilterId: nil, title: title, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
|
||||||
case let .communityInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
|
case let .communityInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
|
||||||
let _ = alreadyPeers
|
let _ = alreadyPeers
|
||||||
|
|
||||||
var allPeers: [Peer] = []
|
var allPeers: [Peer] = []
|
||||||
var peerPresences: [PeerId: Api.User] = [:]
|
var peerPresences: [PeerId: Api.User] = [:]
|
||||||
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
let telegramUser = TelegramUser(user: user)
|
||||||
@ -310,6 +320,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
allPeers.append(peer)
|
allPeers.append(peer)
|
||||||
}
|
}
|
||||||
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||||
|
if let participantsCount = participantsCount {
|
||||||
|
memberCounts[chat.peerId] = Int(participantsCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePeers(transaction: transaction, peers: allPeers, update: { _, updated -> Peer in
|
updatePeers(transaction: transaction, peers: allPeers, update: { _, updated -> Peer in
|
||||||
@ -356,7 +371,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> castError(CheckChatFolderLinkError.self)
|
|> castError(CheckChatFolderLinkError.self)
|
||||||
@ -370,12 +385,31 @@ public enum JoinChatFolderLinkError {
|
|||||||
case tooManyChannels(limit: Int32, premiumLimit: Int32)
|
case tooManyChannels(limit: Int32, premiumLimit: Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
public final class JoinChatFolderResult {
|
||||||
return account.postbox.transaction { transaction -> [Api.InputPeer] in
|
public let folderId: Int32
|
||||||
return peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
|
public let title: String
|
||||||
|
public let newChatCount: Int
|
||||||
|
|
||||||
|
public init(folderId: Int32, title: String, newChatCount: Int) {
|
||||||
|
self.folderId = folderId
|
||||||
|
self.title = title
|
||||||
|
self.newChatCount = newChatCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> {
|
||||||
|
return account.postbox.transaction { transaction -> ([Api.InputPeer], Int) in
|
||||||
|
var newChatCount = 0
|
||||||
|
for peerId in peerIds {
|
||||||
|
if transaction.getPeerChatListIndex(peerId) != nil {
|
||||||
|
newChatCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer), newChatCount)
|
||||||
}
|
}
|
||||||
|> castError(JoinChatFolderLinkError.self)
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
|
|> mapToSignal { inputPeers, newChatCount -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> in
|
||||||
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|
||||||
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
|
if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
|
||||||
@ -427,10 +461,36 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
|||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
|
|> mapToSignal { result -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> in
|
||||||
account.stateManager.addUpdates(result)
|
account.stateManager.addUpdates(result)
|
||||||
|
|
||||||
return .complete()
|
var folderResult: JoinChatFolderResult?
|
||||||
|
for update in result.allUpdates {
|
||||||
|
if case let .updateDialogFilter(_, id, data) = update {
|
||||||
|
if let data = data, case let .filter(_, title, _, _) = ChatListFilter(apiFilter: data) {
|
||||||
|
folderResult = JoinChatFolderResult(folderId: id, title: title, newChatCount: newChatCount)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let folderResult = folderResult {
|
||||||
|
return _internal_updatedChatListFilters(postbox: account.postbox)
|
||||||
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|
|> filter { filters -> Bool in
|
||||||
|
if filters.contains(where: { $0.id == folderResult.folderId }) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|> map { _ -> JoinChatFolderResult in
|
||||||
|
return folderResult
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,23 +499,26 @@ public final class ChatFolderUpdates: Equatable {
|
|||||||
fileprivate let folderId: Int32
|
fileprivate let folderId: Int32
|
||||||
fileprivate let title: String
|
fileprivate let title: String
|
||||||
fileprivate let missingPeers: [EnginePeer]
|
fileprivate let missingPeers: [EnginePeer]
|
||||||
|
fileprivate let memberCounts: [EnginePeer.Id: Int]
|
||||||
|
|
||||||
public var availableChatsToJoin: Int {
|
public var availableChatsToJoin: Int {
|
||||||
return self.missingPeers.count
|
return self.missingPeers.count
|
||||||
}
|
}
|
||||||
|
|
||||||
public var chatFolderLinkContents: ChatFolderLinkContents {
|
public var chatFolderLinkContents: ChatFolderLinkContents {
|
||||||
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set())
|
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set(), memberCounts: self.memberCounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
folderId: Int32,
|
folderId: Int32,
|
||||||
title: String,
|
title: String,
|
||||||
missingPeers: [EnginePeer]
|
missingPeers: [EnginePeer],
|
||||||
|
memberCounts: [EnginePeer.Id: Int]
|
||||||
) {
|
) {
|
||||||
self.folderId = folderId
|
self.folderId = folderId
|
||||||
self.title = title
|
self.title = title
|
||||||
self.missingPeers = missingPeers
|
self.missingPeers = missingPeers
|
||||||
|
self.memberCounts = memberCounts
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
|
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
|
||||||
@ -513,7 +576,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
|||||||
var state = state
|
var state = state
|
||||||
|
|
||||||
state.updates.removeAll(where: { $0.folderId == folderId })
|
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||||
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: []))
|
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: [], memberCounts: []))
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
@ -525,6 +588,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
|||||||
return account.postbox.transaction { transaction -> Void in
|
return account.postbox.transaction { transaction -> Void in
|
||||||
var peers: [Peer] = []
|
var peers: [Peer] = []
|
||||||
var peerPresences: [PeerId: Api.User] = [:]
|
var peerPresences: [PeerId: Api.User] = [:]
|
||||||
|
var memberCounts: [ChatListFiltersState.ChatListFilterUpdates.MemberCount] = []
|
||||||
|
|
||||||
for user in users {
|
for user in users {
|
||||||
let telegramUser = TelegramUser(user: user)
|
let telegramUser = TelegramUser(user: user)
|
||||||
@ -535,6 +599,11 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
|||||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
peers.append(peer)
|
peers.append(peer)
|
||||||
}
|
}
|
||||||
|
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||||
|
if let participantsCount = participantsCount {
|
||||||
|
memberCounts.append(ChatListFiltersState.ChatListFilterUpdates.MemberCount(id: chat.peerId, count: participantsCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||||
@ -546,7 +615,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
|||||||
var state = state
|
var state = state
|
||||||
|
|
||||||
state.updates.removeAll(where: { $0.folderId == folderId })
|
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||||
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId)))
|
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId), memberCounts: memberCounts))
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
@ -561,6 +630,7 @@ func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) ->
|
|||||||
struct InternalData: Equatable {
|
struct InternalData: Equatable {
|
||||||
var title: String
|
var title: String
|
||||||
var peerIds: [EnginePeer.Id]
|
var peerIds: [EnginePeer.Id]
|
||||||
|
var memberCounts: [EnginePeer.Id: Int]
|
||||||
}
|
}
|
||||||
|
|
||||||
return _internal_updatedChatListFiltersState(postbox: account.postbox)
|
return _internal_updatedChatListFiltersState(postbox: account.postbox)
|
||||||
@ -575,7 +645,11 @@ func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) ->
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let filteredPeerIds: [PeerId] = update.peerIds.filter { !data.includePeers.peers.contains($0) }
|
let filteredPeerIds: [PeerId] = update.peerIds.filter { !data.includePeers.peers.contains($0) }
|
||||||
return InternalData(title: title, peerIds: filteredPeerIds)
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
for item in update.memberCounts {
|
||||||
|
memberCounts[item.id] = Int(item.count)
|
||||||
|
}
|
||||||
|
return InternalData(title: title, peerIds: filteredPeerIds, memberCounts: memberCounts)
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { internalData -> Signal<ChatFolderUpdates?, NoError> in
|
|> mapToSignal { internalData -> Signal<ChatFolderUpdates?, NoError> in
|
||||||
@ -592,7 +666,7 @@ func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) ->
|
|||||||
peers.append(EnginePeer(peer))
|
peers.append(EnginePeer(peer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers)
|
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers, memberCounts: internalData.memberCounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -664,6 +664,7 @@ public struct PeerInvitationImportersState: Equatable {
|
|||||||
public var date: Int32
|
public var date: Int32
|
||||||
public var about: String?
|
public var about: String?
|
||||||
public var approvedBy: PeerId?
|
public var approvedBy: PeerId?
|
||||||
|
public var joinedViaFolderLink: Bool
|
||||||
}
|
}
|
||||||
public var importers: [Importer]
|
public var importers: [Importer]
|
||||||
public var isLoadingMore: Bool
|
public var isLoadingMore: Bool
|
||||||
@ -708,6 +709,7 @@ final class CachedPeerInvitationImporters: Codable {
|
|||||||
let peerIds: [PeerId]
|
let peerIds: [PeerId]
|
||||||
let dates: [PeerId: Int32]
|
let dates: [PeerId: Int32]
|
||||||
let abouts: [PeerId: String]
|
let abouts: [PeerId: String]
|
||||||
|
let joinedViaFolderLink: [PeerId: Bool]
|
||||||
let count: Int32
|
let count: Int32
|
||||||
|
|
||||||
static func key(peerId: PeerId, link: String, requested: Bool) -> ValueBoxKey {
|
static func key(peerId: PeerId, link: String, requested: Bool) -> ValueBoxKey {
|
||||||
@ -727,13 +729,17 @@ final class CachedPeerInvitationImporters: Codable {
|
|||||||
$0[$1.peer.peerId] = about
|
$0[$1.peer.peerId] = about
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.joinedViaFolderLink = importers.reduce(into: [PeerId: Bool]()) {
|
||||||
|
$0[$1.peer.peerId] = $1.joinedViaFolderLink
|
||||||
|
}
|
||||||
self.count = count
|
self.count = count
|
||||||
}
|
}
|
||||||
|
|
||||||
init(peerIds: [PeerId], dates: [PeerId: Int32], abouts: [PeerId: String], count: Int32) {
|
init(peerIds: [PeerId], dates: [PeerId: Int32], abouts: [PeerId: String], joinedViaFolderLink: [PeerId: Bool], count: Int32) {
|
||||||
self.peerIds = peerIds
|
self.peerIds = peerIds
|
||||||
self.dates = dates
|
self.dates = dates
|
||||||
self.abouts = abouts
|
self.abouts = abouts
|
||||||
|
self.joinedViaFolderLink = joinedViaFolderLink
|
||||||
self.count = count
|
self.count = count
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,6 +758,16 @@ final class CachedPeerInvitationImporters: Codable {
|
|||||||
}
|
}
|
||||||
self.dates = dates
|
self.dates = dates
|
||||||
|
|
||||||
|
var joinedViaFolderLink: [PeerId: Bool] = [:]
|
||||||
|
let joinedViaFolderLinkArray = try container.decode([Int64].self, forKey: "joinedViaFolderLink")
|
||||||
|
for index in stride(from: 0, to: joinedViaFolderLinkArray.endIndex, by: 2) {
|
||||||
|
let userId = datesArray[index]
|
||||||
|
let value = datesArray[index + 1]
|
||||||
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||||
|
joinedViaFolderLink[peerId] = value != 0
|
||||||
|
}
|
||||||
|
self.joinedViaFolderLink = joinedViaFolderLink
|
||||||
|
|
||||||
var abouts: [PeerId: String] = [:]
|
var abouts: [PeerId: String] = [:]
|
||||||
let aboutsArray = try container.decodeIfPresent([DictionaryPair].self, forKey: "abouts")
|
let aboutsArray = try container.decodeIfPresent([DictionaryPair].self, forKey: "abouts")
|
||||||
if let aboutsArray = aboutsArray {
|
if let aboutsArray = aboutsArray {
|
||||||
@ -777,6 +793,13 @@ final class CachedPeerInvitationImporters: Codable {
|
|||||||
}
|
}
|
||||||
try container.encode(dates, forKey: "dates")
|
try container.encode(dates, forKey: "dates")
|
||||||
|
|
||||||
|
var joinedViaFolderLink: [Int64] = []
|
||||||
|
for (peerId, value) in self.joinedViaFolderLink {
|
||||||
|
joinedViaFolderLink.append(peerId.id._internalGetInt64Value())
|
||||||
|
joinedViaFolderLink.append(Int64(value ? 1 : 0))
|
||||||
|
}
|
||||||
|
try container.encode(joinedViaFolderLink, forKey: "joinedViaFolderLink")
|
||||||
|
|
||||||
var abouts: [DictionaryPair] = []
|
var abouts: [DictionaryPair] = []
|
||||||
for (peerId, about) in self.abouts {
|
for (peerId, about) in self.abouts {
|
||||||
abouts.append(DictionaryPair(peerId.id._internalGetInt64Value(), value: about))
|
abouts.append(DictionaryPair(peerId.id._internalGetInt64Value(), value: about))
|
||||||
@ -847,7 +870,7 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
var result: [PeerInvitationImportersState.Importer] = []
|
var result: [PeerInvitationImportersState.Importer] = []
|
||||||
for peerId in cachedResult.peerIds {
|
for peerId in cachedResult.peerIds {
|
||||||
if let peer = transaction.getPeer(peerId), let date = cachedResult.dates[peerId] {
|
if let peer = transaction.getPeer(peerId), let date = cachedResult.dates[peerId] {
|
||||||
result.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: cachedResult.abouts[peerId]))
|
result.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: cachedResult.abouts[peerId], joinedViaFolderLink: cachedResult.joinedViaFolderLink[peerId] ?? false))
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -958,15 +981,17 @@ private final class PeerInvitationImportersContextImpl {
|
|||||||
let date: Int32
|
let date: Int32
|
||||||
let about: String?
|
let about: String?
|
||||||
let approvedBy: PeerId?
|
let approvedBy: PeerId?
|
||||||
|
let joinedViaFolderLink: Bool
|
||||||
switch importer {
|
switch importer {
|
||||||
case let .chatInviteImporter(_, userId, dateValue, aboutValue, approvedByValue):
|
case let .chatInviteImporter(flags, userId, dateValue, aboutValue, approvedByValue):
|
||||||
|
joinedViaFolderLink = (flags & (1 << 3)) != 0
|
||||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||||
date = dateValue
|
date = dateValue
|
||||||
about = aboutValue
|
about = aboutValue
|
||||||
approvedBy = approvedByValue.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }
|
approvedBy = approvedByValue.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }
|
||||||
}
|
}
|
||||||
if let peer = transaction.getPeer(peerId) {
|
if let peer = transaction.getPeer(peerId) {
|
||||||
resultImporters.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: about, approvedBy: approvedBy))
|
resultImporters.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: about, approvedBy: approvedBy, joinedViaFolderLink: joinedViaFolderLink))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if populateCache && query == nil {
|
if populateCache && query == nil {
|
||||||
|
|||||||
@ -1054,7 +1054,7 @@ public extension TelegramEngine {
|
|||||||
return _internal_checkChatFolderLink(account: self.account, slug: slug)
|
return _internal_checkChatFolderLink(account: self.account, slug: slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func joinChatFolderLink(slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
public func joinChatFolderLink(slug: String, peerIds: [EnginePeer.Id]) -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> {
|
||||||
return _internal_joinChatFolderLink(account: self.account, slug: slug, peerIds: peerIds)
|
return _internal_joinChatFolderLink(account: self.account, slug: slug, peerIds: peerIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,12 +55,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
var containerInset: CGFloat
|
var containerInset: CGFloat
|
||||||
var bottomInset: CGFloat
|
var bottomInset: CGFloat
|
||||||
var topInset: CGFloat
|
var topInset: CGFloat
|
||||||
|
var contentHeight: CGFloat
|
||||||
|
|
||||||
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
|
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat, contentHeight: CGFloat) {
|
||||||
self.containerSize = containerSize
|
self.containerSize = containerSize
|
||||||
self.containerInset = containerInset
|
self.containerInset = containerInset
|
||||||
self.bottomInset = bottomInset
|
self.bottomInset = bottomInset
|
||||||
self.topInset = topInset
|
self.topInset = topInset
|
||||||
|
self.contentHeight = contentHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +84,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
private let scrollView: ScrollView
|
private let scrollView: ScrollView
|
||||||
private let scrollContentClippingView: SparseContainerView
|
private let scrollContentClippingView: SparseContainerView
|
||||||
private let scrollContentView: UIView
|
private let scrollContentView: UIView
|
||||||
|
private let bottomBackgroundLayer: SimpleLayer
|
||||||
|
private let bottomSeparatorLayer: SimpleLayer
|
||||||
|
|
||||||
private let topIcon = ComponentView<Empty>()
|
private let topIcon = ComponentView<Empty>()
|
||||||
|
|
||||||
@ -134,6 +138,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
self.itemContainerView.clipsToBounds = true
|
self.itemContainerView.clipsToBounds = true
|
||||||
self.itemContainerView.layer.cornerRadius = 10.0
|
self.itemContainerView.layer.cornerRadius = 10.0
|
||||||
|
|
||||||
|
self.bottomBackgroundLayer = SimpleLayer()
|
||||||
|
self.bottomSeparatorLayer = SimpleLayer()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.dimView)
|
self.addSubview(self.dimView)
|
||||||
@ -165,6 +172,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
|
|
||||||
self.scrollContentView.addSubview(self.itemContainerView)
|
self.scrollContentView.addSubview(self.itemContainerView)
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.bottomBackgroundLayer)
|
||||||
|
self.layer.addSublayer(self.bottomSeparatorLayer)
|
||||||
|
|
||||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +241,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
topOffset = max(0.0, topOffset)
|
topOffset = max(0.0, topOffset)
|
||||||
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||||
|
|
||||||
|
let bottomDistance = itemLayout.contentHeight - self.scrollView.bounds.maxY
|
||||||
|
let bottomAlphaDistance: CGFloat = 30.0
|
||||||
|
var bottomAlpha: CGFloat = bottomDistance / bottomAlphaDistance
|
||||||
|
bottomAlpha = max(0.0, min(1.0, bottomAlpha))
|
||||||
|
|
||||||
|
let bottomOverlayAlpha: CGFloat = bottomAlpha
|
||||||
|
transition.setAlpha(layer: self.bottomBackgroundLayer, alpha: bottomOverlayAlpha)
|
||||||
|
transition.setAlpha(layer: self.bottomSeparatorLayer, alpha: bottomOverlayAlpha)
|
||||||
|
|
||||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||||
|
|
||||||
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
||||||
@ -244,10 +263,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
let animateOffset: CGFloat = self.backgroundLayer.frame.minY
|
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
self.bottomBackgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
self.bottomSeparatorLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
if let actionButtonView = self.actionButton.view {
|
if let actionButtonView = self.actionButton.view {
|
||||||
actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
}
|
}
|
||||||
@ -265,6 +286,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
completion()
|
completion()
|
||||||
})
|
})
|
||||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||||
|
self.bottomBackgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||||
|
self.bottomSeparatorLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||||
if let actionButtonView = self.actionButton.view {
|
if let actionButtonView = self.actionButton.view {
|
||||||
actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||||
@ -308,6 +331,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
|
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
|
||||||
self.itemContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor
|
self.itemContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor
|
||||||
|
self.bottomBackgroundLayer.backgroundColor = environment.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor
|
||||||
|
self.bottomSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
@ -485,6 +510,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
self.items[id] = item
|
self.items[id] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subtitle: String?
|
||||||
|
if linkContents.alreadyMemberPeerIds.contains(peer.id) {
|
||||||
|
subtitle = "You are already a member"
|
||||||
|
} else if let memberCount = linkContents.memberCounts[peer.id] {
|
||||||
|
subtitle = "\(memberCount) participants"
|
||||||
|
}
|
||||||
|
|
||||||
let itemSize = item.update(
|
let itemSize = item.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
component: AnyComponent(PeerListItemComponent(
|
component: AnyComponent(PeerListItemComponent(
|
||||||
@ -494,7 +526,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
sideInset: 0.0,
|
sideInset: 0.0,
|
||||||
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||||
peer: peer,
|
peer: peer,
|
||||||
subtitle: nil,
|
subtitle: subtitle,
|
||||||
selectionState: .editing(isSelected: self.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)),
|
selectionState: .editing(isSelected: self.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)),
|
||||||
hasNext: i != linkContents.peers.count - 1,
|
hasNext: i != linkContents.peers.count - 1,
|
||||||
action: { [weak self] peer in
|
action: { [weak self] peer in
|
||||||
@ -647,7 +679,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
self.selectedItems.insert(peerId)
|
self.selectedItems.insert(peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .easeInOut)))
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -737,21 +769,72 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
} else if let _ = component.linkContents {
|
} else if let _ = component.linkContents {
|
||||||
if self.joinDisposable == nil, !self.selectedItems.isEmpty {
|
if self.joinDisposable == nil, !self.selectedItems.isEmpty {
|
||||||
let joinSignal: Signal<Never, JoinChatFolderLinkError>
|
let joinSignal: Signal<JoinChatFolderResult?, JoinChatFolderLinkError>
|
||||||
switch component.subject {
|
switch component.subject {
|
||||||
case .remove:
|
case .remove:
|
||||||
return
|
return
|
||||||
case let .slug(slug):
|
case let .slug(slug):
|
||||||
joinSignal = component.context.engine.peers.joinChatFolderLink(slug: slug, peerIds: Array(self.selectedItems))
|
joinSignal = component.context.engine.peers.joinChatFolderLink(slug: slug, peerIds: Array(self.selectedItems))
|
||||||
|
|> map(Optional.init)
|
||||||
case let .updates(updates):
|
case let .updates(updates):
|
||||||
|
var result: JoinChatFolderResult?
|
||||||
|
if let localFilterId = updates.chatFolderLinkContents.localFilterId, let title = updates.chatFolderLinkContents.title {
|
||||||
|
result = JoinChatFolderResult(folderId: localFilterId, title: title, newChatCount: self.selectedItems.count)
|
||||||
|
}
|
||||||
joinSignal = component.context.engine.peers.joinAvailableChatsInFolder(updates: updates, peerIds: Array(self.selectedItems))
|
joinSignal = component.context.engine.peers.joinAvailableChatsInFolder(updates: updates, peerIds: Array(self.selectedItems))
|
||||||
|
|> map { _ -> JoinChatFolderResult? in
|
||||||
|
}
|
||||||
|
|> then(Signal<JoinChatFolderResult?, JoinChatFolderLinkError>.single(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inProgress = true
|
self.inProgress = true
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
self.joinDisposable = (joinSignal
|
self.joinDisposable = (joinSignal
|
||||||
|> deliverOnMainQueue).start(error: { [weak self] error in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let result, let navigationController = controller.navigationController as? NavigationController {
|
||||||
|
var chatListController: ChatListController?
|
||||||
|
for viewController in navigationController.viewControllers {
|
||||||
|
if let rootController = viewController as? TabBarController {
|
||||||
|
for c in rootController.controllers {
|
||||||
|
if let c = c as? ChatListController {
|
||||||
|
chatListController = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let c = viewController as? ChatListController {
|
||||||
|
chatListController = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let chatListController {
|
||||||
|
navigationController.popToRoot(animated: true)
|
||||||
|
let context = component.context
|
||||||
|
chatListController.navigateToFolder(folderId: result.folderId, completion: { [weak context, weak chatListController] in
|
||||||
|
guard let context, let chatListController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
if case .updates = component.subject {
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: "Folder \(result.title) Updated", text: "You have joined \(result.newChatCount) new chats", timeout: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
|
} else if result.newChatCount != 0 {
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: "Folder \(result.title) Added", text: "You also joined \(result.newChatCount) chats", timeout: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
|
} else {
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Folder \(result.title) Added", timeout: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.dismiss()
|
||||||
|
}, error: { [weak self] error in
|
||||||
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -772,11 +855,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
controller.push(limitController)
|
controller.push(limitController)
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
}, completed: { [weak self] in
|
|
||||||
guard let self, let controller = self.environment?.controller() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
controller.dismiss()
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
@ -796,6 +874,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.bottomBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0), size: CGSize(width: availableSize.width, height: bottomPanelHeight)))
|
||||||
|
transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
if let controller = environment.controller() {
|
if let controller = environment.controller() {
|
||||||
let subLayout = ContainerViewLayout(
|
let subLayout = ContainerViewLayout(
|
||||||
size: availableSize, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset),
|
size: availableSize, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset),
|
||||||
@ -817,16 +898,16 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
|
|
||||||
let scrollContentHeight = max(topInset + contentHeight, availableSize.height - containerInset)
|
let scrollContentHeight = max(topInset + contentHeight, availableSize.height - containerInset)
|
||||||
|
|
||||||
self.scrollContentClippingView.layer.cornerRadius = 10.0
|
//self.scrollContentClippingView.layer.cornerRadius = 10.0
|
||||||
|
|
||||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset)
|
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset, contentHeight: scrollContentHeight)
|
||||||
|
|
||||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||||
|
|
||||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
|
||||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: actionButtonFrame.minY - 24.0 - (containerInset + 56.0)))
|
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: actionButtonFrame.minY - 8.0 - (containerInset + 56.0)))
|
||||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||||
|
|
||||||
@ -876,6 +957,7 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
|
|||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
self.automaticallyControlPresentationContextLayout = false
|
self.automaticallyControlPresentationContextLayout = false
|
||||||
|
self.lockOrientation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
|||||||
@ -182,7 +182,7 @@ final class PeerListItemComponent: Component {
|
|||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
var theme = CheckNodeTheme(theme: component.theme, style: .plain)
|
var theme = CheckNodeTheme(theme: component.theme, style: .plain)
|
||||||
if isTinted {
|
if isTinted {
|
||||||
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.35)
|
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5)
|
||||||
}
|
}
|
||||||
checkLayer.theme = theme
|
checkLayer.theme = theme
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ final class PeerListItemComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
var theme = CheckNodeTheme(theme: component.theme, style: .plain)
|
var theme = CheckNodeTheme(theme: component.theme, style: .plain)
|
||||||
if isTinted {
|
if isTinted {
|
||||||
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.35)
|
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5)
|
||||||
}
|
}
|
||||||
checkLayer = CheckLayer(theme: theme)
|
checkLayer = CheckLayer(theme: theme)
|
||||||
self.checkLayer = checkLayer
|
self.checkLayer = checkLayer
|
||||||
|
|||||||
12
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "link_18.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
101
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/link_18.pdf
vendored
Normal file
101
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/link_18.pdf
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
%PDF-1.7
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<< >>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
<< /Length 3 0 R >>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 2.304932 1.549805 cm
|
||||||
|
0.000000 0.000000 0.000000 scn
|
||||||
|
7.766377 12.077935 m
|
||||||
|
8.749319 13.060877 10.342982 13.060877 11.325925 12.077935 c
|
||||||
|
12.308867 11.094994 12.308867 9.501329 11.325925 8.518387 c
|
||||||
|
9.825925 7.018387 l
|
||||||
|
8.842983 6.035446 7.249319 6.035446 6.266377 7.018387 c
|
||||||
|
6.135469 7.149295 6.022435 7.290437 5.926912 7.438976 c
|
||||||
|
5.728258 7.747883 5.316799 7.837261 5.007892 7.638608 c
|
||||||
|
4.698985 7.439953 4.609607 7.028494 4.808261 6.719588 c
|
||||||
|
4.954801 6.491719 5.127476 6.276385 5.325925 6.077936 c
|
||||||
|
6.828264 4.575597 9.264038 4.575597 10.766377 6.077936 c
|
||||||
|
12.266377 7.577936 l
|
||||||
|
13.768716 9.080275 13.768716 11.516048 12.266377 13.018387 c
|
||||||
|
10.764037 14.520726 8.328264 14.520726 6.825925 13.018387 c
|
||||||
|
5.325925 11.518387 l
|
||||||
|
5.066226 11.258689 5.066226 10.837634 5.325925 10.577935 c
|
||||||
|
5.585624 10.318236 6.006679 10.318236 6.266377 10.577935 c
|
||||||
|
7.766377 12.077935 l
|
||||||
|
h
|
||||||
|
5.626755 2.818445 m
|
||||||
|
4.643812 1.835504 3.050149 1.835504 2.067207 2.818445 c
|
||||||
|
1.084265 3.801387 1.084265 5.395051 2.067207 6.377992 c
|
||||||
|
3.567207 7.877992 l
|
||||||
|
4.550149 8.860934 6.143812 8.860934 7.126754 7.877992 c
|
||||||
|
7.257662 7.747084 7.370696 7.605942 7.466219 7.457404 c
|
||||||
|
7.664873 7.148497 8.076332 7.059119 8.385240 7.257772 c
|
||||||
|
8.694146 7.456426 8.783525 7.867885 8.584870 8.176792 c
|
||||||
|
8.438332 8.404661 8.265656 8.619995 8.067206 8.818444 c
|
||||||
|
6.564867 10.320784 4.129094 10.320784 2.626755 8.818444 c
|
||||||
|
1.126755 7.318444 l
|
||||||
|
-0.375585 5.816106 -0.375585 3.380332 1.126755 1.877994 c
|
||||||
|
2.629094 0.375654 5.064867 0.375654 6.567206 1.877994 c
|
||||||
|
8.067206 3.377994 l
|
||||||
|
8.326905 3.637691 8.326905 4.058746 8.067206 4.318445 c
|
||||||
|
7.807508 4.578144 7.386453 4.578144 7.126754 4.318445 c
|
||||||
|
5.626755 2.818445 l
|
||||||
|
h
|
||||||
|
f*
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
1707
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
<< /Annots []
|
||||||
|
/Type /Page
|
||||||
|
/MediaBox [ 0.000000 0.000000 18.000000 18.000000 ]
|
||||||
|
/Resources 1 0 R
|
||||||
|
/Contents 2 0 R
|
||||||
|
/Parent 5 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<< /Kids [ 4 0 R ]
|
||||||
|
/Count 1
|
||||||
|
/Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
6 0 obj
|
||||||
|
<< /Pages 5 0 R
|
||||||
|
/Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 7
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000010 00000 n
|
||||||
|
0000000034 00000 n
|
||||||
|
0000001797 00000 n
|
||||||
|
0000001820 00000 n
|
||||||
|
0000001993 00000 n
|
||||||
|
0000002067 00000 n
|
||||||
|
trailer
|
||||||
|
<< /ID [ (some) (id) ]
|
||||||
|
/Root 6 0 R
|
||||||
|
/Size 7
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
2126
|
||||||
|
%%EOF
|
||||||
@ -7292,7 +7292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let strongSelf = self, case let .message(index) = toIndex {
|
if let strongSelf = self, case let .message(index) = toIndex {
|
||||||
if case let .message(messageSubject, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id {
|
if case let .message(messageSubject, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id {
|
||||||
if messageId.peerId == index.id.peerId {
|
if messageId.peerId == index.id.peerId {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist), elevatedLayout: false, action: { _ in return true }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||||
}
|
}
|
||||||
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
||||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
||||||
@ -8771,12 +8771,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
bannedMediaInput = true
|
bannedMediaInput = true
|
||||||
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||||
if !isVideo {
|
if !isVideo {
|
||||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||||
if isVideo {
|
if isVideo {
|
||||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8785,12 +8785,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
bannedMediaInput = true
|
bannedMediaInput = true
|
||||||
} else if group.hasBannedPermission(.banSendVoice) {
|
} else if group.hasBannedPermission(.banSendVoice) {
|
||||||
if !isVideo {
|
if !isVideo {
|
||||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||||
if isVideo {
|
if isVideo {
|
||||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10092,7 +10092,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
presentAddMembersImpl(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: peer, selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable)
|
presentAddMembersImpl(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: peer, selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable)
|
||||||
}, presentGigagroupHelp: { [weak self] in
|
}, presentGigagroupHelp: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription), elevatedLayout: false, action: { _ in return true }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||||
}
|
}
|
||||||
}, editMessageMedia: { [weak self] messageId, draw in
|
}, editMessageMedia: { [weak self] messageId, draw in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -11166,7 +11166,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||||
|
|
||||||
let controller = richTextAlertController(context: strongSelf.context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
let controller = richTextAlertController(context: strongSelf.context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_SettingsTip), elevatedLayout: false, action: { _ in return false }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_SettingsTip, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_LearnMore, action: {
|
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_LearnMore, action: {
|
||||||
|
|
||||||
let context = strongSelf.context
|
let context = strongSelf.context
|
||||||
@ -12959,7 +12959,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}), case let .app(_, botName, _) = button {
|
}), case let .app(_, botName, _) = button {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: botJustInstalled ? strongSelf.presentationData.strings.WebApp_AddToAttachmentSucceeded(botName).string : strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError), elevatedLayout: false, action: { _ in return false }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: botJustInstalled ? strongSelf.presentationData.strings.WebApp_AddToAttachmentSucceeded(botName).string : strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
} else {
|
} else {
|
||||||
let _ = (context.engine.messages.getAttachMenuBot(botId: botId)
|
let _ = (context.engine.messages.getAttachMenuBot(botId: botId)
|
||||||
|> deliverOnMainQueue).start(next: { bot in
|
|> deliverOnMainQueue).start(next: { bot in
|
||||||
|
|||||||
@ -815,7 +815,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
let _ = context.engine.messages.rateAudioTranscription(messageId: message.id, id: audioTranscription.id, isGood: value).start()
|
let _ = context.engine.messages.rateAudioTranscription(messageId: message.id, id: audioTranscription.id, isGood: value).start()
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let content: UndoOverlayContent = .info(title: nil, text: presentationData.strings.Chat_AudioTranscriptionFeedbackTip)
|
let content: UndoOverlayContent = .info(title: nil, text: presentationData.strings.Chat_AudioTranscriptionFeedbackTip, timeout: nil)
|
||||||
controllerInteraction.displayUndo(content)
|
controllerInteraction.displayUndo(content)
|
||||||
}), false), at: 0)
|
}), false), at: 0)
|
||||||
actions.insert(.separator, at: 1)
|
actions.insert(.separator, at: 1)
|
||||||
@ -842,9 +842,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
|
|
||||||
let settings = NotificationSoundSettings.extract(from: context.currentAppConfiguration.with({ $0 }))
|
let settings = NotificationSoundSettings.extract(from: context.currentAppConfiguration.with({ $0 }))
|
||||||
if size > settings.maxSize {
|
if size > settings.maxSize {
|
||||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string))
|
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string, timeout: nil))
|
||||||
} else if Double(duration) > Double(settings.maxDuration) {
|
} else if Double(duration) > Double(settings.maxDuration) {
|
||||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
|
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string, timeout: nil))
|
||||||
} else {
|
} else {
|
||||||
let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|
let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
|||||||
@ -164,7 +164,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: new, stickerPacks: [new], parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|
strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: new, stickerPacks: [new], parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .editExportedInvitation(_, invite), let .revokeExportedInvitation(invite), let .deleteExportedInvitation(invite), let .participantJoinedViaInvite(invite), let .participantJoinByRequest(invite, _):
|
case let .editExportedInvitation(_, invite), let .revokeExportedInvitation(invite), let .deleteExportedInvitation(invite), let .participantJoinedViaInvite(invite, _), let .participantJoinByRequest(invite, _):
|
||||||
if let inviteLink = invite.link, !inviteLink.hasSuffix("...") {
|
if let inviteLink = invite.link, !inviteLink.hasSuffix("...") {
|
||||||
if invite.isPermanent {
|
if invite.isPermanent {
|
||||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||||
|
|||||||
@ -1418,7 +1418,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||||
case let .participantJoinedViaInvite(invite):
|
case let .participantJoinedViaInvite(invite, joinedViaFolderLink):
|
||||||
var peers = SimpleDictionary<PeerId, Peer>()
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
var author: Peer?
|
var author: Peer?
|
||||||
if let peer = self.entry.peers[self.entry.event.peerId] {
|
if let peer = self.entry.peers[self.entry.event.peerId] {
|
||||||
@ -1429,7 +1429,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
var text: String = ""
|
var text: String = ""
|
||||||
var entities: [MessageTextEntity] = []
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
let rawText: PresentationStrings.FormattedString = self.presentationData.strings.Channel_AdminLog_JoinedViaInviteLink(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link?.replacingOccurrences(of: "https://", with: "") ?? "")
|
let rawText: PresentationStrings.FormattedString
|
||||||
|
if joinedViaFolderLink {
|
||||||
|
rawText = self.presentationData.strings.Channel_AdminLog_JoinedViaFolderInviteLink(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link?.replacingOccurrences(of: "https://", with: "") ?? "")
|
||||||
|
} else {
|
||||||
|
rawText = self.presentationData.strings.Channel_AdminLog_JoinedViaInviteLink(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link?.replacingOccurrences(of: "https://", with: "") ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
appendAttributedText(text: rawText, generateEntities: { index in
|
appendAttributedText(text: rawText, generateEntities: { index in
|
||||||
if index == 0, let author = author {
|
if index == 0, let author = author {
|
||||||
|
|||||||
@ -593,7 +593,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}
|
}
|
||||||
case let .startAttach(peerId, payload, choose):
|
case let .startAttach(peerId, payload, choose):
|
||||||
let presentError: (String) -> Void = { errorText in
|
let presentError: (String) -> Void = { errorText in
|
||||||
present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: errorText), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
|
present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: errorText, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
|
||||||
return true
|
return true
|
||||||
}), nil)
|
}), nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6080,7 +6080,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
if let strongSelf = self, let peer = strongSelf.data?.peer {
|
if let strongSelf = self, let peer = strongSelf.data?.peer {
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })
|
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })
|
||||||
controller.keepOnParentDismissal = true
|
controller.keepOnParentDismissal = true
|
||||||
strongSelf.controller?.present(controller, in: .window(.root))
|
strongSelf.controller?.present(controller, in: .window(.root))
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@ swift_library(
|
|||||||
"//submodules/TelegramCore:TelegramCore",
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
"//submodules/Postbox:Postbox",
|
"//submodules/Postbox:Postbox",
|
||||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||||
"//submodules/Display:Display",
|
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import UIKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Display
|
|
||||||
|
private extension UIColor {
|
||||||
|
convenience init(rgb: UInt32) {
|
||||||
|
self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum PresentationBuiltinThemeReference: Int32 {
|
public enum PresentationBuiltinThemeReference: Int32 {
|
||||||
case dayClassic = 0
|
case dayClassic = 0
|
||||||
|
|||||||
@ -12,7 +12,7 @@ public enum UndoOverlayContent {
|
|||||||
case hidArchive(title: String, text: String, undo: Bool)
|
case hidArchive(title: String, text: String, undo: Bool)
|
||||||
case revealedArchive(title: String, text: String, undo: Bool)
|
case revealedArchive(title: String, text: String, undo: Bool)
|
||||||
case succeed(text: String)
|
case succeed(text: String)
|
||||||
case info(title: String?, text: String)
|
case info(title: String?, text: String, timeout: Double?)
|
||||||
case emoji(name: String, text: String)
|
case emoji(name: String, text: String)
|
||||||
case swipeToReply(title: String, text: String)
|
case swipeToReply(title: String, text: String)
|
||||||
case actionSucceeded(title: String, text: String, cancel: String)
|
case actionSucceeded(title: String, text: String, cancel: String)
|
||||||
|
|||||||
@ -173,7 +173,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.maximumNumberOfLines = 5
|
self.textNode.maximumNumberOfLines = 5
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 3
|
self.originalRemainingSeconds = 3
|
||||||
case let .info(title, text):
|
case let .info(title, text, timeout):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
self.iconCheckNode = nil
|
self.iconCheckNode = nil
|
||||||
@ -193,11 +193,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
self.textNode.maximumNumberOfLines = 10
|
self.textNode.maximumNumberOfLines = 10
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
|
if let timeout {
|
||||||
|
self.originalRemainingSeconds = timeout
|
||||||
|
} else {
|
||||||
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
|
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
|
||||||
|
}
|
||||||
|
|
||||||
if text.contains("](") {
|
if text.contains("](") {
|
||||||
isUserInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case let .actionSucceeded(title, text, cancel):
|
case let .actionSucceeded(title, text, cancel):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user