mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
5018fd7c65
commit
1d80861e09
20 changed files with 269 additions and 733 deletions
|
|
@ -15818,3 +15818,10 @@ Error: %8$@";
|
|||
|
||||
"Premium.CopyProtection" = "Disable Sharing";
|
||||
"Premium.CopyProtectionInfo" = "Prevent forwarding, saving and copying content in private chats.";
|
||||
|
||||
"Channel.EditAdmin.PermissionManageRanks" = "Edit Member Tags";
|
||||
|
||||
"Group.EditAdmin.MemberTagTitle" = "Member Tag";
|
||||
"Group.EditAdmin.MemberTagInfo" = "Add short tag next to %@'s name.";
|
||||
|
||||
"Channel.Owner.Title" = "Owner";
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ public final class AccountWithInfo: Equatable {
|
|||
public enum OpenURLContext {
|
||||
case generic
|
||||
case chat(peerId: PeerId, message: Message?, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?)
|
||||
case external
|
||||
}
|
||||
|
||||
public struct ChatAvailableMessageActionOptions: OptionSet {
|
||||
|
|
@ -1534,6 +1535,8 @@ public protocol SharedAccountContext: AnyObject {
|
|||
|
||||
func makeChatRankInfoScreen(context: AccountContext, chatPeer: EnginePeer, userPeer: EnginePeer, role: ChatRankInfoScreenRole, rank: String, canChange: Bool, completion: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeChatRankPreviewItem(context: AccountContext, peer: EnginePeer, rank: String, rankRole: ChatRankInfoScreenRole, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, sectionId: Int32) -> ListViewItem
|
||||
|
||||
func navigateToCurrentCall()
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
var immediateHasOngoingCall: Bool { get }
|
||||
|
|
|
|||
|
|
@ -151,6 +151,12 @@ final class WebView: WKWebView {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func sendEvent(name: String, data: String?) {
|
||||
let script = "window.TelegramGameProxy && window.TelegramGameProxy.receiveEvent && window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))"
|
||||
self.evaluateJavaScript(script, completionHandler: { _, _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
|
|
@ -399,6 +405,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
switch eventName {
|
||||
case "cancellingTouch":
|
||||
self.cancelInteractiveTransitionGestures()
|
||||
case "oauth_request":
|
||||
self.webView.sendEvent(name: "oauth_supported", data: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ private enum ChannelAdminEntryTag: ItemListItemTag {
|
|||
private enum ChannelAdminEntryStableId: Hashable {
|
||||
case info
|
||||
case rankTitle
|
||||
case rankPreview
|
||||
case rank
|
||||
case rankInfo
|
||||
case adminRights
|
||||
|
|
@ -113,6 +114,7 @@ private let storiesRelatedFlags: [TelegramChatAdminRightsFlags] = [
|
|||
private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, EnginePeer.Presence?)
|
||||
case rankTitle(PresentationTheme, String, Int32?, Int32)
|
||||
case rankPreview(PresentationTheme, PresentationStrings, EnginePeer, String, Bool)
|
||||
case rank(PresentationTheme, PresentationStrings, String, String, Bool)
|
||||
case rankInfo(PresentationTheme, String, Bool)
|
||||
case adminRights(PresentationTheme, String, Bool)
|
||||
|
|
@ -126,7 +128,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||
switch self {
|
||||
case .info:
|
||||
return ChannelAdminSection.info.rawValue
|
||||
case .rankTitle, .rank, .rankInfo:
|
||||
case .rankTitle, .rankPreview, .rank, .rankInfo:
|
||||
return ChannelAdminSection.rank.rawValue
|
||||
case .adminRights:
|
||||
return ChannelAdminSection.adminRights.rawValue
|
||||
|
|
@ -145,6 +147,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||
return .info
|
||||
case .rankTitle:
|
||||
return .rankTitle
|
||||
case .rankPreview:
|
||||
return .rankPreview
|
||||
case .rank:
|
||||
return .rank
|
||||
case .rankInfo:
|
||||
|
|
@ -194,6 +198,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .rankPreview(lhsTheme, lhsStrings, lhsPeer, lhsRank, lhsIsOwner):
|
||||
if case let .rankPreview(rhsTheme, rhsStrings, rhsPeer, rhsRank, rhsIsOwner) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPeer == rhsPeer, lhsRank == rhsRank, lhsIsOwner == rhsIsOwner {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .rank(lhsTheme, lhsStrings, lhsPlaceholder, lhsValue, lhsEnabled):
|
||||
if case let .rank(rhsTheme, rhsStrings, rhsPlaceholder, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
return true
|
||||
|
|
@ -281,44 +291,23 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||
default:
|
||||
return true
|
||||
}
|
||||
case .rankTitle:
|
||||
switch rhs {
|
||||
case .info, .rankTitle:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rank:
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rankInfo:
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank, .rankInfo:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .adminRights:
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank, .rankInfo, .adminRights:
|
||||
case .info, .adminRights:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rightsTitle:
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank, .rankInfo, .adminRights, .rightsTitle:
|
||||
case .info, .adminRights, .rightsTitle:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case let .rightItem(_, lhsIndex, _, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank, .rankInfo, .adminRights, .rightsTitle:
|
||||
case .info, .adminRights, .rightsTitle:
|
||||
return false
|
||||
case let .rightItem(_, rhsIndex, _, _, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
|
|
@ -327,14 +316,42 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||
}
|
||||
case .addAdminsInfo:
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank, .rankInfo, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo:
|
||||
case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .transfer:
|
||||
switch rhs {
|
||||
case .info, .rankTitle, .rank, .rankInfo, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer:
|
||||
case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rankTitle:
|
||||
switch rhs {
|
||||
case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rankPreview:
|
||||
switch rhs {
|
||||
case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rank:
|
||||
switch rhs {
|
||||
case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview, .rank:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .rankInfo:
|
||||
switch rhs {
|
||||
case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview, .rank, .rankInfo:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
|
@ -357,6 +374,9 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||
accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic)
|
||||
}
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: accessoryText, sectionId: self.section)
|
||||
case let .rankPreview(_, _, peer, rank, isOwner):
|
||||
let globalPresentationData = arguments.context.sharedContext.currentPresentationData.with { $0 }
|
||||
return arguments.context.sharedContext.makeChatRankPreviewItem(context: arguments.context, peer: peer, rank: rank, rankRole: isOwner ? .creator : .admin, theme: presentationData.theme, strings: presentationData.strings, wallpaper: globalPresentationData.chatWallpaper, fontSize: globalPresentationData.chatFontSize, chatBubbleCorners: globalPresentationData.chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, sectionId: self.section)
|
||||
case let .rank(_, _, placeholder, text, enabled):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in
|
||||
arguments.updateRank(text, updatedText)
|
||||
|
|
@ -523,6 +543,8 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm
|
|||
}
|
||||
} else if right.contains(.canPinMessages) {
|
||||
return strings.Channel_EditAdmin_PermissionPinMessages
|
||||
} else if right.contains(.canManageRanks) {
|
||||
return strings.Channel_EditAdmin_PermissionManageRanks
|
||||
} else if right.contains(.canManageTopics) {
|
||||
return strings.Channel_EditAdmin_PermissionManageTopics
|
||||
} else if right.contains(.canAddAdmins) {
|
||||
|
|
@ -644,6 +666,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
.direct(.canDeleteMessages),
|
||||
.direct(.canBanUsers),
|
||||
.direct(.canInviteUsers),
|
||||
.direct(.canManageRanks),
|
||||
.direct(.canPinMessages),
|
||||
.direct(.canManageTopics),
|
||||
.direct(.canManageCalls),
|
||||
|
|
@ -656,6 +679,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
.direct(.canDeleteMessages),
|
||||
.direct(.canBanUsers),
|
||||
.direct(.canInviteUsers),
|
||||
.direct(.canManageRanks),
|
||||
.direct(.canPinMessages),
|
||||
.sub(.stories, storiesRelatedFlags),
|
||||
.direct(.canManageCalls),
|
||||
|
|
@ -665,25 +689,6 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
}
|
||||
}
|
||||
|
||||
if isGroup, !invite {
|
||||
let placeholder = isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder
|
||||
|
||||
let currentRank: String?
|
||||
if let updatedRank = state.updatedRank {
|
||||
currentRank = updatedRank
|
||||
} else if let initialParticipant = initialParticipant {
|
||||
currentRank = initialParticipant.rank
|
||||
} else {
|
||||
currentRank = nil
|
||||
}
|
||||
|
||||
let rankEnabled = !state.updating && canEdit
|
||||
//TODO:localize
|
||||
entries.append(.rankTitle(presentationData.theme, "Member Tag".uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
|
||||
entries.append(.rank(presentationData.theme, presentationData.strings, placeholder, currentRank ?? "", rankEnabled))
|
||||
entries.append(.rankInfo(presentationData.theme, "Add short tag next to \(admin.compactDisplayTitle)'s name.", invite))
|
||||
}
|
||||
|
||||
if isCreator {
|
||||
if isGroup {
|
||||
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
|
||||
|
|
@ -907,6 +912,27 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
if canTransfer {
|
||||
entries.append(.transfer(presentationData.theme, isGroup ? presentationData.strings.Group_EditAdmin_TransferOwnership : presentationData.strings.Channel_EditAdmin_TransferOwnership))
|
||||
}
|
||||
|
||||
if case .group = channel.info {
|
||||
let placeholder = isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder
|
||||
|
||||
let currentRank: String?
|
||||
if let updatedRank = state.updatedRank {
|
||||
currentRank = updatedRank
|
||||
} else if let initialParticipant = initialParticipant {
|
||||
currentRank = initialParticipant.rank
|
||||
} else {
|
||||
currentRank = nil
|
||||
}
|
||||
|
||||
let rankEnabled = !state.updating && canEdit
|
||||
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
|
||||
if let adminPeer {
|
||||
entries.append(.rankPreview(presentationData.theme, presentationData.strings, adminPeer, currentRank ?? placeholder, isCreator))
|
||||
entries.append(.rank(presentationData.theme, presentationData.strings, isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder, currentRank ?? "", rankEnabled))
|
||||
entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagInfo(adminPeer.compactDisplayTitle).string, invite))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if canDismiss {
|
||||
|
|
@ -929,16 +955,10 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
|
||||
let rankEnabled = !state.updating && canEdit
|
||||
|
||||
if !invite {
|
||||
//TODO:localize
|
||||
let placeholder = isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder
|
||||
entries.append(.rankTitle(presentationData.theme, "Member Tag".uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
|
||||
entries.append(.rank(presentationData.theme, presentationData.strings, placeholder, currentRank ?? "", rankEnabled))
|
||||
entries.append(.rankInfo(presentationData.theme, "Add short tag next to \(admin.compactDisplayTitle)'s name.", invite))
|
||||
}
|
||||
|
||||
if isCreator {
|
||||
|
||||
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
|
||||
entries.append(.rankPreview(presentationData.theme, presentationData.strings, admin, currentRank ?? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder, true))
|
||||
entries.append(.rank(presentationData.theme, presentationData.strings, presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder, currentRank ?? "", rankEnabled))
|
||||
} else {
|
||||
if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, invite {
|
||||
if let initialParticipant = initialParticipant, case let .member(_, _, adminRights, _, _, _) = initialParticipant, adminRights != nil {
|
||||
|
|
@ -995,6 +1015,12 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
if case let .user(admin) = admin, case .creator = group.role, admin.botInfo == nil && !admin.isDeleted && areAllAdminRightsEnabled(currentRightsFlags, peer: .legacyGroup(group), except: .canBeAnonymous) {
|
||||
entries.append(.transfer(presentationData.theme, presentationData.strings.Group_EditAdmin_TransferOwnership))
|
||||
}
|
||||
|
||||
let placeholder = isCreator ? presentationData.strings.Group_EditAdmin_RankOwnerPlaceholder : presentationData.strings.Group_EditAdmin_RankAdminPlaceholder
|
||||
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
|
||||
entries.append(.rankPreview(presentationData.theme, presentationData.strings, admin, currentRank ?? placeholder, isCreator))
|
||||
entries.append(.rank(presentationData.theme, presentationData.strings, placeholder, currentRank ?? "", rankEnabled))
|
||||
entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagInfo(admin.compactDisplayTitle).string, invite))
|
||||
}
|
||||
|
||||
if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _, _) = initialParticipant, admin.id != accountPeerId, let adminInfo {
|
||||
|
|
@ -1613,8 +1639,15 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
|||
|
||||
var footerItem: ItemListControllerFooterItem?
|
||||
|
||||
var isCreator = false
|
||||
if case let .channel(channel) = channelPeer, channel.flags.contains(.isCreator) {
|
||||
isCreator = true
|
||||
} else if case let .legacyGroup(group) = channelPeer, case .creator = group.role {
|
||||
isCreator = true
|
||||
}
|
||||
|
||||
let title: String
|
||||
if initialParticipant?.adminInfo == nil {
|
||||
if initialParticipant?.adminInfo == nil && !isCreator {
|
||||
var isGroup: Bool = false
|
||||
var peerTitle: String = ""
|
||||
if case let .legacyGroup(peer) = channelPeer {
|
||||
|
|
@ -1655,7 +1688,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
|||
title = presentationData.strings.Channel_Management_AddModerator
|
||||
}
|
||||
} else {
|
||||
title = presentationData.strings.Channel_Moderator_Title
|
||||
title = isCreator ? presentationData.strings.Channel_Owner_Title : presentationData.strings.Channel_Moderator_Title
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 224
|
||||
return 223
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
|
|
|||
|
|
@ -206,30 +206,33 @@ public enum MessageActionUrlAuthError {
|
|||
|
||||
public enum MessageActionUrlSubject {
|
||||
case message(id: MessageId, buttonId: Int32)
|
||||
case url(String)
|
||||
case url(url: String, inAppOrigin: String?)
|
||||
}
|
||||
|
||||
func _internal_requestMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
let request: Signal<Api.UrlAuthResult?, MTRpcError>
|
||||
var flags: Int32 = 0
|
||||
switch subject {
|
||||
case let .message(messageId, buttonId):
|
||||
flags |= (1 << 1)
|
||||
request = account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
|> castError(MTRpcError.self)
|
||||
|> mapToSignal { peer -> Signal<Api.UrlAuthResult?, MTRpcError> in
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.requestUrlAuth(flags: flags, peer: inputPeer, msgId: messageId.id, buttonId: buttonId, url: nil, inAppOrigin: nil))
|
||||
|> map(Optional.init)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
case let .message(messageId, buttonId):
|
||||
flags |= (1 << 1)
|
||||
request = account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
|> castError(MTRpcError.self)
|
||||
|> mapToSignal { peer -> Signal<Api.UrlAuthResult?, MTRpcError> in
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.requestUrlAuth(flags: flags, peer: inputPeer, msgId: messageId.id, buttonId: buttonId, url: nil, inAppOrigin: nil))
|
||||
|> map(Optional.init)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
case let .url(url):
|
||||
flags |= (1 << 2)
|
||||
request = account.network.request(Api.functions.messages.requestUrlAuth(flags: flags, peer: nil, msgId: nil, buttonId: nil, url: url, inAppOrigin: nil))
|
||||
|> map(Optional.init)
|
||||
}
|
||||
case let .url(url, inAppOrigin):
|
||||
flags |= (1 << 2)
|
||||
if let _ = inAppOrigin {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
request = account.network.request(Api.functions.messages.requestUrlAuth(flags: flags, peer: nil, msgId: nil, buttonId: nil, url: url, inAppOrigin: inAppOrigin))
|
||||
|> map(Optional.init)
|
||||
}
|
||||
|
||||
return request
|
||||
|
|
@ -303,7 +306,7 @@ func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActi
|
|||
return .single(nil)
|
||||
}
|
||||
}
|
||||
case let .url(url):
|
||||
case let .url(url, _):
|
||||
flags |= (1 << 2)
|
||||
request = account.network.request(Api.functions.messages.acceptUrlAuth(flags: flags, peer: nil, msgId: nil, buttonId: nil, url: url, matchCode: matchCode))
|
||||
|> map(Optional.init)
|
||||
|
|
|
|||
|
|
@ -520,6 +520,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ChatParticipantRightsScreen",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatRankInfoScreen",
|
||||
"//submodules/TelegramUI/Components/RankChatPreviewItem",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
|
|
|||
|
|
@ -809,6 +809,8 @@ open class AlertScreen: ViewControllerComponentContainer, KeyShortcutResponder {
|
|||
if !self.processedDidDisappear {
|
||||
self.processedDidDisappear = true
|
||||
|
||||
self.view.window?.endEditing(true)
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? AlertScreenComponent.View {
|
||||
let dismissedByTapOutside = componentView.dismissedByTapOutside
|
||||
componentView.animateOut(completion: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public final class RankChatPreviewItem: ListViewItem, ItemListItem, ListItemComp
|
|||
}
|
||||
|
||||
let context: AccountContext
|
||||
let systemStyle: ItemListSystemStyle
|
||||
let theme: PresentationTheme
|
||||
let componentTheme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
|
@ -64,6 +65,7 @@ public final class RankChatPreviewItem: ListViewItem, ItemListItem, ListItemComp
|
|||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
systemStyle: ItemListSystemStyle = .legacy,
|
||||
theme: PresentationTheme,
|
||||
componentTheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
|
|
@ -80,6 +82,7 @@ public final class RankChatPreviewItem: ListViewItem, ItemListItem, ListItemComp
|
|||
maskSide: Bool = false
|
||||
) {
|
||||
self.context = context
|
||||
self.systemStyle = systemStyle
|
||||
self.theme = theme
|
||||
self.componentTheme = componentTheme
|
||||
self.strings = strings
|
||||
|
|
@ -430,7 +433,7 @@ final class RankChatPreviewItemNode: ListViewItemNode {
|
|||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ final class QuickReplyEmptyStateComponent: Component {
|
|||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
|
|
@ -92,7 +93,7 @@ final class QuickReplyEmptyStateComponent: Component {
|
|||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: min(availableSize.width - 16.0 * 2.0, 280.0), height: 50.0)
|
||||
containerSize: CGSize(width: min(availableSize.width - 16.0 * 2.0, 280.0), height: 52.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: availableSize.height - component.insets.bottom - 14.0 - buttonSize.height), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
case .add:
|
||||
return ItemListPeerActionItem(
|
||||
presentationData: ItemListPresentationData(listNode.presentationData),
|
||||
systemStyle: .glass,
|
||||
icon: PresentationResourcesItemList.plusIconImage(listNode.presentationData.theme),
|
||||
iconSignal: nil,
|
||||
title: listNode.presentationData.strings.QuickReply_InlineCreateAction,
|
||||
|
|
@ -600,7 +601,7 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
}
|
||||
} else {
|
||||
var completion: ((String?) -> Void)?
|
||||
let alertController = quickReplyNameAlertController(
|
||||
let (alertController, displayError) = quickReplyNameAlertController(
|
||||
context: component.context,
|
||||
text: environment.strings.QuickReply_CreateShortcutTitle,
|
||||
subtext: environment.strings.QuickReply_CreateShortcutText,
|
||||
|
|
@ -612,24 +613,21 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
)
|
||||
completion = { [weak self, weak alertController] value in
|
||||
guard let self, let environment = self.environment else {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
if let value, !value.isEmpty {
|
||||
guard let shortcutMessageList = self.shortcutMessageList else {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
if shortcutMessageList.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) {
|
||||
if let contentNode = alertController?.contentNode as? QuickReplyNameAlertContentNode {
|
||||
contentNode.setErrorText(errorText: environment.strings.QuickReply_ShortcutExistsInlineError)
|
||||
}
|
||||
displayError(environment.strings.QuickReply_ShortcutExistsInlineError)
|
||||
return
|
||||
}
|
||||
|
||||
alertController?.view.endEditing(true)
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
self.openQuickReplyChat(shortcut: value, shortcutId: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -643,7 +641,7 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
}
|
||||
|
||||
var completion: ((String?) -> Void)?
|
||||
let alertController = quickReplyNameAlertController(
|
||||
let (alertController, displayError) = quickReplyNameAlertController(
|
||||
context: component.context,
|
||||
text: environment.strings.QuickReply_EditShortcutTitle,
|
||||
subtext: environment.strings.QuickReply_EditShortcutText,
|
||||
|
|
@ -655,28 +653,25 @@ final class QuickReplySetupScreenComponent: Component {
|
|||
)
|
||||
completion = { [weak self, weak alertController] value in
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
if let value, !value.isEmpty {
|
||||
if value == currentValue {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
guard let shortcutMessageList = self.shortcutMessageList else {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
if shortcutMessageList.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) {
|
||||
if let contentNode = alertController?.contentNode as? QuickReplyNameAlertContentNode {
|
||||
contentNode.setErrorText(errorText: environment.strings.QuickReply_ShortcutExistsInlineError)
|
||||
}
|
||||
displayError(environment.strings.QuickReply_ShortcutExistsInlineError)
|
||||
} else {
|
||||
component.context.engine.accountData.editMessageShortcut(id: id, shortcut: value)
|
||||
|
||||
alertController?.view.endEditing(true)
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,632 +8,96 @@ import TelegramCore
|
|||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import EmojiStatusComponent
|
||||
import AlertComponent
|
||||
import AlertInputFieldComponent
|
||||
|
||||
private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
private var theme: PresentationTheme
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textInputNode: EditableTextNode
|
||||
private let placeholderNode: ASTextNode
|
||||
private let characterLimitView = ComponentView<Empty>()
|
||||
public func quickReplyNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, subtext: String, value: String?, characterLimit: Int, apply: @escaping (String?) -> Void) -> (controller: AlertScreen, displayError: (String) -> Void) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
private let characterLimit: Int
|
||||
let inputState = AlertInputFieldComponent.ExternalState()
|
||||
|
||||
var updateHeight: (() -> Void)?
|
||||
var complete: (() -> Void)?
|
||||
var textChanged: ((String) -> Void)?
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
|
||||
private let inputInsets: UIEdgeInsets
|
||||
|
||||
private let validCharacterSets: [CharacterSet]
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
return self.textInputNode.attributedText?.string ?? ""
|
||||
}
|
||||
set {
|
||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
var placeholder: String = "" {
|
||||
didSet {
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String, characterLimit: Int) {
|
||||
self.theme = theme
|
||||
self.characterLimit = characterLimit
|
||||
|
||||
self.inputInsets = UIEdgeInsets(top: 9.0, left: 6.0, bottom: 9.0, right: 16.0)
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
|
||||
self.textInputNode = EditableTextNode()
|
||||
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(13.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||
self.textInputNode.clipsToBounds = true
|
||||
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: self.inputInsets.left, bottom: self.inputInsets.bottom, right: self.inputInsets.right)
|
||||
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textInputNode.keyboardType = .default
|
||||
self.textInputNode.autocapitalizationType = .none
|
||||
self.textInputNode.returnKeyType = .done
|
||||
self.textInputNode.autocorrectionType = .no
|
||||
self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
|
||||
|
||||
self.placeholderNode = ASTextNode()
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
self.placeholderNode.displaysAsynchronously = false
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
|
||||
self.validCharacterSets = [
|
||||
CharacterSet.alphanumerics,
|
||||
CharacterSet(charactersIn: "0123456789_"),
|
||||
]
|
||||
|
||||
super.init()
|
||||
|
||||
self.textInputNode.delegate = self
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textInputNode)
|
||||
self.addSubnode(self.placeholderNode)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
|
||||
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
|
||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left + 5.0, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
|
||||
|
||||
let characterLimitString: String
|
||||
let characterLimitColor: UIColor
|
||||
if self.text.count <= self.characterLimit {
|
||||
let remaining = self.characterLimit - self.text.count
|
||||
if remaining < 5 {
|
||||
characterLimitString = "\(remaining)"
|
||||
} else {
|
||||
characterLimitString = " "
|
||||
}
|
||||
characterLimitColor = self.theme.list.itemPlaceholderTextColor
|
||||
} else {
|
||||
characterLimitString = "\(self.characterLimit - self.text.count)"
|
||||
characterLimitColor = self.theme.list.itemDestructiveColor
|
||||
}
|
||||
|
||||
let characterLimitSize = self.characterLimitView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: characterLimitString, font: Font.regular(13.0), textColor: characterLimitColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let characterLimitComponentView = self.characterLimitView.view {
|
||||
if characterLimitComponentView.superview == nil {
|
||||
self.view.addSubview(characterLimitComponentView)
|
||||
}
|
||||
characterLimitComponentView.frame = CGRect(origin: CGPoint(x: width - 23.0 - characterLimitSize.width, y: 18.0), size: characterLimitSize)
|
||||
}
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.textInputNode.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func deactivateInput() {
|
||||
self.textInputNode.resignFirstResponder()
|
||||
}
|
||||
|
||||
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
self.textChanged?(editableTextNode.textView.text)
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
}
|
||||
|
||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
if text == "\n" {
|
||||
self.complete?()
|
||||
return false
|
||||
}
|
||||
if text.unicodeScalars.contains(where: { c in
|
||||
return !self.validCharacterSets.contains(where: { set in
|
||||
return set.contains(c)
|
||||
})
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
|
||||
let unboundTextFieldHeight = max(34.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||
|
||||
return min(61.0, max(34.0, unboundTextFieldHeight))
|
||||
}
|
||||
|
||||
private func updateTextNodeText(animated: Bool) {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
|
||||
let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
|
||||
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
if !self.bounds.size.height.isEqual(to: panelHeight) {
|
||||
self.updateHeight?()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func clearPressed() {
|
||||
self.textInputNode.attributedText = nil
|
||||
self.deactivateInput()
|
||||
}
|
||||
}
|
||||
|
||||
public final class QuickReplyNameAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private var theme: AlertControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
private let text: String
|
||||
private let subtext: String
|
||||
private let titleFont: PromptControllerTitleFont
|
||||
|
||||
private let textView = ComponentView<Empty>()
|
||||
private let subtextView = ComponentView<Empty>()
|
||||
|
||||
fileprivate let inputFieldNode: PromptInputFieldNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private var validLayout: CGSize?
|
||||
private var errorText: String?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
var complete: (() -> Void)?
|
||||
|
||||
override public var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, subtext: String, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.text = text
|
||||
self.subtext = subtext
|
||||
self.titleFont = titleFont
|
||||
|
||||
self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: strings.QuickReply_ShortcutPlaceholder, characterLimit: characterLimit)
|
||||
self.inputFieldNode.text = value ?? ""
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.inputFieldNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
self.actionNodes.last?.actionEnabled = true
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.inputFieldNode.updateHeight = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let _ = strongSelf.validLayout {
|
||||
strongSelf.requestLayout?(.immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.inputFieldNode.textChanged = { [weak self] text in
|
||||
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
|
||||
lastNode.actionEnabled = text.count <= characterLimit
|
||||
strongSelf.requestLayout?(.immediate)
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.inputFieldNode.complete = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let lastNode = self.actionNodes.last, lastNode.actionEnabled {
|
||||
self.complete?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
var value: String {
|
||||
return self.inputFieldNode.text
|
||||
}
|
||||
|
||||
public func setErrorText(errorText: String?) {
|
||||
if self.errorText != errorText {
|
||||
self.errorText = errorText
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
|
||||
if errorText != nil {
|
||||
HapticFeedback().error()
|
||||
self.inputFieldNode.layer.addShakeAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||
|
||||
let hadValidLayout = self.validLayout != nil
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 16.0)
|
||||
let spacing: CGFloat = 5.0
|
||||
let subtextSpacing: CGFloat = -1.0
|
||||
|
||||
let textSize = self.textView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: self.text, font: Font.semibold(17.0), textColor: self.theme.primaryColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: measureSize.width, height: 1000.0)
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) * 0.5), y: origin.y), size: textSize)
|
||||
if let textComponentView = self.textView.view {
|
||||
if textComponentView.superview == nil {
|
||||
textComponentView.layer.anchorPoint = CGPoint()
|
||||
self.view.addSubview(textComponentView)
|
||||
}
|
||||
textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin)
|
||||
}
|
||||
origin.y += textSize.height + 6.0 + subtextSpacing
|
||||
|
||||
let subtextSize = self.subtextView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(string: self.errorText ?? self.subtext, font: Font.regular(13.0), textColor: self.errorText != nil ? self.theme.destructiveColor : self.theme.primaryColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: measureSize.width, height: 1000.0)
|
||||
)
|
||||
let subtextFrame = CGRect(origin: CGPoint(x: floor((size.width - subtextSize.width) * 0.5), y: origin.y), size: subtextSize)
|
||||
if let subtextComponentView = self.subtextView.view {
|
||||
if subtextComponentView.superview == nil {
|
||||
subtextComponentView.layer.anchorPoint = CGPoint()
|
||||
self.view.addSubview(subtextComponentView)
|
||||
}
|
||||
subtextComponentView.bounds = CGRect(origin: CGPoint(), size: subtextFrame.size)
|
||||
transition.updatePosition(layer: subtextComponentView.layer, position: subtextFrame.origin)
|
||||
}
|
||||
origin.y += subtextSize.height + 6.0 + spacing
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
|
||||
|
||||
var contentWidth = max(textSize.width, minActionsWidth)
|
||||
contentWidth = max(subtextSize.width, minActionsWidth)
|
||||
contentWidth = max(contentWidth, 234.0)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultWidth = contentWidth + insets.left + insets.right
|
||||
|
||||
let inputFieldWidth = resultWidth
|
||||
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
|
||||
let inputHeight = inputFieldHeight
|
||||
let inputFieldFrame = CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)
|
||||
transition.updateFrame(node: self.inputFieldNode, frame: inputFieldFrame)
|
||||
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
||||
|
||||
let resultSize = CGSize(width: resultWidth, height: textSize.height + subtextSpacing + subtextSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom)
|
||||
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if !hadValidLayout {
|
||||
self.inputFieldNode.activateInput()
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.inputFieldNode.layer.addShakeAnimation()
|
||||
self.hapticFeedback.error()
|
||||
}
|
||||
}
|
||||
|
||||
public enum PromptControllerTitleFont {
|
||||
case regular
|
||||
case bold
|
||||
}
|
||||
|
||||
public func quickReplyNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, subtext: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
// let strings = presentationData.strings
|
||||
//
|
||||
// let inputState = AlertInputFieldComponent.ExternalState()
|
||||
//
|
||||
// let doneIsEnabled: Signal<Bool, NoError> = inputState.valueSignal
|
||||
// |> map { value in
|
||||
// return !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
// }
|
||||
//
|
||||
// let doneInProgressValuePromise = ValuePromise<Bool>(false)
|
||||
// let doneInProgress = doneInProgressValuePromise.get()
|
||||
//
|
||||
// var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
// content.append(AnyComponentWithIdentity(
|
||||
// id: "title",
|
||||
// component: AnyComponent(
|
||||
// AlertTitleComponent(title: strings.WebBrowser_Exceptions_Create_Title)
|
||||
// )
|
||||
// ))
|
||||
// content.append(AnyComponentWithIdentity(
|
||||
// id: "text",
|
||||
// component: AnyComponent(
|
||||
// AlertTextComponent(content: .plain(strings.WebBrowser_Exceptions_Create_Text))
|
||||
// )
|
||||
// ))
|
||||
//
|
||||
// let domainRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.?)*([a-zA-Z]*)?(:)?(/)?$", options: [])
|
||||
// let pathRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}/", options: [])
|
||||
// var applyImpl: (() -> Void)?
|
||||
// content.append(AnyComponentWithIdentity(
|
||||
// id: "input",
|
||||
// component: AnyComponent(
|
||||
// AlertInputFieldComponent(
|
||||
// context: context,
|
||||
// initialValue: nil,
|
||||
// placeholder: strings.QuickReply_ShortcutPlaceholder,
|
||||
// characterLimit: characterLimit,
|
||||
// hasClearButton: false,
|
||||
// keyboardType: .URL,
|
||||
// autocapitalizationType: .none,
|
||||
// autocorrectionType: .no,
|
||||
// isInitiallyFocused: true,
|
||||
// externalState: inputState,
|
||||
// shouldChangeText: { updatedText in
|
||||
// guard let domainRegex, let pathRegex else {
|
||||
// return true
|
||||
// }
|
||||
// let domainMatches = domainRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count))
|
||||
// let pathMatches = pathRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count))
|
||||
// if domainMatches.count > 0, pathMatches.count == 0 {
|
||||
// return true
|
||||
// } else {
|
||||
// return false
|
||||
// }
|
||||
// },
|
||||
// returnKeyAction: {
|
||||
// applyImpl?()
|
||||
// }
|
||||
// )
|
||||
// )
|
||||
// ))
|
||||
//
|
||||
// var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
|
||||
// if let updatedPresentationData {
|
||||
// effectiveUpdatedPresentationData = updatedPresentationData
|
||||
// } else {
|
||||
// effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
|
||||
// }
|
||||
//
|
||||
// let alertController = AlertScreen(
|
||||
// configuration: AlertScreen.Configuration(allowInputInset: true),
|
||||
// content: content,
|
||||
// actions: [
|
||||
// .init(title: strings.Common_Cancel, action: {
|
||||
// apply(nil)
|
||||
// }),
|
||||
// .init(title: strings.Common_Done, type: .default, action: {
|
||||
// applyImpl?()
|
||||
// }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgress)
|
||||
// ],
|
||||
// updatedPresentationData: effectiveUpdatedPresentationData
|
||||
// )
|
||||
// applyImpl = {
|
||||
// let updatedLink = explicitUrl(inputState.value)
|
||||
// if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true]) {
|
||||
// doneInProgressValuePromise.set(true)
|
||||
// apply(updatedLink)
|
||||
// } else {
|
||||
// inputState.animateError()
|
||||
// }
|
||||
// }
|
||||
// return alertController
|
||||
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var applyImpl: (() -> Void)?
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
apply(nil)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: {
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = QuickReplyNameAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, subtext: subtext, titleFont: titleFont, value: value, characterLimit: characterLimit)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
applyImpl = { [weak contentNode] in
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
apply(contentNode.value)
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
|
||||
})
|
||||
controller.dismissed = { _ in
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
contentNode.inputFieldNode.deactivateInput()
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
let errorPromise = ValuePromise<String?>(nil)
|
||||
let contentSignal = errorPromise.get()
|
||||
|> map { error in
|
||||
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
AlertTitleComponent(title: text)
|
||||
)
|
||||
))
|
||||
if let error {
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(error), color: .destructive)
|
||||
)
|
||||
))
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(
|
||||
AlertTextComponent(content: .plain(subtext))
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
content.append(AnyComponentWithIdentity(
|
||||
id: "input",
|
||||
component: AnyComponent(
|
||||
AlertInputFieldComponent(
|
||||
context: context,
|
||||
initialValue: nil,
|
||||
placeholder: strings.QuickReply_ShortcutPlaceholder,
|
||||
characterLimit: characterLimit,
|
||||
hasClearButton: false,
|
||||
keyboardType: .URL,
|
||||
autocapitalizationType: .none,
|
||||
autocorrectionType: .no,
|
||||
isInitiallyFocused: true,
|
||||
externalState: inputState,
|
||||
returnKeyAction: {
|
||||
applyImpl?()
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
return content
|
||||
}
|
||||
return controller
|
||||
|
||||
var effectiveUpdatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)
|
||||
if let updatedPresentationData {
|
||||
effectiveUpdatedPresentationData = updatedPresentationData
|
||||
} else {
|
||||
effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
|
||||
}
|
||||
|
||||
let alertController = AlertScreen(
|
||||
configuration: AlertScreen.Configuration(allowInputInset: true),
|
||||
contentSignal: contentSignal,
|
||||
actionsSignal: .single([
|
||||
.init(title: strings.Common_Cancel, action: {
|
||||
apply(nil)
|
||||
}),
|
||||
.init(title: strings.Common_Done, type: .default, action: {
|
||||
applyImpl?()
|
||||
}, autoDismiss: false)
|
||||
]),
|
||||
updatedPresentationData: effectiveUpdatedPresentationData
|
||||
)
|
||||
applyImpl = {
|
||||
apply(inputState.value)
|
||||
}
|
||||
|
||||
let displayError = { [weak inputState] error in
|
||||
errorPromise.set(error)
|
||||
inputState?.animateError()
|
||||
HapticFeedback().error()
|
||||
}
|
||||
|
||||
return (alertController, displayError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2522,7 +2522,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||
if let authContext = authContext, let confirmationCode = parseConfirmationCodeUrl(sharedContext: sharedContext, url: url) {
|
||||
authContext.rootController.applyConfirmationCode(confirmationCode)
|
||||
} else if let context = context {
|
||||
context.openUrl(url)
|
||||
context.openUrl(url, external: true)
|
||||
} else if let authContext = authContext {
|
||||
if let proxyData = parseProxyUrl(sharedContext: sharedContext, url: url) {
|
||||
authContext.rootController.view.endEditing(true)
|
||||
|
|
|
|||
|
|
@ -923,10 +923,10 @@ final class AuthorizedApplicationContext {
|
|||
}
|
||||
}
|
||||
|
||||
func openUrl(_ url: URL) {
|
||||
func openUrl(_ url: URL, external: Bool = false) {
|
||||
if self.rootController.rootTabController != nil {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url.absoluteString, forceExternal: false, presentationData: presentationData, navigationController: self.rootController, dismissInput: { [weak self] in
|
||||
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: external ? .external : .generic, url: url.absoluteString, forceExternal: false, presentationData: presentationData, navigationController: self.rootController, dismissInput: { [weak self] in
|
||||
self?.rootController.view.endEditing(true)
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -9089,7 +9089,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
self.controllerInteraction?.sendEmoji(text, attribute, false)
|
||||
},
|
||||
requestMessageActionUrlAuth: { [weak self] subject in
|
||||
if case let .url(url) = subject {
|
||||
if case let .url(url, _) = subject {
|
||||
self?.controllerInteraction?.requestMessageActionUrlAuth(url, subject)
|
||||
}
|
||||
}, joinVoiceChat: { [weak self] peerId, invite, call in
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ extension ChatControllerImpl {
|
|||
func editChat() {
|
||||
if case let .customChatContents(customChatContents) = self.subject, case let .quickReplyMessageInput(currentValue, shortcutType) = customChatContents.kind, case .generic = shortcutType {
|
||||
var completion: ((String?) -> Void)?
|
||||
let alertController = quickReplyNameAlertController(
|
||||
let (alertController, displayError) = quickReplyNameAlertController(
|
||||
context: self.context,
|
||||
text: self.presentationData.strings.QuickReply_EditShortcutTitle,
|
||||
subtext: self.presentationData.strings.QuickReply_EditShortcutText,
|
||||
|
|
@ -27,12 +27,12 @@ extension ChatControllerImpl {
|
|||
)
|
||||
completion = { [weak self, weak alertController] value in
|
||||
guard let self else {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
if let value, !value.isEmpty {
|
||||
if value == currentValue {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -40,14 +40,12 @@ extension ChatControllerImpl {
|
|||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] shortcutMessageList in
|
||||
guard let self else {
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
if shortcutMessageList.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) {
|
||||
if let contentNode = alertController?.contentNode as? QuickReplyNameAlertContentNode {
|
||||
contentNode.setErrorText(errorText: self.presentationData.strings.QuickReply_ShortcutExistsInlineError)
|
||||
}
|
||||
displayError(self.presentationData.strings.QuickReply_ShortcutExistsInlineError)
|
||||
} else {
|
||||
let _ = self.chatTitleView?.update(
|
||||
context: self.context,
|
||||
|
|
@ -61,8 +59,7 @@ extension ChatControllerImpl {
|
|||
transition: .immediate
|
||||
)
|
||||
|
||||
alertController?.view.endEditing(true)
|
||||
alertController?.dismissAnimated()
|
||||
alertController?.dismiss(animated: true, completion: nil)
|
||||
|
||||
if case let .customChatContents(customChatContents) = self.subject {
|
||||
customChatContents.quickReplyUpdateShortcut(value: value)
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ extension ChatControllerImpl {
|
|||
allButtons.insert(button, at: 1)
|
||||
}
|
||||
|
||||
if !"".isEmpty, let user = peer as? TelegramUser, user.botInfo == nil {
|
||||
if let user = peer as? TelegramUser, user.botInfo == nil {
|
||||
if let index = buttons.firstIndex(where: { $0 == .location }) {
|
||||
buttons.insert(.quickReply, at: index + 1)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func openResolvedUrlImpl(
|
|||
case let .externalUrl(url):
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: urlContext, url: url, forceExternal: forceExternal, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: navigationController, dismissInput: dismissInput)
|
||||
case let .urlAuth(url):
|
||||
requestMessageActionUrlAuth?(.url(url))
|
||||
requestMessageActionUrlAuth?(.url(url: url, inAppOrigin: nil))
|
||||
dismissInput()
|
||||
break
|
||||
case let .peer(peer, navigation):
|
||||
|
|
@ -1816,7 +1816,8 @@ func openResolvedUrlImpl(
|
|||
present(alertController, nil)
|
||||
})
|
||||
case let .oauth(url):
|
||||
let _ = (context.engine.messages.requestMessageActionUrlAuth(subject: .url(url))
|
||||
let subject: MessageActionUrlSubject = .url(url: url, inAppOrigin: nil)
|
||||
let _ = (context.engine.messages.requestMessageActionUrlAuth(subject: subject)
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
if case .request = result {
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
|
@ -1825,13 +1826,13 @@ func openResolvedUrlImpl(
|
|||
case let .accept(allowWriteAccess, sharePhoneNumber):
|
||||
let signal: Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError>
|
||||
if accountContext === context {
|
||||
signal = accountContext.engine.messages.acceptMessageActionUrlAuth(subject: .url(url), allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
signal = accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
} else {
|
||||
accountContext.account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
signal = accountContext.engine.messages.requestMessageActionUrlAuth(subject: .url(url))
|
||||
signal = accountContext.engine.messages.requestMessageActionUrlAuth(subject: subject)
|
||||
|> castError(MessageActionUrlAuthError.self)
|
||||
|> mapToSignal { result in
|
||||
return accountContext.engine.messages.acceptMessageActionUrlAuth(subject: .url(url), allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
return accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
} |> afterDisposed {
|
||||
accountContext.account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -504,7 +504,12 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||
}
|
||||
case "passport", "oauth", "resolve":
|
||||
if isOAuthUrl(parsedUrl) {
|
||||
handleResolvedUrl(.oauth(url: url))
|
||||
switch urlContext {
|
||||
case .external:
|
||||
handleResolvedUrl(.oauth(url: url))
|
||||
default:
|
||||
break
|
||||
}
|
||||
return
|
||||
} else if let secureId = parseSecureIdUrl(parsedUrl) {
|
||||
if case .chat = urlContext {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ import GiftCraftScreen
|
|||
import ChatParticipantRightsScreen
|
||||
import PeerCopyProtectionInfoScreen
|
||||
import ChatRankInfoScreen
|
||||
import RankChatPreviewItem
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
|
|
@ -4331,6 +4332,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
public func makeChatRankInfoScreen(context: AccountContext, chatPeer: EnginePeer, userPeer: EnginePeer, role: ChatRankInfoScreenRole, rank: String, canChange: Bool, completion: @escaping () -> Void) -> ViewController {
|
||||
return ChatRankInfoScreen(context: context, chatPeer: chatPeer, userPeer: userPeer, role: role, rank: rank, canChange: canChange, completion: completion)
|
||||
}
|
||||
|
||||
public func makeChatRankPreviewItem(context: AccountContext, peer: EnginePeer, rank: String, rankRole: ChatRankInfoScreenRole, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, sectionId: Int32) -> ListViewItem {
|
||||
let messageItem = RankChatPreviewItem.MessageItem(
|
||||
peer: peer,
|
||||
text: "Reinhardt, we need to find you some new tunes.",
|
||||
entities: nil,
|
||||
media: [],
|
||||
rank: rank,
|
||||
rankRole: rankRole
|
||||
)
|
||||
return RankChatPreviewItem(context: context, systemStyle: .glass, theme: theme, componentTheme: theme, strings: strings, sectionId: sectionId, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, messageItems: [messageItem])
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue