diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 5d23086978..7f9d30e05b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8660,7 +8660,7 @@ Sorry for the inconvenience."; "RequestPeer.Requirement.Group.Rights.Delete" = "delete messages"; "RequestPeer.Requirement.Group.Rights.Edit" = "edit messages"; "RequestPeer.Requirement.Group.Rights.Ban" = "ban users"; -"RequestPeer.Requirement.Group.Rights.Invite" = "invite users via link"; +"RequestPeer.Requirement.Group.Rights.Invite" = "add members"; "RequestPeer.Requirement.Group.Rights.Pin" = "pin messages"; "RequestPeer.Requirement.Group.Rights.Topics" = "manage topics"; "RequestPeer.Requirement.Group.Rights.VideoChats" = "manage video chats"; diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index f3ce8a1dd7..a674e5befe 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -461,7 +461,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } - public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { + public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { switch self { case let .topic(peer, threadInfo, _, theme, strings, expandType): let actionTitle: String? @@ -621,7 +621,11 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if filter.contains(.onlyGroups) { headerType = .chats } else { - headerType = .localPeers + if let _ = requestPeerType { + headerType = .chats + } else { + headerType = .localPeers + } } header = ChatListSearchItemHeader(type: headerType, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : { toggleExpandLocalResults() @@ -826,12 +830,12 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) } return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) } @@ -2269,7 +2273,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let animated = selectionChanged || expandGlobalSearchChanged let firstTime = previousEntries == nil - var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags != nil, isEmpty: !isSearching && (entriesAndFlags?.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, location: location, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture, location in + var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags != nil, isEmpty: !isSearching && (entriesAndFlags?.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, requestPeerType: requestPeerType, location: location, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture, location in interaction.peerContextAction?(message, node, rect, gesture, location) }, toggleExpandLocalResults: { guard let strongSelf = self else { diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index dae69edd03..b03a9f40f0 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -113,7 +113,7 @@ public final class HashtagSearchController: TelegramBaseController { }) let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], location: .chatList(groupId: .root), key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], requestPeerType: nil, location: .chatList(groupId: .root), key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { }, toggleExpandGlobalResults: { }, searchPeer: { _ in }, searchQuery: "", searchOptions: nil, messageContextAction: nil, openClearRecentlyDownloaded: {}, toggleAllPaused: {}) diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index b802d9ab6d..68eddf4b0a 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -17,6 +17,7 @@ import WebSearchUI import PeerInfoUI import MapResourceToAvatarSizes import LegacyMediaPickerUI +import TextFormat private struct CreateChannelArguments { let context: AccountContext @@ -26,11 +27,14 @@ private struct CreateChannelArguments { let done: () -> Void let changeProfilePhoto: () -> Void let focusOnDescription: () -> Void + let updatePublicLinkText: (String) -> Void + let openAuction: (String) -> Void } private enum CreateChannelSection: Int32 { case info case description + case username } private enum CreateChannelEntryTag: ItemListItemTag { @@ -62,16 +66,23 @@ private enum CreateChannelEntryTag: ItemListItemTag { private enum CreateChannelEntry: ItemListNodeEntry { case channelInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?) case setProfilePhoto(PresentationTheme, String) - + case descriptionSetup(PresentationTheme, String, String) case descriptionInfo(PresentationTheme, String) + case usernameHeader(PresentationTheme, String) + case username(PresentationTheme, String, String) + case usernameStatus(PresentationTheme, String, AddressNameValidationStatus, String, String) + case usernameInfo(PresentationTheme, String) + var section: ItemListSectionId { switch self { case .channelInfo, .setProfilePhoto: return CreateChannelSection.info.rawValue case .descriptionSetup, .descriptionInfo: return CreateChannelSection.description.rawValue + case .usernameHeader, .username, .usernameStatus, .usernameInfo: + return CreateChannelSection.username.rawValue } } @@ -85,6 +96,14 @@ private enum CreateChannelEntry: ItemListNodeEntry { return 2 case .descriptionInfo: return 3 + case .usernameHeader: + return 4 + case .username: + return 5 + case .usernameStatus: + return 6 + case .usernameInfo: + return 7 } } @@ -136,6 +155,30 @@ private enum CreateChannelEntry: ItemListNodeEntry { } else { return false } + case let .usernameHeader(lhsTheme, lhsText): + if case let .usernameHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .username(lhsTheme, lhsText, lhsValue): + if case let .username(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .usernameStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername): + if case let .usernameStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername { + return true + } else { + return false + } + case let .usernameInfo(lhsTheme, lhsText): + if case let .usernameInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } } } @@ -164,6 +207,37 @@ private enum CreateChannelEntry: ItemListNodeEntry { }, tag: CreateChannelEntryTag.description) case let .descriptionInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .usernameHeader(_, title): + return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) + case let .username(theme, placeholder, text): + return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in + arguments.updatePublicLinkText(updatedText) + }, action: { + }) + case let .usernameStatus(_, _, status, text, username): + var displayActivity = false + let textColor: ItemListActivityTextItem.TextColor + switch status { + case .invalidFormat: + textColor = .destructive + case let .availability(availability): + switch availability { + case .available: + textColor = .constructive + case .purchaseAvailable: + textColor = .generic + case .invalid, .taken: + textColor = .destructive + } + case .checking: + textColor = .generic + displayActivity = true + } + return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in + arguments.openAuction(username) + }, sectionId: self.section) + case let .usernameInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) } } } @@ -173,6 +247,8 @@ private struct CreateChannelState: Equatable { var editingName: ItemListAvatarAndNameInfoItemName var editingDescriptionText: String var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar? + var editingPublicLinkText: String? + var addressNameValidationStatus: AddressNameValidationStatus? static func ==(lhs: CreateChannelState, rhs: CreateChannelState) -> Bool { if lhs.creating != rhs.creating { @@ -187,11 +263,17 @@ private struct CreateChannelState: Equatable { if lhs.avatar != rhs.avatar { return false } + if lhs.editingPublicLinkText != rhs.editingPublicLinkText { + return false + } + if lhs.addressNameValidationStatus != rhs.addressNameValidationStatus { + return false + } return true } } -private func CreateChannelEntries(presentationData: PresentationData, state: CreateChannelState) -> [CreateChannelEntry] { +private func CreateChannelEntries(presentationData: PresentationData, state: CreateChannelState, requestPeer: ReplyMarkupButtonRequestPeerType.Channel?) -> [CreateChannelEntry] { var entries: [CreateChannelEntry] = [] let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil) @@ -203,6 +285,55 @@ private func CreateChannelEntries(presentationData: PresentationData, state: Cre entries.append(.descriptionSetup(presentationData.theme, presentationData.strings.Channel_Edit_AboutItem, state.editingDescriptionText)) entries.append(.descriptionInfo(presentationData.theme, presentationData.strings.Channel_About_Help)) + if let requestPeer { + if let hasUsername = requestPeer.hasUsername, hasUsername { + let currentUsername = state.editingPublicLinkText ?? "" + entries.append(.usernameHeader(presentationData.theme, presentationData.strings.CreateGroup_PublicLinkTitle.uppercased())) + entries.append(.username(presentationData.theme, presentationData.strings.Group_PublicLink_Placeholder, currentUsername)) + + if let status = state.addressNameValidationStatus { + let statusText: String + switch status { + case let .invalidFormat(error): + switch error { + case .startsWithDigit: + statusText = presentationData.strings.Username_InvalidStartsWithNumber + case .startsWithUnderscore: + statusText = presentationData.strings.Username_InvalidStartsWithUnderscore + case .endsWithUnderscore: + statusText = presentationData.strings.Username_InvalidEndsWithUnderscore + case .invalidCharacters: + statusText = presentationData.strings.Username_InvalidCharacters + case .tooShort: + statusText = presentationData.strings.Username_InvalidTooShort + } + case let .availability(availability): + switch availability { + case .available: + statusText = presentationData.strings.Username_UsernameIsAvailable(currentUsername).string + case .invalid: + statusText = presentationData.strings.Username_InvalidCharacters + case .taken: + statusText = presentationData.strings.Username_InvalidTaken + case .purchaseAvailable: + var markdownString = presentationData.strings.Username_UsernamePurchaseAvailable + let entities = generateTextEntities(markdownString, enabledTypes: [.mention]) + if let entity = entities.first { + markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound)) + markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound)) + } + statusText = markdownString + } + case .checking: + statusText = presentationData.strings.Username_CheckingUsername + } + entries.append(.usernameStatus(presentationData.theme, currentUsername, status, statusText, currentUsername)) + } + + entries.append(.usernameInfo(presentationData.theme, presentationData.strings.CreateGroup_PublicLinkInfo)) + } + } + return entries } @@ -222,6 +353,7 @@ public func createChannelController(context: AccountContext, mode: CreateChannel var replaceControllerImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? + var dismissImpl: (() -> Void)? var endEditingImpl: (() -> Void)? var focusOnDescriptionImpl: (() -> Void)? @@ -232,6 +364,14 @@ public func createChannelController(context: AccountContext, mode: CreateChannel let uploadedAvatar = Promise() var uploadedVideoAvatar: (Promise, Double?)? = nil + let checkAddressNameDisposable = MetaDisposable() + actionsDisposable.add(checkAddressNameDisposable) + + var requestPeer: ReplyMarkupButtonRequestPeerType.Channel? + if case let .requestPeer(peerType) = mode { + requestPeer = peerType + } + let arguments = CreateChannelArguments(context: context, updateEditingName: { editingName in updateState { current in var current = current @@ -250,8 +390,8 @@ public func createChannelController(context: AccountContext, mode: CreateChannel return current } }, done: { - let (creating, title, description) = stateValue.with { state -> (Bool, String, String) in - return (state.creating, state.editingName.composedTitle, state.editingDescriptionText) + let (creating, title, description, publicLink) = stateValue.with { state -> (Bool, String, String, String?) in + return (state.creating, state.editingName.composedTitle, state.editingDescriptionText, state.editingPublicLinkText) } if !creating && !title.isEmpty { @@ -263,7 +403,23 @@ public func createChannelController(context: AccountContext, mode: CreateChannel } endEditingImpl?() - actionsDisposable.add((context.engine.peers.createChannel(title: title, description: description.isEmpty ? nil : description) + + var createSignal: Signal = context.engine.peers.createChannel(title: title, description: description.isEmpty ? nil : description) + if case .requestPeer = mode { + if let publicLink, !publicLink.isEmpty { + createSignal = createSignal + |> mapToSignal { peerId in + return context.engine.peers.updateAddressName(domain: .peer(peerId), name: publicLink) + |> mapError { _ in + return .generic + } + |> map { _ in + return peerId + } + } + } + } + actionsDisposable.add((createSignal |> deliverOnMainQueue |> afterDisposed { Queue.mainQueue().async { @@ -283,8 +439,14 @@ public func createChannelController(context: AccountContext, mode: CreateChannel }).start() } - let controller = channelVisibilityController(context: context, peerId: peerId, mode: .initialSetup, upgradedToSupergroup: { _, f in f() }) - replaceControllerImpl?(controller) + if case .requestPeer = mode { + completion?(peerId, { + dismissImpl?() + }) + } else { + let controller = channelVisibilityController(context: context, peerId: peerId, mode: .initialSetup, upgradedToSupergroup: { _, f in f() }) + replaceControllerImpl?(controller) + } }, error: { error in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let text: String? @@ -504,21 +666,57 @@ public func createChannelController(context: AccountContext, mode: CreateChannel }) }, focusOnDescription: { focusOnDescriptionImpl?() + }, updatePublicLinkText: { text in + if text.isEmpty { + checkAddressNameDisposable.set(nil) + updateState { state in + var updated = state + updated.editingPublicLinkText = text + updated.addressNameValidationStatus = nil + return updated + } + } else { + updateState { state in + var updated = state + updated.editingPublicLinkText = text + return updated + } + + checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: .peer(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(0))), name: text) + |> deliverOnMainQueue).start(next: { result in + updateState { state in + var updated = state + updated.addressNameValidationStatus = result + return updated + } + })) + } + }, openAuction: { username in + endEditingImpl?() + + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) }) - + let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in let rightNavigationButton: ItemListNavigationButton if state.creating { rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: !state.editingName.composedTitle.isEmpty, action: { + var isEnabled = true + if state.editingName.composedTitle.isEmpty { + isEnabled = false + } + if case let .requestPeer(peerType) = mode, let hasUsername = peerType.hasUsername, hasUsername, (state.editingPublicLinkText ?? "").isEmpty { + isEnabled = false + } + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Next), style: .bold, enabled: isEnabled, action: { arguments.done() }) } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelIntro_CreateChannel), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: CreateChannelEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: CreateChannelEntryTag.info) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: CreateChannelEntries(presentationData: presentationData, state: state, requestPeer: requestPeer), style: .blocks, focusItemTag: CreateChannelEntryTag.info) return (controllerState, (listState, arguments)) } |> afterDisposed { @@ -535,6 +733,9 @@ public func createChannelController(context: AccountContext, mode: CreateChannel presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } + dismissImpl = { [weak controller] in + controller?.dismiss() + } controller.willDisappear = { _ in endEditingImpl?() } diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 0cde5a4f6f..955cd3d506 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -415,7 +415,7 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat if let hasUsername = requestPeer.hasUsername, hasUsername { let currentUsername = state.editingPublicLinkText ?? "" entries.append(.usernameHeader(presentationData.theme, presentationData.strings.CreateGroup_PublicLinkTitle.uppercased())) - entries.append(.username(presentationData.theme, "link", currentUsername)) + entries.append(.username(presentationData.theme, presentationData.strings.Group_PublicLink_Placeholder, currentUsername)) if let status = state.addressNameValidationStatus { let statusText: String @@ -1180,7 +1180,14 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] if state.creating { rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Compose_Create), style: .bold, enabled: !state.editingName.composedTitle.isEmpty, action: { + var isEnabled = true + if state.editingName.composedTitle.isEmpty { + isEnabled = false + } + if case let .requestPeer(peerType) = mode, let hasUsername = peerType.hasUsername, hasUsername, (state.editingPublicLinkText ?? "").isEmpty { + isEnabled = false + } + rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Compose_Create), style: .bold, enabled: isEnabled, action: { arguments.done() }) }