Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

# Conflicts:
#	submodules/TelegramCore/Sources/InvitationLinks.swift
This commit is contained in:
overtake 2021-01-19 16:03:02 +03:00
commit 56ebab264e
33 changed files with 4938 additions and 4640 deletions

View File

@ -5883,8 +5883,11 @@ Sorry for the inconvenience.";
"InviteLink.InviteLink" = "Invite Link"; "InviteLink.InviteLink" = "Invite Link";
"InviteLink.CreatedBy" = "Link Created By"; "InviteLink.CreatedBy" = "Link Created By";
"InviteLink.DeleteLinkAlert.Text" = "Are you sure you want to delete this link? It will be completely gone.";
"InviteLink.DeleteLinkAlert.Action" = "Delete";
"InviteLink.DeleteAllRevokedLinksAlert.Text" = "This will delete all revoked links."; "InviteLink.DeleteAllRevokedLinksAlert.Text" = "This will delete all revoked links.";
"InviteLink.DeleteAllRevokedLinksAlert.Action" = "Delete"; "InviteLink.DeleteAllRevokedLinksAlert.Action" = "Delete All";
"InviteLink.ExpiresIn" = "expires in %@"; "InviteLink.ExpiresIn" = "expires in %@";
@ -5892,3 +5895,5 @@ Sorry for the inconvenience.";
"Conversation.ChecksTooltip.Read" = "Read"; "Conversation.ChecksTooltip.Read" = "Read";
"DialogList.MultipleTypingPair" = "%@ and %@ are typing"; "DialogList.MultipleTypingPair" = "%@ and %@ are typing";
"Common.Save" = "Save";

View File

@ -1212,8 +1212,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.forEachController({ controller in strongSelf.forEachController({ controller in
if let controller = controller as? UndoOverlayController { if let controller = controller as? UndoOverlayController {
switch controller.content { switch controller.content {
case let .archivedChat(archivedChat): case let .archivedChat(peerId, _, _, _):
if peerIds.contains(PeerId(archivedChat.peerId)) { if peerIds.contains(PeerId(peerId)) {
controller.dismiss() controller.dismiss()
} }
default: default:

View File

@ -158,7 +158,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: { return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
interaction.activateSearch() interaction.activateSearch()
}) })
case let .sort(theme, strings, sortOrder): case let .sort(_, strings, sortOrder):
var text = strings.Contacts_SortedByName var text = strings.Contacts_SortedByName
if case .presence = sortOrder { if case .presence = sortOrder {
text = strings.Contacts_SortedByPresence text = strings.Contacts_SortedByPresence
@ -166,17 +166,17 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .inline(dropDownIcon, .right), highlight: .alpha, header: nil, action: { return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .inline(dropDownIcon, .right), highlight: .alpha, header: nil, action: {
interaction.openSortMenu() interaction.openSortMenu()
}) })
case let .permissionInfo(theme, title, text, suppressed): case let .permissionInfo(_, title, text, suppressed):
return InfoListItem(presentationData: ItemListPresentationData(presentationData), title: title, text: .plain(text), style: .plain, closeAction: suppressed ? nil : { return InfoListItem(presentationData: ItemListPresentationData(presentationData), title: title, text: .plain(text), style: .plain, closeAction: suppressed ? nil : {
interaction.suppressWarning() interaction.suppressWarning()
}) })
case let .permissionEnable(theme, text): case let .permissionEnable(_, text):
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .none, header: nil, action: { return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .none, header: nil, action: {
interaction.authorize() interaction.authorize()
}) })
case let .option(_, option, header, theme, _): case let .option(_, option, header, _, _):
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: header, action: option.action) return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: header, action: option.action)
case let .peer(_, peer, presence, header, selection, theme, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled): case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled):
var status: ContactsPeerItemStatus var status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer
var isContextActionEnabled = false var isContextActionEnabled = false
@ -928,9 +928,9 @@ public final class ContactListNode: ASDisplayNode {
|> mapToSignal { presentation in |> mapToSignal { presentation in
var generateSections = false var generateSections = false
var includeChatList = false var includeChatList = false
if case let .natural(natural) = presentation { if case let .natural(_, includeChatListValue) = presentation {
generateSections = true generateSections = true
includeChatList = natural.includeChatList includeChatList = includeChatListValue
} }
if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels, globalSearch) = presentation { if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels, globalSearch) = presentation {

View File

@ -1225,7 +1225,7 @@ public class GalleryController: ViewController, StandalonePresentableController
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle()) self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent()) self.centralItemFooterContentNode.set(centralItemNode.footerContent())
if let (media, _) = mediaForMessage(message: message) { if let _ = mediaForMessage(message: message) {
centralItemNode.activateAsInitial() centralItemNode.activateAsInitial()
} }
} }

View File

@ -50,7 +50,7 @@ func isValidNumberOfUsers(_ number: String) -> Bool {
private enum InviteLinksEditEntry: ItemListNodeEntry { private enum InviteLinksEditEntry: ItemListNodeEntry {
case timeHeader(PresentationTheme, String) case timeHeader(PresentationTheme, String)
case timePicker(PresentationTheme, InviteLinkTimeLimit) case timePicker(PresentationTheme, InviteLinkTimeLimit)
case timeExpiryDate(PresentationTheme, Int32?) case timeExpiryDate(PresentationTheme, Int32?, Bool)
case timeCustomPicker(PresentationTheme, Int32?) case timeCustomPicker(PresentationTheme, Int32?)
case timeInfo(PresentationTheme, String) case timeInfo(PresentationTheme, String)
@ -111,8 +111,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .timeExpiryDate(lhsTheme, lhsDate): case let .timeExpiryDate(lhsTheme, lhsDate, lhsActive):
if case let .timeExpiryDate(rhsTheme, rhsDate) = rhs, lhsTheme === rhsTheme, lhsDate == rhsDate { if case let .timeExpiryDate(rhsTheme, rhsDate, rhsActive) = rhs, lhsTheme === rhsTheme, lhsDate == rhsDate, lhsActive == rhsActive {
return true return true
} else { } else {
return false return false
@ -182,14 +182,14 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
return updatedState return updatedState
}) })
}) })
case let .timeExpiryDate(_, value): case let .timeExpiryDate(theme, value, active):
let text: String let text: String
if let value = value { if let value = value {
text = stringForFullDate(timestamp: value, strings: presentationData.strings, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ".")) text = stringForFullDate(timestamp: value, strings: presentationData.strings, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."))
} else { } else {
text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever
} }
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.InviteLink_Create_TimeLimitExpiryDate, label: text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.InviteLink_Create_TimeLimitExpiryDate, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
arguments.dismissInput() arguments.dismissInput()
arguments.updateState { state in arguments.updateState { state in
var updatedState = state var updatedState = state
@ -223,7 +223,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
}) })
case let .usageCustomPicker(theme, value, focused): case let .usageCustomPicker(theme, value, focused):
let text = value.flatMap { String($0) } ?? (focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited) let text = value.flatMap { String($0) } ?? (focused ? "" : presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsersUnlimited)
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, tag: nil, sectionId: self.section, textUpdated: { updatedText in return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: presentationData.strings.InviteLink_Create_UsersLimitNumberOfUsers, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, alignment: .right, selectAllOnFocus: true, tag: nil, sectionId: self.section, textUpdated: { updatedText in
guard !updatedText.isEmpty else { guard !updatedText.isEmpty else {
return return
} }
@ -276,7 +276,7 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
} else if let value = state.time.value { } else if let value = state.time.value {
time = currentTime + value time = currentTime + value
} }
entries.append(.timeExpiryDate(presentationData.theme, time)) entries.append(.timeExpiryDate(presentationData.theme, time, state.pickingTimeLimit))
if state.pickingTimeLimit { if state.pickingTimeLimit {
entries.append(.timeCustomPicker(presentationData.theme, time ?? currentTime)) entries.append(.timeCustomPicker(presentationData.theme, time ?? currentTime))
} }
@ -300,9 +300,10 @@ private struct InviteLinkEditControllerState: Equatable {
var time: InviteLinkTimeLimit var time: InviteLinkTimeLimit
var pickingTimeLimit = false var pickingTimeLimit = false
var pickingUsageLimit = false var pickingUsageLimit = false
var updating = false
} }
public func inviteLinkEditController(context: AccountContext, peerId: PeerId, invite: ExportedInvitation?, completion: (() -> Void)? = nil) -> ViewController { public func inviteLinkEditController(context: AccountContext, peerId: PeerId, invite: ExportedInvitation?, completion: ((ExportedInvitation?) -> Void)? = nil) -> ViewController {
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
@ -327,7 +328,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
initialState = InviteLinkEditControllerState(usage: InviteLinkUsageLimit(value: usageLimit), time: timeLimit, pickingTimeLimit: false, pickingUsageLimit: false) initialState = InviteLinkEditControllerState(usage: InviteLinkUsageLimit(value: usageLimit), time: timeLimit, pickingTimeLimit: false, pickingUsageLimit: false)
} else { } else {
initialState = InviteLinkEditControllerState(usage: .medium, time: .week, pickingTimeLimit: false, pickingUsageLimit: false) initialState = InviteLinkEditControllerState(usage: .unlimited, time: .unlimited, pickingTimeLimit: false, pickingUsageLimit: false)
} }
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
@ -359,8 +360,17 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
dismissAction() dismissAction()
dismissImpl?() dismissImpl?()
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link)
completion?() |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
|> deliverOnMainQueue).start(next: { invite in
completion?(invite)
}, error: { _ in
updateState { state in
var updatedState = state
updatedState.updating = false
return updatedState
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}) })
}) })
]), ]),
@ -369,6 +379,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
presentControllerImpl?(controller, nil) presentControllerImpl?(controller, nil)
}) })
let previousState = Atomic<InviteLinkEditControllerState?>(value: nil)
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get())
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
@ -376,7 +387,13 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
dismissImpl?() dismissImpl?()
}) })
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { let rightNavigationButton = ItemListNavigationButton(content: .text(invite == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Save), style: state.updating ? .activity : .bold, enabled: true, action: {
updateState { state in
var updatedState = state
updatedState.updating = true
return updatedState
}
let expireDate: Int32? let expireDate: Int32?
if case let .custom(value) = state.time { if case let .custom(value) = state.time {
expireDate = value expireDate = value
@ -390,21 +407,43 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
let usageLimit = state.usage.value let usageLimit = state.usage.value
if invite == nil { if invite == nil {
let _ = (createPeerExportedInvitation(account: context.account, peerId: peerId, expireDate: expireDate, usageLimit: usageLimit) let _ = (createPeerExportedInvitation(account: context.account, peerId: peerId, expireDate: expireDate, usageLimit: usageLimit)
|> deliverOnMainQueue).start(next: { result in |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
completion?() |> deliverOnMainQueue).start(next: { invite in
completion?(invite)
dismissImpl?() dismissImpl?()
}, error: { _ in
updateState { state in
var updatedState = state
updatedState.updating = false
return updatedState
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}) })
} else if let invite = invite { } else if let invite = invite {
let _ = (editPeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link, expireDate: expireDate, usageLimit: usageLimit) let _ = (editPeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link, expireDate: expireDate, usageLimit: usageLimit)
|> deliverOnMainQueue).start(next: { result in |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
completion?() |> deliverOnMainQueue).start(next: { invite in
completion?(invite)
dismissImpl?() dismissImpl?()
}, error: { _ in
updateState { state in
var updatedState = state
updatedState.updating = false
return updatedState
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}) })
} }
}) })
let previousState = previousState.swap(state)
var animateChanges = false
if let previousState = previousState, previousState.pickingTimeLimit != state.pickingTimeLimit {
animateChanges = true
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(invite == nil ? presentationData.strings.InviteLink_Create_Title : presentationData.strings.InviteLink_Create_EditTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(invite == nil ? presentationData.strings.InviteLink_Create_Title : presentationData.strings.InviteLink_Create_EditTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -139,7 +139,9 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
case let .header(theme, title, text): case let .header(theme, title, text):
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text) return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
case let .mainLink(_, invite): case let .mainLink(_, invite):
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, shareAction: { return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
interaction.copyLink(invite)
}, shareAction: {
interaction.shareLink(invite) interaction.shareLink(invite)
}, contextAction: { node in }, contextAction: { node in
interaction.mainLinkContextAction(invite, node, nil) interaction.mainLinkContextAction(invite, node, nil)
@ -371,7 +373,7 @@ public final class InviteLinkInviteController: ViewController {
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction() dismissAction()
self?.revokeDisposable.set((ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId, revokeExisted: true) |> deliverOnMainQueue).start(completed: { self?.revokeDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
})) }))
}) })
@ -585,11 +587,6 @@ public final class InviteLinkInviteController: ViewController {
if result === self.headerNode.view { if result === self.headerNode.view {
return self.view return self.view
} }
if result === self.headerNode.view {
return self.view
}
if !self.bounds.contains(point) { if !self.bounds.contains(point) {
return nil return nil
} }
@ -615,8 +612,6 @@ public final class InviteLinkInviteController: ViewController {
case .changed: case .changed:
var translation = recognizer.translation(in: self.contentNode.view).y var translation = recognizer.translation(in: self.contentNode.view).y
if let currentPanOffset = self.panGestureArguments { if let currentPanOffset = self.panGestureArguments {
if case let .known(value) = contentOffset, value <= 0.5 { if case let .known(value) = contentOffset, value <= 0.5 {
} else { } else {
translation = currentPanOffset translation = currentPanOffset

View File

@ -22,18 +22,20 @@ import ShareController
private final class InviteLinkListControllerArguments { private final class InviteLinkListControllerArguments {
let context: AccountContext let context: AccountContext
let shareMainLink: (ExportedInvitation?) -> Void let shareMainLink: (ExportedInvitation) -> Void
let openMainLink: (ExportedInvitation?) -> Void let openMainLink: (ExportedInvitation) -> Void
let copyLink: (ExportedInvitation) -> Void
let mainLinkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void let mainLinkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
let createLink: () -> Void let createLink: () -> Void
let openLink: (ExportedInvitation) -> Void let openLink: (ExportedInvitation) -> Void
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
let deleteAllRevokedLinks: () -> Void let deleteAllRevokedLinks: () -> Void
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation?) -> Void, openMainLink: @escaping (ExportedInvitation?) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, deleteAllRevokedLinks: @escaping () -> Void) { init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
self.context = context self.context = context
self.shareMainLink = shareMainLink self.shareMainLink = shareMainLink
self.openMainLink = openMainLink self.openMainLink = openMainLink
self.copyLink = copyLink
self.mainLinkContextAction = mainLinkContextAction self.mainLinkContextAction = mainLinkContextAction
self.createLink = createLink self.createLink = createLink
self.openLink = openLink self.openLink = openLink
@ -178,8 +180,14 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case let .mainLinkHeader(_, text): case let .mainLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .mainLink(_, invite, peers): case let .mainLink(_, invite, peers):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, shareAction: { return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: peers, displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
arguments.shareMainLink(invite) if let invite = invite {
arguments.copyLink(invite)
}
}, shareAction: {
if let invite = invite {
arguments.shareMainLink(invite)
}
}, contextAction: { node in }, contextAction: { node in
arguments.mainLinkContextAction(invite, node, nil) arguments.mainLinkContextAction(invite, node, nil)
}, viewAction: { }, viewAction: {
@ -299,33 +307,21 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
let deleteAllRevokedLinksDisposable = MetaDisposable() let deleteAllRevokedLinksDisposable = MetaDisposable()
actionsDisposable.add(deleteAllRevokedLinksDisposable) actionsDisposable.add(deleteAllRevokedLinksDisposable)
actionsDisposable.add((context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<String?, NoError> in
return ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId)
|> mapToSignal { _ -> Signal<String?, NoError> in
return .complete()
}
}).start())
var getControllerImpl: (() -> ViewController?)? var getControllerImpl: (() -> ViewController?)?
let invitesPromise = Promise<ExportedInvitations?>() let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: false)
invitesPromise.set(.single(nil) let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: true, forceUpdate: true)
|> then(peerExportedInvitations(account: context.account, peerId: peerId, revoked: false)))
let revokedInvitesPromise = Promise<ExportedInvitations?>()
revokedInvitesPromise.set(.single(nil)
|> then(peerExportedInvitations(account: context.account, peerId: peerId, revoked: true)))
let arguments = InviteLinkListControllerArguments(context: context, shareMainLink: { invite in let arguments = InviteLinkListControllerArguments(context: context, shareMainLink: { invite in
if let invite = invite { let shareController = ShareController(context: context, subject: .url(invite.link))
let shareController = ShareController(context: context, subject: .url(invite.link)) presentControllerImpl?(shareController, nil)
presentControllerImpl?(shareController, nil)
}
}, openMainLink: { invite in }, openMainLink: { invite in
if let invite = invite { let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: nil, importersContext: nil)
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, importersContext: nil) pushControllerImpl?(controller)
pushControllerImpl?(controller) }, copyLink: { invite in
} UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil)
}, mainLinkContextAction: { invite, node, gesture in }, mainLinkContextAction: { invite, node, gesture in
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else { guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else {
return return
@ -383,13 +379,15 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
} }
} }
if revoke { if revoke {
revokeLinkDisposable.set((ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId, revokeExisted: true) |> deliverOnMainQueue).start(completed: { revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
updateState { state in updateState { state in
var updatedState = state var updatedState = state
updatedState.revokingPrivateLink = false updatedState.revokingPrivateLink = false
return updatedState return updatedState
} }
invitesPromise.set(peerExportedInvitations(account: context.account, peerId: peerId, revoked: false))
invitesContext.reload()
revokedInvitesContext.reload()
})) }))
} }
}) })
@ -402,14 +400,16 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
presentInGlobalOverlayImpl?(contextController) presentInGlobalOverlayImpl?(contextController)
}, createLink: { }, createLink: {
let controller = inviteLinkEditController(context: context, peerId: peerId, invite: nil, completion: { let controller = inviteLinkEditController(context: context, peerId: peerId, invite: nil, completion: { invite in
invitesPromise.set(peerExportedInvitations(account: context.account, peerId: peerId, revoked: false)) if let invite = invite {
invitesContext.add(invite)
}
}) })
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, openLink: { invite in }, openLink: { invite in
if let invite = invite { if let invite = invite {
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, importersContext: nil) let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: invitesContext, importersContext: nil)
pushControllerImpl?(controller) pushControllerImpl?(controller)
} }
}, linkContextAction: { invite, node, gesture in }, linkContextAction: { invite, node, gesture in
@ -445,8 +445,15 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
let controller = inviteLinkEditController(context: context, peerId: peerId, invite: invite, completion: { let controller = inviteLinkEditController(context: context, peerId: peerId, invite: invite, completion: { invite in
invitesPromise.set(peerExportedInvitations(account: context.account, peerId: peerId, revoked: false)) if let invite = invite {
if invite.isRevoked {
invitesContext.remove(invite)
revokedInvitesContext.add(invite)
} else {
invitesContext.update(invite)
}
}
}) })
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
pushControllerImpl?(controller) pushControllerImpl?(controller)
@ -465,13 +472,15 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
} }
controller.setItemGroups([ controller.setItemGroups([
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text), ActionSheetTextItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: {
dismissAction() dismissAction()
revokeLinkDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { revokeLinkDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})) }))
revokedInvitesContext.remove(invite)
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
@ -497,6 +506,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
})) }))
invitesContext.remove(invite)
revokedInvitesContext.add(invite)
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
@ -520,8 +532,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
dismissAction() dismissAction()
deleteAllRevokedLinksDisposable.set((deleteAllRevokedPeerExportedInvitations(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: { deleteAllRevokedLinksDisposable.set((deleteAllRevokedPeerExportedInvitations(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
})) }))
revokedInvitesContext.clear()
}) })
]), ]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
@ -551,11 +564,11 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
importersState.set(context.state |> map(Optional.init)) importersState.set(context.state |> map(Optional.init))
} }
let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesPromise.get(), revokedInvitesPromise.get()) let signal = combineLatest(context.sharedContext.presentationData, peerView, importersContext, importersState.get(), invitesContext.state, revokedInvitesContext.state)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, view, importersContext, importers, invites, revokedInvites -> (ItemListControllerState, (ItemListNodeState, Any)) in
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.InviteLink_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites?.list, revokedInvites: revokedInvites?.list, mainPeers: importers?.importers.compactMap { $0.peer.peer } ?? []), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.invitations, revokedInvites: revokedInvites.invitations, mainPeers: importers?.importers.compactMap { $0.peer.peer } ?? []), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -342,7 +342,6 @@ public final class InviteLinkQRCodeController: ViewController {
self.containerLayout = (layout, navigationBarHeight) self.containerLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar, .input]) var insets = layout.insets(options: [.statusBar, .input])
let cleanInsets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top) insets.top = max(10.0, insets.top)
let makeImageLayout = self.qrImageNode.asyncLayout() let makeImageLayout = self.qrImageNode.asyncLayout()

View File

@ -24,12 +24,14 @@ import DirectionalPanGesture
class InviteLinkViewInteraction { class InviteLinkViewInteraction {
let context: AccountContext let context: AccountContext
let openPeer: (PeerId) -> Void let openPeer: (PeerId) -> Void
let copyLink: (ExportedInvitation) -> Void
let shareLink: (ExportedInvitation) -> Void let shareLink: (ExportedInvitation) -> Void
let contextAction: (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void let contextAction: (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void
init(context: AccountContext, openPeer: @escaping (PeerId) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, contextAction: @escaping (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void) { init(context: AccountContext, openPeer: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, contextAction: @escaping (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context self.context = context
self.openPeer = openPeer self.openPeer = openPeer
self.copyLink = copyLink
self.shareLink = shareLink self.shareLink = shareLink
self.contextAction = contextAction self.contextAction = contextAction
} }
@ -116,7 +118,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
return false return false
} }
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsLoading): case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsLoading):
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate { if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsLoading == rhsLoading {
return true return true
} else { } else {
return false return false
@ -169,7 +171,9 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
case let .link(_, invite): case let .link(_, invite):
let buttonColor = color(for: invite) let buttonColor = color(for: invite)
let availability = invitationAvailability(invite) let availability = invitationAvailability(invite)
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: !invite.isRevoked && !availability.isZero, displayImporters: false, buttonColor: buttonColor, sectionId: 0, style: .plain, shareAction: { return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, peers: [], displayButton: !invite.isRevoked && !availability.isZero, displayImporters: false, buttonColor: buttonColor, sectionId: 0, style: .plain, copyAction: {
interaction.copyLink(invite)
}, shareAction: {
interaction.shareLink(invite) interaction.shareLink(invite)
}, contextAction: { node in }, contextAction: { node in
interaction.contextAction(invite, node, nil) interaction.contextAction(invite, node, nil)
@ -188,7 +192,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
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: true, 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: true, sectionId: 0, action: {
interaction.openPeer(peer.id) interaction.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: ItemListPeerItemShimmering(alternationIndex: 0)) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
} }
} }
} }
@ -213,14 +217,16 @@ public final class InviteLinkViewController: ViewController {
private let context: AccountContext private let context: AccountContext
private let peerId: PeerId private let peerId: PeerId
private let invite: ExportedInvitation private let invite: ExportedInvitation
private let invitationsContext: PeerExportedInvitationsContext?
private let importersContext: PeerInvitationImportersContext? private let importersContext: PeerInvitationImportersContext?
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
public init(context: AccountContext, peerId: PeerId, invite: ExportedInvitation, importersContext: PeerInvitationImportersContext?) { public init(context: AccountContext, peerId: PeerId, invite: ExportedInvitation, invitationsContext: PeerExportedInvitationsContext?, importersContext: PeerInvitationImportersContext?) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.invite = invite self.invite = invite
self.invitationsContext = invitationsContext
self.importersContext = importersContext self.importersContext = importersContext
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -383,6 +389,10 @@ public final class InviteLinkViewController: ViewController {
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), keepStack: .always)) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), keepStack: .always))
} }
}, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self?.controller?.present(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), in: .window(.root))
}, shareLink: { [weak self] invite in }, shareLink: { [weak self] invite in
let shareController = ShareController(context: context, subject: .url(invite.link)) let shareController = ShareController(context: context, subject: .url(invite.link))
self?.controller?.present(shareController, in: .window(.root)) self?.controller?.present(shareController, in: .window(.root))
@ -531,8 +541,17 @@ public final class InviteLinkViewController: ViewController {
let navigationController = self.controller?.navigationController as? NavigationController let navigationController = self.controller?.navigationController as? NavigationController
self.controller?.dismiss() self.controller?.dismiss()
let invitationsContext = self.controller?.invitationsContext
if let navigationController = navigationController { if let navigationController = navigationController {
let controller = inviteLinkEditController(context: self.context, peerId: self.peerId, invite: self.invite) let controller = inviteLinkEditController(context: self.context, peerId: self.peerId, invite: self.invite, completion: { [weak self] invite in
if let invite = invite {
if invite.isRevoked {
invitationsContext?.remove(invite)
} else {
invitationsContext?.update(invite)
}
}
})
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
} }
@ -655,11 +674,6 @@ public final class InviteLinkViewController: ViewController {
if result === self.headerNode.view { if result === self.headerNode.view {
return self.view return self.view
} }
if result === self.headerNode.view {
return self.view
}
if !self.bounds.contains(point) { if !self.bounds.contains(point) {
return nil return nil
} }

View File

@ -73,7 +73,7 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode private let maskNode: ASImageNode
private let datePicker: UIDatePicker private var datePicker: UIDatePicker?
private var item: ItemListDatePickerItem? private var item: ItemListDatePickerItem?
@ -98,25 +98,31 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true self.bottomStripeNode.isLayerBacked = true
self.datePicker = UIDatePicker()
self.datePicker.minimumDate = Date()
self.datePicker.datePickerMode = .dateAndTime
if #available(iOS 14.0, *) {
self.datePicker.preferredDatePickerStyle = .inline
}
super.init(layerBacked: false, dynamicBounce: false) super.init(layerBacked: false, dynamicBounce: false)
self.datePicker.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
} }
public override func didLoad() { public override func didLoad() {
super.didLoad() super.didLoad()
self.view.addSubview(self.datePicker) let datePicker = UIDatePicker()
datePicker.minimumDate = Date()
datePicker.datePickerMode = .dateAndTime
if #available(iOS 14.0, *) {
datePicker.preferredDatePickerStyle = .inline
}
datePicker.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
self.view.addSubview(datePicker)
self.datePicker = datePicker
} }
@objc private func datePickerUpdated() { @objc private func datePickerUpdated() {
self.item?.updated?(Int32(self.datePicker.date.timeIntervalSince1970)) guard let datePicker = self.datePicker else {
return
}
self.item?.updated?(Int32(datePicker.date.timeIntervalSince1970))
} }
public func asyncLayout() -> (_ item: ItemListDatePickerItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { public func asyncLayout() -> (_ item: ItemListDatePickerItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@ -162,8 +168,8 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
} }
strongSelf.datePicker.date = item.date.flatMap { Date(timeIntervalSince1970: TimeInterval($0)) } ?? Date() strongSelf.datePicker?.date = item.date.flatMap { Date(timeIntervalSince1970: TimeInterval($0)) } ?? Date()
strongSelf.datePicker.frame = CGRect(origin: CGPoint(x: 16.0, y: 3.0), size: CGSize(width: contentSize.width - 32.0, height: contentSize.height)) strongSelf.datePicker?.frame = CGRect(origin: CGPoint(x: 16.0, y: 3.0), size: CGSize(width: contentSize.width - 32.0, height: contentSize.height))
switch item.style { switch item.style {
case .plain: case .plain:

View File

@ -130,7 +130,6 @@ public class ItemListInviteLinkGridItemNode: ListViewItemNode, ItemListItemNode
let itemSeparatorColor: UIColor let itemSeparatorColor: UIColor
let leftInset = 16.0 + params.leftInset let leftInset = 16.0 + params.leftInset
let rightInset = 16.0 + params.rightInset
var height: CGFloat var height: CGFloat
let count = item.invites?.count ?? 0 let count = item.invites?.count ?? 0

View File

@ -35,6 +35,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
let buttonColor: UIColor? let buttonColor: UIColor?
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
let style: ItemListStyle let style: ItemListStyle
let copyAction: (() -> Void)?
let shareAction: (() -> Void)? let shareAction: (() -> Void)?
let contextAction: ((ASDisplayNode) -> Void)? let contextAction: ((ASDisplayNode) -> Void)?
let viewAction: (() -> Void)? let viewAction: (() -> Void)?
@ -50,6 +51,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
buttonColor: UIColor?, buttonColor: UIColor?,
sectionId: ItemListSectionId, sectionId: ItemListSectionId,
style: ItemListStyle, style: ItemListStyle,
copyAction: (() -> Void)?,
shareAction: (() -> Void)?, shareAction: (() -> Void)?,
contextAction: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode) -> Void)?,
viewAction: (() -> Void)?, viewAction: (() -> Void)?,
@ -64,6 +66,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
self.buttonColor = buttonColor self.buttonColor = buttonColor
self.sectionId = sectionId self.sectionId = sectionId
self.style = style self.style = style
self.copyAction = copyAction
self.shareAction = shareAction self.shareAction = shareAction
self.contextAction = contextAction self.contextAction = contextAction
self.viewAction = viewAction self.viewAction = viewAction
@ -114,6 +117,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
private let fieldNode: ASImageNode private let fieldNode: ASImageNode
private let addressNode: TextNode private let addressNode: TextNode
private let fieldButtonNode: HighlightTrackingButtonNode
private let extractedContainerNode: ContextExtractedContentContainingNode private let extractedContainerNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
private let addressButtonNode: HighlightTrackingButtonNode private let addressButtonNode: HighlightTrackingButtonNode
@ -158,6 +162,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
self.addressNode = TextNode() self.addressNode = TextNode()
self.addressNode.isUserInteractionEnabled = false self.addressNode.isUserInteractionEnabled = false
self.fieldButtonNode = HighlightTrackingButtonNode()
self.addressButtonNode = HighlightTrackingButtonNode() self.addressButtonNode = HighlightTrackingButtonNode()
self.extractedContainerNode = ContextExtractedContentContainingNode() self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode() self.containerNode = ContextControllerSourceNode()
@ -177,6 +183,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
self.addSubnode(self.fieldNode) self.addSubnode(self.fieldNode)
self.addSubnode(self.addressNode) self.addSubnode(self.addressNode)
self.addSubnode(self.fieldButtonNode)
self.addSubnode(self.avatarsNode) self.addSubnode(self.avatarsNode)
self.addSubnode(self.invitedPeersNode) self.addSubnode(self.invitedPeersNode)
self.addSubnode(self.avatarsButtonNode) self.addSubnode(self.avatarsButtonNode)
@ -189,6 +196,19 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
self.addSubnode(self.activateArea) self.addSubnode(self.activateArea)
self.fieldButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.addressNode.layer.removeAnimation(forKey: "opacity")
strongSelf.addressNode.alpha = 0.4
} else {
strongSelf.addressNode.alpha = 1.0
strongSelf.addressNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.fieldButtonNode.addTarget(self, action: #selector(self.fieldButtonPressed), forControlEvents: .touchUpInside)
self.addressButtonNode.addTarget(self, action: #selector(self.addressButtonPressed), forControlEvents: .touchUpInside) self.addressButtonNode.addTarget(self, action: #selector(self.addressButtonPressed), forControlEvents: .touchUpInside)
self.addressButtonNode.highligthedChanged = { [weak self] highlighted in self.addressButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
@ -224,6 +244,12 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
self.avatarsButtonNode.addTarget(self, action: #selector(self.avatarsButtonPressed), forControlEvents: .touchUpInside) self.avatarsButtonNode.addTarget(self, action: #selector(self.avatarsButtonPressed), forControlEvents: .touchUpInside)
} }
@objc private func fieldButtonPressed() {
if let item = self.item {
item.copyAction?()
}
}
@objc private func addressButtonPressed() { @objc private func addressButtonPressed() {
if let item = self.item { if let item = self.item {
item.contextAction?(self.extractedContainerNode) item.contextAction?(self.extractedContainerNode)
@ -393,6 +419,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
let fieldFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: CGSize(width: params.width - leftInset - rightInset, height: fieldHeight)) let fieldFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: CGSize(width: params.width - leftInset - rightInset, height: fieldHeight))
strongSelf.fieldNode.frame = fieldFrame strongSelf.fieldNode.frame = fieldFrame
strongSelf.fieldButtonNode.frame = fieldFrame
strongSelf.addressNode.frame = CGRect(origin: CGPoint(x: fieldFrame.minX + floorToScreenPixels((fieldFrame.width - addressLayout.size.width) / 2.0), y: fieldFrame.minY + floorToScreenPixels((fieldFrame.height - addressLayout.size.height) / 2.0) + 1.0), size: addressLayout.size) strongSelf.addressNode.frame = CGRect(origin: CGPoint(x: fieldFrame.minX + floorToScreenPixels((fieldFrame.width - addressLayout.size.width) / 2.0), y: fieldFrame.minY + floorToScreenPixels((fieldFrame.height - addressLayout.size.height) / 2.0) + 1.0), size: addressLayout.size)

View File

@ -18,6 +18,7 @@ public enum ItemListDisclosureStyle {
public enum ItemListDisclosureLabelStyle { public enum ItemListDisclosureLabelStyle {
case text case text
case detailText case detailText
case coloredText(UIColor)
case multilineDetailText case multilineDetailText
case badge(UIColor) case badge(UIColor)
case color(UIColor) case color(UIColor)
@ -277,6 +278,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor
labelFont = detailFont labelFont = detailFont
labelConstrain = params.width - params.rightInset - 40.0 - leftInset labelConstrain = params.width - params.rightInset - 40.0 - leftInset
case let .coloredText(color):
labelBadgeColor = color
labelFont = titleFont
default: default:
labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor labelBadgeColor = item.presentationData.theme.list.itemSecondaryTextColor
labelFont = titleFont labelFont = titleFont

View File

@ -46,6 +46,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let clearType: ItemListSingleLineInputClearType let clearType: ItemListSingleLineInputClearType
let maxLength: Int let maxLength: Int
let enabled: Bool let enabled: Bool
let selectAllOnFocus: Bool
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
let action: () -> Void let action: () -> Void
let textUpdated: (String) -> Void let textUpdated: (String) -> Void
@ -55,7 +56,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let cleared: (() -> Void)? let cleared: (() -> Void)?
public let tag: ItemListItemTag? public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) { public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.title = title self.title = title
self.text = text self.text = text
@ -67,6 +68,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
self.clearType = clearType self.clearType = clearType
self.maxLength = maxLength self.maxLength = maxLength
self.enabled = enabled self.enabled = enabled
self.selectAllOnFocus = selectAllOnFocus
self.tag = tag self.tag = tag
self.sectionId = sectionId self.sectionId = sectionId
self.textUpdated = textUpdated self.textUpdated = textUpdated
@ -494,6 +496,13 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
@objc public func textFieldDidBeginEditing(_ textField: UITextField) { @objc public func textFieldDidBeginEditing(_ textField: UITextField) {
self.item?.updatedFocus?(true) self.item?.updatedFocus?(true)
if self.item?.selectAllOnFocus == true {
DispatchQueue.main.async {
let startPosition = self.textNode.textField.beginningOfDocument
let endPosition = self.textNode.textField.endOfDocument
self.textNode.textField.selectedTextRange = self.textNode.textField.textRange(from: startPosition, to: endPosition)
}
}
self.updateClearButtonVisibility() self.updateClearButtonVisibility()
} }

View File

@ -368,7 +368,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ChannelAdminControllerArguments let arguments = arguments as! ChannelAdminControllerArguments
switch self { switch self {
case let .info(_, strings, dateTimeFormat, peer, presence): case let .info(_, _, dateTimeFormat, peer, presence):
return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, cachedData: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, cachedData: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in
}, avatarTapped: { }, avatarTapped: {
}) })

View File

@ -182,17 +182,17 @@ private enum ChannelMembersEntry: ItemListNodeEntry {
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ChannelMembersControllerArguments let arguments = arguments as! ChannelMembersControllerArguments
switch self { switch self {
case let .addMember(theme, text): case let .addMember(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.addMember() arguments.addMember()
}) })
case let .inviteLink(theme, text): case let .inviteLink(_, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.inviteViaLink() arguments.inviteViaLink()
}) })
case let .addMemberInfo(theme, text): case let .addMemberInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled): case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled):
let text: ItemListPeerItemText let text: ItemListPeerItemText
if let user = participant.peer as? TelegramUser, let _ = user.botInfo { if let user = participant.peer as? TelegramUser, let _ = user.botInfo {
text = .text(strings.Bot_GenericBotStatus, .secondary) text = .text(strings.Bot_GenericBotStatus, .secondary)
@ -465,6 +465,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
} }
}, inviteViaLink: { }, inviteViaLink: {
if let controller = getControllerImpl?() { if let controller = getControllerImpl?() {
dismissInputImpl?()
presentControllerImpl?(InviteLinkInviteController(context: context, peerId: peerId, parentNavigationController: controller.navigationController as? NavigationController), nil) presentControllerImpl?(InviteLinkInviteController(context: context, peerId: peerId, parentNavigationController: controller.navigationController as? NavigationController), nil)
} }
}) })

View File

@ -30,11 +30,12 @@ private final class ChannelVisibilityControllerArguments {
let displayPrivateLinkMenu: (String) -> Void let displayPrivateLinkMenu: (String) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let revokePeerId: (PeerId) -> Void let revokePeerId: (PeerId) -> Void
let shareLink: () -> Void let copyLink: (ExportedInvitation) -> Void
let shareLink: (ExportedInvitation) -> Void
let linkContextAction: (ASDisplayNode) -> Void let linkContextAction: (ASDisplayNode) -> Void
let manageInviteLinks: () -> Void let manageInviteLinks: () -> Void
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, shareLink: @escaping () -> Void, linkContextAction: @escaping (ASDisplayNode) -> Void, manageInviteLinks: @escaping () -> Void) { init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode) -> Void, manageInviteLinks: @escaping () -> Void) {
self.context = context self.context = context
self.updateCurrentType = updateCurrentType self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText self.updatePublicLinkText = updatePublicLinkText
@ -42,6 +43,7 @@ private final class ChannelVisibilityControllerArguments {
self.displayPrivateLinkMenu = displayPrivateLinkMenu self.displayPrivateLinkMenu = displayPrivateLinkMenu
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.revokePeerId = revokePeerId self.revokePeerId = revokePeerId
self.copyLink = copyLink
self.shareLink = shareLink self.shareLink = shareLink
self.linkContextAction = linkContextAction self.linkContextAction = linkContextAction
self.manageInviteLinks = manageInviteLinks self.manageInviteLinks = manageInviteLinks
@ -77,7 +79,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case publicLinkAvailability(PresentationTheme, String, Bool) case publicLinkAvailability(PresentationTheme, String, Bool)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String) case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
case privateLinkHeader(PresentationTheme, String) case privateLinkHeader(PresentationTheme, String)
case privateLink(PresentationTheme, ExportedInvitation?) case privateLink(PresentationTheme, ExportedInvitation?, Bool)
case privateLinkInfo(PresentationTheme, String) case privateLinkInfo(PresentationTheme, String)
case privateLinkManage(PresentationTheme, String) case privateLinkManage(PresentationTheme, String)
case privateLinkManageInfo(PresentationTheme, String) case privateLinkManageInfo(PresentationTheme, String)
@ -182,8 +184,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .privateLink(lhsTheme, lhsInvite): case let .privateLink(lhsTheme, lhsInvite, lhsDisplayImporters):
if case let .privateLink(rhsTheme, rhsInvite) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite { if case let .privateLink(rhsTheme, rhsInvite, rhsDisplayImporters) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, lhsDisplayImporters == rhsDisplayImporters {
return true return true
} else { } else {
return false return false
@ -290,9 +292,15 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section) return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section)
case let .privateLinkHeader(_, title): case let .privateLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .privateLink(_, invite): case let .privateLink(_, invite, displayImporters):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], displayButton: true, displayImporters: true, buttonColor: nil, sectionId: self.section, style: .blocks, shareAction: { return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
arguments.shareLink() if let invite = invite {
arguments.copyLink(invite)
}
}, shareAction: {
if let invite = invite {
arguments.shareLink(invite)
}
}, contextAction: { node in }, contextAction: { node in
arguments.linkContextAction(node) arguments.linkContextAction(node)
}, viewAction: { }, viewAction: {
@ -594,7 +602,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
case .privateChannel: case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased())) entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite)) entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
if isGroup { if isGroup {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
} else { } else {
@ -613,7 +621,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
case .privateLink: case .privateLink:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased())) entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite)) entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
switch mode { switch mode {
case .initialSetup: case .initialSetup:
@ -712,7 +720,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
case .privateChannel: case .privateChannel:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased())) entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_PermanentLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite)) entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
switch mode { switch mode {
case .initialSetup: case .initialSetup:
@ -836,13 +844,6 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
let revokeLinkDisposable = MetaDisposable() let revokeLinkDisposable = MetaDisposable()
actionsDisposable.add(revokeLinkDisposable) actionsDisposable.add(revokeLinkDisposable)
actionsDisposable.add((context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<String?, NoError> in
return ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId)
|> mapToSignal { _ -> Signal<String?, NoError> in
return .complete()
}
}).start())
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
updateState { state in updateState { state in
return state.withUpdatedSelectedType(type) return state.withUpdatedSelectedType(type)
@ -898,22 +899,13 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
} }
}) })
})) }))
}, shareLink: { }, copyLink: { invite in
let _ = (context.account.postbox.transaction { transaction -> String? in UIPasteboard.general.string = invite.link
if let cachedData = transaction.getPeerCachedData(peerId: peerId) { let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if let cachedData = cachedData as? CachedChannelData { presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Username_LinkCopied, false)), nil)
return cachedData.exportedInvitation?.link }, shareLink: { invite in
} else if let cachedData = cachedData as? CachedGroupData { let shareController = ShareController(context: context, subject: .url(invite.link))
return cachedData.exportedInvitation?.link presentControllerImpl?(shareController, nil)
}
}
return nil
} |> deliverOnMainQueue).start(next: { link in
if let link = link {
let shareController = ShareController(context: context, subject: .url(link))
presentControllerImpl?(shareController, nil)
}
})
}, linkContextAction: { node in }, linkContextAction: { node in
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else { guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else {
return return
@ -991,7 +983,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
} }
} }
if revoke { if revoke {
revokeLinkDisposable.set((ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId, revokeExisted: true) |> deliverOnMainQueue).start(completed: { revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
updateState { updateState {
$0.withUpdatedRevokingPrivateLink(false) $0.withUpdatedRevokingPrivateLink(false)
} }
@ -1067,7 +1059,6 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
return state.withUpdatedUpdatingAddressName(false) return state.withUpdatedUpdatingAddressName(false)
} }
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: { }, completed: {
updateState { state in updateState { state in
return state.withUpdatedUpdatingAddressName(false) return state.withUpdatedUpdatingAddressName(false)

View File

@ -3,20 +3,24 @@ import Postbox
public final class CachedStickerQueryResult: PostboxCoding { public final class CachedStickerQueryResult: PostboxCoding {
public let items: [TelegramMediaFile] public let items: [TelegramMediaFile]
public let hash: Int32 public let hash: Int32
public let timestamp: Int32
public init(items: [TelegramMediaFile], hash: Int32) { public init(items: [TelegramMediaFile], hash: Int32, timestamp: Int32) {
self.items = items self.items = items
self.hash = hash self.hash = hash
self.timestamp = timestamp
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.items = decoder.decodeObjectArrayForKey("it").map { $0 as! TelegramMediaFile } self.items = decoder.decodeObjectArrayForKey("it").map { $0 as! TelegramMediaFile }
self.hash = decoder.decodeInt32ForKey("h", orElse: 0) self.hash = decoder.decodeInt32ForKey("h", orElse: 0)
self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.items, forKey: "it") encoder.encodeObjectArray(self.items, forKey: "it")
encoder.encodeInt32(self.hash, forKey: "h") encoder.encodeInt32(self.hash, forKey: "h")
encoder.encodeInt32(self.timestamp, forKey: "t")
} }
public static func cacheKey(_ query: String) -> ValueBoxKey { public static func cacheKey(_ query: String) -> ValueBoxKey {

View File

@ -74,6 +74,7 @@ public struct Namespaces {
public static let cachedContextResults: Int8 = 10 public static let cachedContextResults: Int8 = 10
public static let proximityNotificationStoredState: Int8 = 11 public static let proximityNotificationStoredState: Int8 = 11
public static let cachedPeerInvitationImporters: Int8 = 12 public static let cachedPeerInvitationImporters: Int8 = 12
public static let cachedPeerExportedInvitations: Int8 = 13
} }
public struct UnorderedItemList { public struct UnorderedItemList {

View File

@ -11,7 +11,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[231260545] = { return Api.ChatFull.parse_chatFull($0) } dict[-213431562] = { return Api.ChatFull.parse_chatFull($0) }
dict[2055070967] = { return Api.ChatFull.parse_channelFull($0) } dict[2055070967] = { return Api.ChatFull.parse_channelFull($0) }
dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) } dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) }
dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) } dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) }

View File

@ -2206,14 +2206,14 @@ public extension Api {
} }
public enum ChatFull: TypeConstructorDescription { public enum ChatFull: TypeConstructorDescription {
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?) case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?)
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?) case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call): case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
if boxed { if boxed {
buffer.appendInt32(231260545) buffer.appendInt32(-213431562)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
@ -2221,7 +2221,7 @@ public extension Api {
participants.serialize(buffer, true) participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true) notifySettings.serialize(buffer, true)
exportedInvite.serialize(buffer, true) if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count)) buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! { for item in botInfo! {
@ -2300,9 +2300,9 @@ public extension Api {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings _6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
} }
var _7: Api.ExportedChatInvite? var _7: Api.ExportedChatInvite?
if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite _7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} } }
var _8: [Api.BotInfo]? var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self) _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
@ -2321,13 +2321,13 @@ public extension Api {
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = _7 != nil let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7!, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11) return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11)
} }
else { else {
return nil return nil

View File

@ -1061,24 +1061,6 @@ public final class VoiceChatController: ViewController {
strongSelf.accountPeer = accountPeer strongSelf.accountPeer = accountPeer
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set()) strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
if let peer = peerViewMainPeer(view) {
if let channel = peer as? TelegramChannel {
let addressName = channel.addressName ?? ""
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
if addressName.isEmpty {
let _ = ensuredExistingPeerExportedInvitation(account: strongSelf.context.account, peerId: call.peerId).start()
}
}
} else if let group = peer as? TelegramGroup {
switch group.role {
case .creator, .admin:
let _ = ensuredExistingPeerExportedInvitation(account: strongSelf.context.account, peerId: call.peerId).start()
default:
break
}
}
}
strongSelf.didSetDataReady = true strongSelf.didSetDataReady = true
strongSelf.controller?.dataReady.set(true) strongSelf.controller?.dataReady.set(true)
} }

View File

@ -170,6 +170,8 @@ private var declaredEncodables: Void = {
declareEncodable(ValidationMessageAttribute.self, f: { ValidationMessageAttribute(decoder: $0) }) declareEncodable(ValidationMessageAttribute.self, f: { ValidationMessageAttribute(decoder: $0) })
declareEncodable(EmojiSearchQueryMessageAttribute.self, f: { EmojiSearchQueryMessageAttribute(decoder: $0) }) declareEncodable(EmojiSearchQueryMessageAttribute.self, f: { EmojiSearchQueryMessageAttribute(decoder: $0) })
declareEncodable(CachedPeerInvitationImporters.self, f: { CachedPeerInvitationImporters(decoder: $0) }) declareEncodable(CachedPeerInvitationImporters.self, f: { CachedPeerInvitationImporters(decoder: $0) })
declareEncodable(CachedPeerExportedInvitations.self, f: { CachedPeerExportedInvitations(decoder: $0) })
declareEncodable(ExportedInvitation.self, f: { ExportedInvitation(decoder: $0) })
return return
}() }()

View File

@ -6,53 +6,45 @@ import MtProtoKit
import SyncCore import SyncCore
public func ensuredExistingPeerExportedInvitation(account: Account, peerId: PeerId, revokeExisted: Bool = false) -> Signal<ExportedInvitation?, NoError> { public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
let flags: Int32 = (1 << 2) let flags: Int32 = (1 << 2)
if let _ = peer as? TelegramChannel { if let _ = peer as? TelegramChannel {
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, cachedData.exportedInvitation != nil && !revokeExisted { return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: nil, usageLimit: nil))
return .single(cachedData.exportedInvitation) |> retryRequest
} else { |> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: nil, usageLimit: nil)) return account.postbox.transaction { transaction -> ExportedInvitation? in
|> retryRequest if let invitation = ExportedInvitation(apiExportedInvite: result) {
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
return account.postbox.transaction { transaction -> ExportedInvitation? in if let current = current as? CachedChannelData {
if let invitation = ExportedInvitation(apiExportedInvite: result) { return current.withUpdatedExportedInvitation(invitation)
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in } else {
if let current = current as? CachedChannelData { return CachedChannelData().withUpdatedExportedInvitation(invitation)
return current.withUpdatedExportedInvitation(invitation) }
} else { })
return CachedChannelData().withUpdatedExportedInvitation(invitation) return invitation
} } else {
}) return nil
return invitation
} else {
return nil
}
} }
} }
} }
} else if let _ = peer as? TelegramGroup { } else if let _ = peer as? TelegramGroup {
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData, cachedData.exportedInvitation != nil && !revokeExisted { return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: nil, usageLimit: nil))
return .single(cachedData.exportedInvitation) |> retryRequest
} else { |> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: nil, usageLimit: nil)) return account.postbox.transaction { transaction -> ExportedInvitation? in
|> retryRequest if let invitation = ExportedInvitation(apiExportedInvite: result) {
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
return account.postbox.transaction { transaction -> ExportedInvitation? in if let current = current as? CachedGroupData {
if let invitation = ExportedInvitation(apiExportedInvite: result) { return current.withUpdatedExportedInvitation(invitation)
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in } else {
if let current = current as? CachedGroupData { return current
return current.withUpdatedExportedInvitation(invitation) }
} else { })
return CachedGroupData().withUpdatedExportedInvitation(invitation) return invitation
} } else {
}) return nil
return invitation
} else {
return nil
}
} }
} }
} }
@ -65,8 +57,12 @@ public func ensuredExistingPeerExportedInvitation(account: Account, peerId: Peer
} |> switchToLatest } |> switchToLatest
} }
public func createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, NoError> { public enum CreatePeerExportedInvitationError {
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in case generic
}
public func createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 0 var flags: Int32 = 0
if let _ = expireDate { if let _ = expireDate {
@ -76,7 +72,7 @@ public func createPeerExportedInvitation(account: Account, peerId: PeerId, expir
flags |= (1 << 1) flags |= (1 << 1)
} }
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: expireDate, usageLimit: usageLimit)) return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: expireDate, usageLimit: usageLimit))
|> retryRequest |> mapError { _ in return CreatePeerExportedInvitationError.generic }
|> map { result -> ExportedInvitation? in |> map { result -> ExportedInvitation? in
if let invitation = ExportedInvitation(apiExportedInvite: result) { if let invitation = ExportedInvitation(apiExportedInvite: result) {
return invitation return invitation
@ -87,7 +83,9 @@ public func createPeerExportedInvitation(account: Account, peerId: PeerId, expir
} else { } else {
return .complete() return .complete()
} }
} |> switchToLatest }
|> castError(CreatePeerExportedInvitationError.self)
|> switchToLatest
} }
public struct ExportedInvitations : Equatable { public struct ExportedInvitations : Equatable {
@ -266,9 +264,10 @@ final class CachedPeerExportedInvitations: PostboxCoding {
let canLoadMore: Bool let canLoadMore: Bool
let count: Int32 let count: Int32
public static func key(peerId: PeerId) -> ValueBoxKey { public static func key(peerId: PeerId, revoked: Bool) -> ValueBoxKey {
let key = ValueBoxKey(length: 8 + 4) let key = ValueBoxKey(length: 8 + 4)
key.setInt64(0, value: peerId.toInt64()) key.setInt64(0, value: peerId.toInt64())
key.setInt32(8, value: revoked ? 1 : 0)
return key return key
} }
@ -295,26 +294,32 @@ private final class PeerExportedInvitationsContextImpl {
private let queue: Queue private let queue: Queue
private let account: Account private let account: Account
private let peerId: PeerId private let peerId: PeerId
private let revoked: Bool
private var forceUpdate: Bool
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
private let updateDisposable = MetaDisposable()
private var isLoadingMore: Bool = false private var isLoadingMore: Bool = false
private var hasLoadedOnce: Bool = false private var hasLoadedOnce: Bool = false
private var canLoadMore: Bool = true private var canLoadMore: Bool = true
private var loadedFromCache: Bool = false
private var results: [ExportedInvitation] = [] private var results: [ExportedInvitation] = []
private var count: Int32 private var count: Int32
private var populateCache: Bool = true private var populateCache: Bool = true
let state = Promise<PeerExportedInvitationsState>() let state = Promise<PeerExportedInvitationsState>()
init(queue: Queue, account: Account, peerId: PeerId) { init(queue: Queue, account: Account, peerId: PeerId, revoked: Bool, forceUpdate: Bool) {
self.queue = queue self.queue = queue
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.revoked = revoked
self.forceUpdate = forceUpdate
self.count = 0 self.count = 0
self.isLoadingMore = true self.isLoadingMore = true
self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in self.disposable.set((account.postbox.transaction { transaction -> CachedPeerExportedInvitations? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerExportedInvitations.key(peerId: peerId))) as? CachedPeerExportedInvitations return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked))) as? CachedPeerExportedInvitations
} }
|> deliverOn(self.queue)).start(next: { [weak self] cachedResult in |> deliverOn(self.queue)).start(next: { [weak self] cachedResult in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -326,6 +331,7 @@ private final class PeerExportedInvitationsContextImpl {
strongSelf.count = cachedResult.count strongSelf.count = cachedResult.count
strongSelf.hasLoadedOnce = true strongSelf.hasLoadedOnce = true
strongSelf.canLoadMore = cachedResult.canLoadMore strongSelf.canLoadMore = cachedResult.canLoadMore
strongSelf.loadedFromCache = true
} }
strongSelf.loadMore() strongSelf.loadMore()
})) }))
@ -335,6 +341,12 @@ private final class PeerExportedInvitationsContextImpl {
deinit { deinit {
self.disposable.dispose() self.disposable.dispose()
self.updateDisposable.dispose()
}
func reload() {
self.forceUpdate = true
self.loadMore()
} }
func loadMore() { func loadMore() {
@ -344,16 +356,33 @@ private final class PeerExportedInvitationsContextImpl {
self.isLoadingMore = true self.isLoadingMore = true
let account = self.account let account = self.account
let peerId = self.peerId let peerId = self.peerId
let lastResult = self.results.last let revoked = self.revoked
var lastResult = self.results.last
if self.forceUpdate {
self.forceUpdate = false
lastResult = nil
}
if !self.forceUpdate && self.loadedFromCache {
self.populateCache = false
self.loadedFromCache = false
}
let populateCache = self.populateCache let populateCache = self.populateCache
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
} }
|> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in |> mapToSignal { inputPeer -> Signal<([ExportedInvitation], Int32), NoError> in
if let inputPeer = inputPeer { if let inputPeer = inputPeer {
let offsetLink = lastResult?.link let offsetLink = lastResult?.link
var flags: Int32 = 0
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: 0, peer: inputPeer, adminId: nil, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100)) if let _ = offsetLink {
flags |= (1 << 2)
}
if revoked {
flags |= (1 << 3)
}
let signal = account.network.request(Api.functions.messages.getExportedChatInvites(flags: flags, peer: inputPeer, adminId: nil, offsetLink: offsetLink, limit: lastResult == nil ? 50 : 100))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in |> `catch` { _ -> Signal<Api.messages.ExportedChatInvites?, NoError> in
return .single(nil) return .single(nil)
@ -374,7 +403,7 @@ private final class PeerExportedInvitationsContextImpl {
}) })
let invitations: [ExportedInvitation] = invites.compactMap { ExportedInvitation(apiExportedInvite: $0) } let invitations: [ExportedInvitation] = invites.compactMap { ExportedInvitation(apiExportedInvite: $0) }
if populateCache { if populateCache {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerInvitationImporters, key: CachedPeerExportedInvitations.key(peerId: peerId)), entry: CachedPeerExportedInvitations(invitations: invitations, canLoadMore: count >= 50, count: count), collectionSpec: cachedPeerExportedInvitationsCollectionSpec) transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked)), entry: CachedPeerExportedInvitations(invitations: invitations, canLoadMore: count >= 50, count: count), collectionSpec: cachedPeerExportedInvitationsCollectionSpec)
} }
return (invitations, count) return (invitations, count)
} }
@ -409,10 +438,63 @@ private final class PeerExportedInvitationsContextImpl {
strongSelf.count = Int32(strongSelf.results.count) strongSelf.count = Int32(strongSelf.results.count)
} }
strongSelf.updateState() strongSelf.updateState()
if strongSelf.forceUpdate {
strongSelf.loadMore()
}
})) }))
self.updateState() self.updateState()
} }
public func add(_ invite: ExportedInvitation) {
var results = self.results
results.removeAll(where: { $0.link == invite.link})
results.insert(invite, at: 0)
self.results = results
self.updateState()
self.updateCache()
}
public func update(_ invite: ExportedInvitation) {
var results = self.results
if let index = self.results.firstIndex(where: { $0.link == invite.link }) {
results[index] = invite
}
self.results = results
self.updateState()
self.updateCache()
}
public func remove(_ invite: ExportedInvitation) {
var results = self.results
results.removeAll(where: { $0.link == invite.link})
self.results = results
self.updateState()
self.updateCache()
}
public func clear() {
self.results = []
self.count = 0
self.updateState()
self.updateCache()
}
private func updateCache() {
guard self.hasLoadedOnce && !self.isLoadingMore else {
return
}
let peerId = self.peerId
let revoked = self.revoked
let invitations = Array(self.results.prefix(50))
let canLoadMore = self.canLoadMore
let count = self.count
self.updateDisposable.set(self.account.postbox.transaction({ transaction in
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerExportedInvitations, key: CachedPeerExportedInvitations.key(peerId: peerId, revoked: revoked)), entry: CachedPeerExportedInvitations(invitations: invitations, canLoadMore: canLoadMore, count: count), collectionSpec: cachedPeerExportedInvitationsCollectionSpec)
}).start())
}
private func updateState() { private func updateState() {
self.state.set(.single(PeerExportedInvitationsState(invitations: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count))) self.state.set(.single(PeerExportedInvitationsState(invitations: self.results, isLoadingMore: self.isLoadingMore, hasLoadedOnce: self.hasLoadedOnce, canLoadMore: self.canLoadMore, count: self.count)))
} }
@ -434,18 +516,48 @@ public final class PeerExportedInvitationsContext {
} }
} }
public init(account: Account, peerId: PeerId, invite: ExportedInvitation) { public init(account: Account, peerId: PeerId, revoked: Bool, forceUpdate: Bool) {
let queue = self.queue let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: { self.impl = QueueLocalObject(queue: queue, generate: {
return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId) return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, revoked: revoked, forceUpdate: forceUpdate)
}) })
} }
public func reload() {
self.impl.with { impl in
impl.reload()
}
}
public func loadMore() { public func loadMore() {
self.impl.with { impl in self.impl.with { impl in
impl.loadMore() impl.loadMore()
} }
} }
public func add(_ invite: ExportedInvitation) {
self.impl.with { impl in
impl.add(invite)
}
}
public func update(_ invite: ExportedInvitation) {
self.impl.with { impl in
impl.update(invite)
}
}
public func remove(_ invite: ExportedInvitation) {
self.impl.with { impl in
impl.remove(invite)
}
}
public func clear() {
self.impl.with { impl in
impl.clear()
}
}
} }
@ -529,6 +641,7 @@ private final class PeerInvitationImportersContextImpl {
private var isLoadingMore: Bool = false private var isLoadingMore: Bool = false
private var hasLoadedOnce: Bool = false private var hasLoadedOnce: Bool = false
private var canLoadMore: Bool = true private var canLoadMore: Bool = true
private var loadedFromCache = false
private var results: [PeerInvitationImportersState.Importer] = [] private var results: [PeerInvitationImportersState.Importer] = []
private var count: Int32 private var count: Int32
private var populateCache: Bool = true private var populateCache: Bool = true
@ -570,6 +683,7 @@ private final class PeerInvitationImportersContextImpl {
strongSelf.results = cachedPeers strongSelf.results = cachedPeers
strongSelf.hasLoadedOnce = true strongSelf.hasLoadedOnce = true
strongSelf.canLoadMore = canLoadMore strongSelf.canLoadMore = canLoadMore
strongSelf.loadedFromCache = true
} }
strongSelf.loadMore() strongSelf.loadMore()
})) }))
@ -589,15 +703,21 @@ private final class PeerInvitationImportersContextImpl {
let account = self.account let account = self.account
let peerId = self.peerId let peerId = self.peerId
let link = self.link let link = self.link
let lastResult = self.results.last
let populateCache = self.populateCache let populateCache = self.populateCache
var lastResult = self.results.last
if self.loadedFromCache {
self.loadedFromCache = false
lastResult = nil
}
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
} }
|> mapToSignal { inputPeer -> Signal<([PeerInvitationImportersState.Importer], Int32), NoError> in |> mapToSignal { inputPeer -> Signal<([PeerInvitationImportersState.Importer], Int32), NoError> in
if let inputPeer = inputPeer { if let inputPeer = inputPeer {
let offsetUser = lastResult?.peer.peer.flatMap { apiInputUser($0) } ?? .inputUserEmpty let offsetUser = lastResult?.peer.peer.flatMap { apiInputUser($0) } ?? .inputUserEmpty
let offsetDate = populateCache ? 0 : lastResult?.date ?? 0 let offsetDate = lastResult?.date ?? 0
let signal = account.network.request(Api.functions.messages.getChatInviteImporters(peer: inputPeer, link: link, offsetDate: offsetDate, offsetUser: offsetUser, limit: lastResult == nil ? 10 : 50)) let signal = account.network.request(Api.functions.messages.getChatInviteImporters(peer: inputPeer, link: link, offsetDate: offsetDate, offsetUser: offsetUser, limit: lastResult == nil ? 10 : 50))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ChatInviteImporters?, NoError> in |> `catch` { _ -> Signal<Api.messages.ChatInviteImporters?, NoError> in

View File

@ -5,6 +5,26 @@ import SwiftSignalKit
import SyncCore import SyncCore
private struct SearchStickersConfiguration {
static var defaultValue: SearchStickersConfiguration {
return SearchStickersConfiguration(cacheTimeout: 86400)
}
public let cacheTimeout: Int32
fileprivate init(cacheTimeout: Int32) {
self.cacheTimeout = cacheTimeout
}
static func with(appConfiguration: AppConfiguration) -> SearchStickersConfiguration {
if let data = appConfiguration.data, let value = data["stickers_emoji_cache_time"] as? Int32 {
return SearchStickersConfiguration(cacheTimeout: value)
} else {
return .defaultValue
}
}
}
public final class FoundStickerItem: Equatable { public final class FoundStickerItem: Equatable {
public let file: TelegramMediaFile public let file: TelegramMediaFile
public let stringRepresentations: [String] public let stringRepresentations: [String]
@ -140,7 +160,15 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
result.append(contentsOf: installedItems) result.append(contentsOf: installedItems)
} }
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query))) as? CachedStickerQueryResult var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query))) as? CachedStickerQueryResult
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue
let searchStickersConfiguration = SearchStickersConfiguration.with(appConfiguration: appConfiguration)
if let currentCached = cached, currentTime > currentCached.timestamp + searchStickersConfiguration.cacheTimeout {
cached = nil
}
return (result, cached) return (result, cached)
} |> mapToSignal { localItems, cached -> Signal<[FoundStickerItem], NoError> in } |> mapToSignal { localItems, cached -> Signal<[FoundStickerItem], NoError> in
@ -199,7 +227,8 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
result.append(contentsOf: animatedItems) result.append(contentsOf: animatedItems)
result.append(contentsOf: items) result.append(contentsOf: items)
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query)), entry: CachedStickerQueryResult(items: files, hash: hash), collectionSpec: collectionSpec) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query)), entry: CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime), collectionSpec: collectionSpec)
return result return result
case .stickersNotModified: case .stickersNotModified:

View File

@ -6,6 +6,7 @@ import SyncCore
public enum ServerProvidedSuggestion: String { public enum ServerProvidedSuggestion: String {
case autoarchivePopular = "AUTOARCHIVE_POPULAR" case autoarchivePopular = "AUTOARCHIVE_POPULAR"
case newcomerTicks = "NEWCOMER_TICKS"
} }
public func getServerProvidedSuggestions(postbox: Postbox) -> Signal<[ServerProvidedSuggestion], NoError> { public func getServerProvidedSuggestions(postbox: Postbox) -> Signal<[ServerProvidedSuggestion], NoError> {
@ -22,12 +23,7 @@ public func getServerProvidedSuggestions(postbox: Postbox) -> Signal<[ServerProv
return [] return []
} }
return list.compactMap { item -> ServerProvidedSuggestion? in return list.compactMap { item -> ServerProvidedSuggestion? in
switch item { return ServerProvidedSuggestion(rawValue: item)
case "AUTOARCHIVE_POPULAR":
return .autoarchivePopular
default:
return nil
}
} }
} }
|> distinctUntilChanged |> distinctUntilChanged

View File

@ -205,10 +205,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
let peerStatusSettings = PeerStatusSettings(apiSettings: userFull.settings) let peerStatusSettings = PeerStatusSettings(apiSettings: userFull.settings)
var hasScheduledMessages = false let hasScheduledMessages = (userFull.flags & 1 << 12) != 0
if (userFull.flags & 1 << 12) != 0 {
hasScheduledMessages = true
}
return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages) return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
} }
@ -257,7 +254,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
let photo: TelegramMediaImage? = chatFull.chatPhoto.flatMap(telegramMediaImageFromApiPhoto) let photo: TelegramMediaImage? = chatFull.chatPhoto.flatMap(telegramMediaImageFromApiPhoto)
let exportedInvitation = ExportedInvitation(apiExportedInvite: chatFull.exportedInvite) let exportedInvitation = chatFull.exportedInvite.flatMap { ExportedInvitation(apiExportedInvite: $0) }
let pinnedMessageId = chatFull.pinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }) let pinnedMessageId = chatFull.pinnedMsgId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) })
var peers: [Peer] = [] var peers: [Peer] = []

View File

@ -305,6 +305,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var applicationInForegroundDisposable: Disposable? private var applicationInForegroundDisposable: Disposable?
private var applicationInFocusDisposable: Disposable? private var applicationInFocusDisposable: Disposable?
private let checksTooltipDisposable = MetaDisposable()
private var shouldDisplayChecksTooltip = false
private var checkedPeerChatServiceActions = false private var checkedPeerChatServiceActions = false
private var willAppear = false private var willAppear = false
@ -3424,6 +3427,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.peekTimerDisposable.dispose() self.peekTimerDisposable.dispose()
self.hasActiveGroupCallDisposable?.dispose() self.hasActiveGroupCallDisposable?.dispose()
self.createVoiceChatDisposable.dispose() self.createVoiceChatDisposable.dispose()
self.checksTooltipDisposable.dispose()
} }
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) { public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
@ -4761,7 +4765,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
let editingMessage = strongSelf.editingMessage
let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText)) let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText))
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
var entitiesAttribute: TextEntitiesMessageAttribute? var entitiesAttribute: TextEntitiesMessageAttribute?
@ -6236,7 +6239,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.openScheduledMessages() strongSelf.openScheduledMessages()
} }
// strongSelf.displayChecksTooltip() if strongSelf.shouldDisplayChecksTooltip {
strongSelf.displayChecksTooltip()
strongSelf.shouldDisplayChecksTooltip = false
strongSelf.checksTooltipDisposable.set(dismissServerProvidedSuggestion(account: strongSelf.context.account, suggestion: .newcomerTicks).start())
}
} }
})) }))
@ -6482,7 +6489,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return false return false
} }
if !strongSelf.context.sharedContext.currentMediaInputSettings.with { $0.enableRaiseToSpeak } { if !strongSelf.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) {
return false return false
} }
@ -6707,6 +6714,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})) }))
} }
self.checksTooltipDisposable.set((getServerProvidedSuggestions(postbox: self.context.account.postbox)
|> deliverOnMainQueue).start(next: { [weak self] values in
guard let strongSelf = self else {
return
}
if !values.contains(.newcomerTicks) {
return
}
strongSelf.shouldDisplayChecksTooltip = true
}))
if self.scheduledActivateInput { if self.scheduledActivateInput {
self.scheduledActivateInput = false self.scheduledActivateInput = false
@ -7481,7 +7499,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> deliverOnMainQueue).start(next: { [weak self, weak controller] result in |> deliverOnMainQueue).start(next: { [weak self, weak controller] result in
controller?.dismiss() controller?.dismiss()
guard let strongSelf = self, case let .result(stats) = result, var categories = stats.media[peer.id] else { guard let strongSelf = self, case let .result(stats) = result, let categories = stats.media[peer.id] else {
return return
} }
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
@ -7682,16 +7700,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
private func editMessageMediaWithMessages(_ messages: [EnqueueMessage]) { private func editMessageMediaWithMessages(_ messages: [EnqueueMessage]) {
if let message = messages.first, case let .message(desc) = message, let mediaReference = desc.mediaReference { if let message = messages.first, case let .message(text, _, maybeMediaReference, _, _) = message, let mediaReference = maybeMediaReference {
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
var state = state var state = state
if let editMessageState = state.editMessageState, case let .media(options) = editMessageState.content, !options.isEmpty { if let editMessageState = state.editMessageState, case let .media(options) = editMessageState.content, !options.isEmpty {
state = state.updatedEditMessageState(ChatEditInterfaceMessageState(content: editMessageState.content, mediaReference: mediaReference)) state = state.updatedEditMessageState(ChatEditInterfaceMessageState(content: editMessageState.content, mediaReference: mediaReference))
} }
if !desc.text.isEmpty { if !text.isEmpty {
state = state.updatedInterfaceState { state in state = state.updatedInterfaceState { state in
if let editMessage = state.editMessage { if let editMessage = state.editMessage {
return state.withUpdatedEditMessage(editMessage.withUpdatedInputState(ChatTextInputState(inputText: NSAttributedString(string: desc.text)))) return state.withUpdatedEditMessage(editMessage.withUpdatedInputState(ChatTextInputState(inputText: NSAttributedString(string: text))))
} }
return state return state
} }
@ -9338,7 +9356,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let complete = results.completed
var navigateIndex: MessageIndex? var navigateIndex: MessageIndex?
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in
if let data = current.search { if let data = current.search {
@ -10111,8 +10128,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
case let .withBotStartPayload(botStart): case let .withBotStartPayload(botStart):
self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart))
default:
break
} }
} }
} else { } else {
@ -10532,7 +10547,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private func openUrl(_ url: String, concealed: Bool, message: Message? = nil) { private func openUrl(_ url: String, concealed: Bool, message: Message? = nil) {
self.commitPurposefulAction() self.commitPurposefulAction()
self.presentVoiceMessageDiscardAlert(action: { let _ = self.presentVoiceMessageDiscardAlert(action: {
if self.context.sharedContext.immediateExperimentalUISettings.playlistPlayback { if self.context.sharedContext.immediateExperimentalUISettings.playlistPlayback {
if url.hasSuffix(".m3u8") { if url.hasSuffix(".m3u8") {
let navigationController = self.navigationController as? NavigationController let navigationController = self.navigationController as? NavigationController

View File

@ -159,6 +159,7 @@ final class PeerInfoScreenData {
let members: PeerInfoMembersData? let members: PeerInfoMembersData?
let encryptionKeyFingerprint: SecretChatKeyFingerprint? let encryptionKeyFingerprint: SecretChatKeyFingerprint?
let globalSettings: TelegramGlobalSettings? let globalSettings: TelegramGlobalSettings?
let invitations: PeerExportedInvitationsState?
init( init(
peer: Peer?, peer: Peer?,
@ -172,7 +173,8 @@ final class PeerInfoScreenData {
linkedDiscussionPeer: Peer?, linkedDiscussionPeer: Peer?,
members: PeerInfoMembersData?, members: PeerInfoMembersData?,
encryptionKeyFingerprint: SecretChatKeyFingerprint?, encryptionKeyFingerprint: SecretChatKeyFingerprint?,
globalSettings: TelegramGlobalSettings? globalSettings: TelegramGlobalSettings?,
invitations: PeerExportedInvitationsState?
) { ) {
self.peer = peer self.peer = peer
self.cachedData = cachedData self.cachedData = cachedData
@ -186,6 +188,7 @@ final class PeerInfoScreenData {
self.members = members self.members = members
self.encryptionKeyFingerprint = encryptionKeyFingerprint self.encryptionKeyFingerprint = encryptionKeyFingerprint
self.globalSettings = globalSettings self.globalSettings = globalSettings
self.invitations = invitations
} }
} }
@ -442,7 +445,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: PeerId, account
linkedDiscussionPeer: nil, linkedDiscussionPeer: nil,
members: nil, members: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: globalSettings globalSettings: globalSettings,
invitations: nil
) )
} }
} }
@ -464,7 +468,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: nil, linkedDiscussionPeer: nil,
members: nil, members: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: nil globalSettings: nil,
invitations: nil
)) ))
case let .user(userPeerId, secretChatId, kind): case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext? let groupsInCommon: GroupsInCommonContext?
@ -603,7 +608,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: nil, linkedDiscussionPeer: nil,
members: nil, members: nil,
encryptionKeyFingerprint: encryptionKeyFingerprint, encryptionKeyFingerprint: encryptionKeyFingerprint,
globalSettings: nil globalSettings: nil,
invitations: nil
) )
} }
case .channel: case .channel:
@ -623,13 +629,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications])) let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
var combinedKeys: [PostboxViewKey] = [] var combinedKeys: [PostboxViewKey] = []
combinedKeys.append(globalNotificationsKey) combinedKeys.append(globalNotificationsKey)
let invitationsContextPromise = Promise<PeerExportedInvitationsContext?>(nil)
let invitationsStatePromise = Promise<PeerExportedInvitationsState?>(nil)
return combineLatest( return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true), context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId), peerInfoAvailableMediaPanes(context: context, peerId: peerId),
context.account.postbox.combinedView(keys: combinedKeys), context.account.postbox.combinedView(keys: combinedKeys),
status status,
invitationsContextPromise.get(),
invitationsStatePromise.get()
) )
|> map { peerView, availablePanes, combinedView, status -> PeerInfoScreenData in |> map { peerView, availablePanes, combinedView, status, currentInvitationsContext, invitations -> PeerInfoScreenData in
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView { if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
@ -642,6 +654,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
discussionPeer = peer discussionPeer = peer
} }
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || ((channel.adminRights != nil && channel.hasPermission(.pinMessages)) && cachedData.flags.contains(.canChangeUsername)), currentInvitationsContext == nil {
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
}
return PeerInfoScreenData( return PeerInfoScreenData(
peer: peerView.peers[peerId], peer: peerView.peers[peerId],
cachedData: peerView.cachedData, cachedData: peerView.cachedData,
@ -654,7 +672,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: discussionPeer, linkedDiscussionPeer: discussionPeer,
members: nil, members: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: nil globalSettings: nil,
invitations: invitations
) )
} }
case let .group(groupId): case let .group(groupId):
@ -751,14 +770,20 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications])) let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
var combinedKeys: [PostboxViewKey] = [] var combinedKeys: [PostboxViewKey] = []
combinedKeys.append(globalNotificationsKey) combinedKeys.append(globalNotificationsKey)
let invitationsContextPromise = Promise<PeerExportedInvitationsContext?>(nil)
let invitationsStatePromise = Promise<PeerExportedInvitationsState?>(nil)
return combineLatest(queue: .mainQueue(), return combineLatest(queue: .mainQueue(),
context.account.viewTracker.peerView(groupId, updateData: true), context.account.viewTracker.peerView(groupId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: groupId), peerInfoAvailableMediaPanes(context: context, peerId: groupId),
context.account.postbox.combinedView(keys: combinedKeys), context.account.postbox.combinedView(keys: combinedKeys),
status, status,
membersData membersData,
invitationsContextPromise.get(),
invitationsStatePromise.get()
) )
|> map { peerView, availablePanes, combinedView, status, membersData -> PeerInfoScreenData in |> map { peerView, availablePanes, combinedView, status, membersData, currentInvitationsContext, invitations -> PeerInfoScreenData in
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView { if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
@ -780,6 +805,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} }
} }
if let group = peerViewMainPeer(peerView) as? TelegramGroup, case .creator = group.role, currentInvitationsContext == nil {
let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
}
return PeerInfoScreenData( return PeerInfoScreenData(
peer: peerView.peers[groupId], peer: peerView.peers[groupId],
cachedData: peerView.cachedData, cachedData: peerView.cachedData,
@ -792,7 +823,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: discussionPeer, linkedDiscussionPeer: discussionPeer,
members: membersData, members: membersData,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: nil globalSettings: nil,
invitations: invitations
) )
} }
} }

View File

@ -1255,7 +1255,14 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
interaction.editingOpenPublicLinkSetup() interaction.editingOpenPublicLinkSetup()
})) }))
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(""), text: presentationData.strings.GroupInfo_InviteLinks, action: { let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
} else {
invitesText = ""
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup() interaction.editingOpenInviteLinksSetup()
})) }))
@ -1330,7 +1337,14 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
} }
} }
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(""), text: presentationData.strings.GroupInfo_InviteLinks, action: { let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
} else {
invitesText = ""
}
items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, action: {
interaction.editingOpenInviteLinksSetup() interaction.editingOpenInviteLinksSetup()
})) }))
@ -4544,6 +4558,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.view.endEditing(true)
let mode: ChannelVisibilityControllerMode let mode: ChannelVisibilityControllerMode
if groupPeer.addressName != nil { if groupPeer.addressName != nil {
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: .generic, upgradedToSupergroup: { _, f in f() }, onDismissRemoveController: contactsController) let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: .generic, upgradedToSupergroup: { _, f in f() }, onDismissRemoveController: contactsController)

View File

@ -91,8 +91,8 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
} }
let resolvedWallpaper: Signal<TelegramWallpaper?, NoError> let resolvedWallpaper: Signal<TelegramWallpaper?, NoError>
if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { if case let .file(id, _, _, _, _, _, slug, _, settings) = presentationTheme.chat.defaultWallpaper, id == 0 {
resolvedWallpaper = cachedWallpaper(account: account, slug: file.slug, settings: file.settings) resolvedWallpaper = cachedWallpaper(account: account, slug: slug, settings: settings)
|> map { wallpaper in |> map { wallpaper in
return wallpaper?.wallpaper return wallpaper?.wallpaper
} }
@ -102,15 +102,15 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager {
return resolvedWallpaper return resolvedWallpaper
|> mapToSignal { wallpaper -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in |> mapToSignal { wallpaper -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in
if let wallpaper = wallpaper, case let .file(file) = wallpaper { if let wallpaper = wallpaper, case let .file(_, _, _, _, _, _, slug, file, _) = wallpaper {
var convertedRepresentations: [ImageRepresentationWithReference] = [] var convertedRepresentations: [ImageRepresentationWithReference] = []
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource, progressiveSizes: []), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource))) convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.resource, progressiveSizes: []), reference: .wallpaper(wallpaper: .slug(slug), resource: file.resource)))
return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false) return wallpaperDatas(account: account, accountManager: accountManager, fileReference: .standalone(media: file), representations: convertedRepresentations, alwaysShowThumbnailFirst: false, thumbnail: false, onlyFullSize: true, autoFetchFullSize: true, synchronousLoad: false)
|> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in |> mapToSignal { _, fullSizeData, complete -> Signal<(PresentationThemeReference, PresentationTheme?), NoError> in
guard complete, let fullSizeData = fullSizeData else { guard complete, let fullSizeData = fullSizeData else {
return .complete() return .complete()
} }
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData, synchronous: true) accountManager.mediaBox.storeResourceData(file.resource.id, data: fullSizeData, synchronous: true)
return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme)) return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme))
} }
} else { } else {