Invite Links Fixes

This commit is contained in:
Ilya Laktyushin 2021-01-19 10:52:48 +03:00
parent 426e8724dc
commit 935f931034
27 changed files with 4471 additions and 4172 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 %@";

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

@ -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
} }
@ -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()
@ -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(invite == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Save), 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

@ -309,19 +309,14 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
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
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
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, importersContext: nil) let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: nil, importersContext: nil)
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, copyLink: { invite in }, copyLink: { invite in
UIPasteboard.general.string = invite.link UIPasteboard.general.string = invite.link
@ -390,7 +385,9 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId) ->
updatedState.revokingPrivateLink = false updatedState.revokingPrivateLink = false
return updatedState return updatedState
} }
invitesPromise.set(peerExportedInvitations(account: context.account, peerId: peerId, revoked: false))
invitesContext.reload()
revokedInvitesContext.reload()
})) }))
} }
}) })
@ -403,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
@ -446,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)
@ -466,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() })])
@ -498,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() })])
@ -521,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() })])
@ -552,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

@ -118,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
@ -192,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)
} }
} }
} }
@ -217,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)
@ -539,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)
} }

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

@ -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

@ -79,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)
@ -184,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
@ -292,8 +292,8 @@ 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, copyAction: { return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
if let invite = invite { if let invite = invite {
arguments.copyLink(invite) arguments.copyLink(invite)
} }
@ -602,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 {
@ -621,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:
@ -720,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:
@ -1059,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

@ -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

@ -57,8 +57,12 @@ public func revokePersistentPeerExportedInvitation(account: Account, peerId: Pee
} |> 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 {
@ -68,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
@ -79,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 {
@ -258,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
} }
@ -287,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 {
@ -318,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()
})) }))
@ -327,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() {
@ -336,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)
@ -366,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)
} }
@ -401,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)))
} }
@ -426,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()
}
}
} }
@ -521,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
@ -562,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()
})) }))
@ -581,8 +703,14 @@ 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)
} }

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)
} }

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 {