Various improvements

This commit is contained in:
Ilya Laktyushin 2022-06-07 15:58:56 +04:00
parent 149805e914
commit 57e7e6906b
20 changed files with 425 additions and 330 deletions

View File

@ -890,7 +890,7 @@ public protocol AccountContext: AnyObject {
public struct PremiumConfiguration {
public static var defaultValue: PremiumConfiguration {
return PremiumConfiguration(isPremiumDisabled: false)
return PremiumConfiguration(isPremiumDisabled: true)
}
public let isPremiumDisabled: Bool

View File

@ -1034,14 +1034,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in
skipStateAnimation = true
updateState { state in
var state = state
state.additionallyIncludePeers = filter.data?.includePeers.peers ?? []
state.additionallyExcludePeers = filter.data?.excludePeers ?? []
state.includeCategories = filter.data?.categories ?? []
state.excludeRead = filter.data?.excludeRead ?? false
state.excludeMuted = filter.data?.excludeMuted ?? false
state.excludeArchived = filter.data?.excludeArchived ?? false
return state
var updatedState = state
updatedState.additionallyIncludePeers = filter.data?.includePeers.peers ?? []
updatedState.additionallyExcludePeers = filter.data?.excludePeers ?? []
updatedState.includeCategories = filter.data?.categories ?? []
updatedState.excludeRead = filter.data?.excludeRead ?? false
updatedState.excludeMuted = filter.data?.excludeMuted ?? false
updatedState.excludeArchived = filter.data?.excludeArchived ?? false
return updatedState
}
})
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
@ -1125,7 +1125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
var found = false
for i in 0 ..< filters.count {
if filters[i].id == updatedFilter.id, case let .filter(_, _, _, data) = filters[i] {
var updatedData = data
var updatedData = updatedFilter.data ?? data
var includePeers = updatedData.includePeers
includePeers.setPeers(state.additionallyIncludePeers)
updatedData.includePeers = includePeers

View File

@ -1484,6 +1484,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme)
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId {
if displayAsMessage {
switch item.content {
@ -1495,7 +1496,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium {
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
}
@ -1509,7 +1510,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium {
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
}

View File

@ -563,6 +563,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
}
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var currentCredibilityIconImage: UIImage?
switch item.peer {
case let .peer(peer, _):
@ -573,7 +575,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium {
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
}

View File

@ -24,6 +24,7 @@ swift_library(
"//submodules/AppBundle:AppBundle",
"//submodules/GZip:GZip",
"//third-party/ZipArchive:ZipArchive",
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
],
visibility = [
"//visibility:public",

View File

@ -15,6 +15,7 @@ import AccountContext
import AppBundle
import ZipArchive
import WebKit
import InAppPurchaseManager
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
@ -86,13 +87,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case experimentalBackground(Bool)
case inlineStickers(Bool)
case localTranscription(Bool)
case snow(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
case voiceConference
case preferredVideoCodec(Int, String, String?, Bool)
case disableVideoAspectScaling(Bool)
case enableVoipTcp(Bool)
case resetInAppPurchases(PresentationTheme)
case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme)
@ -108,7 +109,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, .snow:
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, .resetInAppPurchases:
return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue
@ -187,7 +188,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 31
case .localTranscription:
return 32
case .snow:
case .resetInAppPurchases:
return 33
case .playerEmbedding:
return 34
@ -969,16 +970,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .snow(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Snow", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.snow = value
return PreferencesEntry(settings)
})
}).start()
})
case let .playerEmbedding(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -1035,6 +1026,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case .resetInAppPurchases:
return ItemListActionItem(presentationData: presentationData, title: "Reset IAP Transactions", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.context?.inAppPurchaseManager?.finishAllTransactions()
})
case let .hostInfo(_, string):
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
case .versionInfo:
@ -1096,6 +1091,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
entries.append(.inlineStickers(experimentalSettings.inlineStickers))
entries.append(.localTranscription(experimentalSettings.localTranscription))
entries.append(.resetInAppPurchases(presentationData.theme))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
}

View File

@ -93,6 +93,14 @@ public final class InAppPurchaseManager: NSObject {
return self.productsPromise.get()
}
public func finishAllTransactions() {
let paymentQueue = SKPaymentQueue.default()
let transactions = paymentQueue.transactions
for transaction in transactions {
paymentQueue.finishTransaction(transaction)
}
}
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseState, PurchaseError> {
let payment = SKPayment(product: product.skProduct)
SKPaymentQueue.default().add(payment)
@ -159,6 +167,18 @@ extension InAppPurchaseManager: SKProductsRequestDelegate {
}
}
private func getReceiptData() -> Data? {
var receiptData: Data?
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
} catch {
Logger.shared.log("InAppPurchaseManager", "Couldn't read receipt data with error: \(error.localizedDescription)")
}
}
return receiptData
}
extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
@ -167,23 +187,28 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
let transactionState: TransactionState?
switch transaction.transactionState {
case .purchased:
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
if let transactionIdentifier = transaction.transactionIdentifier {
self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier).start(error: { error in
}, completed: {
queue.finishTransaction(transaction)
}),
forKey: transaction.transactionIdentifier ?? ""
)
if transaction.original == nil {
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
if let transactionIdentifier = transaction.transactionIdentifier {
self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in
queue.finishTransaction(transaction)
}, completed: {
queue.finishTransaction(transaction)
}),
forKey: transaction.transactionIdentifier ?? ""
)
}
} else {
transactionState = nil
queue.finishTransaction(transaction)
}
case .restored:
transactionState = .restored(transactionId: transaction.transactionIdentifier)
if let transactionIdentifier = transaction.transactionIdentifier {
transactionState = .restored(transactionId: transaction.original?.transactionIdentifier)
if let transactionIdentifier = transaction.original?.transactionIdentifier {
self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier).start(error: { error in
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: true).start(error: { _ in
queue.finishTransaction(transaction)
}, completed: {
queue.finishTransaction(transaction)
}),

View File

@ -358,6 +358,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
updatedTheme = item.presentationData.theme
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.accountContext.currentAppConfiguration.with { $0 })
var credibilityIconImage: UIImage?
var credibilityIconOffset: CGFloat = 4.0
if let peer = item.peer {
@ -369,6 +371,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
credibilityIconOffset = 2.0
} else if peer.isVerified {
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
credibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
}

View File

@ -603,6 +603,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
var updatedLabelBadgeImage: UIImage?
var currentCredibilityIconImage: UIImage?
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
} else {
@ -612,7 +614,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if item.peer.isPremium {
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
}

View File

@ -622,9 +622,16 @@ private struct ChannelVisibilityControllerState: Equatable {
}
}
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool) -> [ChannelVisibilityEntry] {
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool) -> [ChannelVisibilityEntry] {
var entries: [ChannelVisibilityEntry] = []
let isInitialSetup: Bool
if case .initialSetup = mode {
isInitialSetup = true
} else {
isInitialSetup = false
}
if let peer = view.peers[view.peerId] as? TelegramChannel {
var isGroup = false
if case .group = peer.info {
@ -721,36 +728,30 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
}
}
switch selectedType {
case .publicChannel:
var displayAvailability = false
if peer.addressName == nil {
displayAvailability = publicChannelsToRevoke != nil && !(publicChannelsToRevoke!.isEmpty)
}
if !"".isEmpty && displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke {
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
}
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
} else {
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0)
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
if let publicChannelsToRevoke = publicChannelsToRevoke {
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
}
} else {
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
}
} else {
switch selectedType {
case .publicChannel:
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName))
if let status = state.addressNameValidationStatus {
let text: String
@ -804,7 +805,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_PublicLink_Info))
} else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
}
}
} else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
}
@ -815,199 +816,203 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
if isGroup {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
} else {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
}
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
var isDiscussion = false
if let cachedData = view.cachedData as? CachedChannelData, case .known = cachedData.linkedDiscussionPeerId {
isDiscussion = true
}
if isGroup && (selectedType == .publicChannel || isDiscussion) {
if isDiscussion {
entries.append(.joinToSendHeader(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Title.uppercased()))
entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone))
entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members))
}
if !isDiscussion || joinToSend == .members {
entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers))
entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo))
}
}
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingChannelInfo) : (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled : presentationData.strings.Group_Setup_ForwardingChannelInfoDisabled)))
} else if let peer = view.peers[view.peerId] as? TelegramGroup {
switch mode {
case .revokeNames:
if let publicChannelsToRevoke = publicChannelsToRevoke {
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
}
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
}
case .privateLink:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
case .generic, .initialSetup:
let selectedType: CurrentChannelType
if let current = state.selectedType {
selectedType = current
} else {
selectedType = .privateChannel
}
let currentAddressName: String
if let current = state.editingPublicLinkText {
currentAddressName = current
} else {
currentAddressName = ""
}
entries.append(.typeHeader(presentationData.theme, presentationData.strings.Group_Setup_TypeHeader.uppercased()))
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
switch selectedType {
case .publicChannel:
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
case .privateChannel:
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
}
switch selectedType {
case .publicChannel:
let displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty)
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
if isGroup {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
} else {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
}
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
if displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke {
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
var isDiscussion = false
if let cachedData = view.cachedData as? CachedChannelData, case .known = cachedData.linkedDiscussionPeerId {
isDiscussion = true
}
if isGroup && (selectedType == .publicChannel || isDiscussion) {
if isDiscussion {
entries.append(.joinToSendHeader(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Title.uppercased()))
entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone))
entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members))
}
if !isDiscussion || joinToSend == .members {
entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers))
entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo))
}
}
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingChannelInfo) : (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled : presentationData.strings.Group_Setup_ForwardingChannelInfoDisabled)))
}
} else if let peer = view.peers[view.peerId] as? TelegramGroup {
if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0)
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
if let publicChannelsToRevoke = publicChannelsToRevoke {
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
}
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
}
} else {
switch mode {
case .revokeNames:
break
case .privateLink:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
case .generic, .initialSetup:
let selectedType: CurrentChannelType
if let current = state.selectedType {
selectedType = current
} else {
selectedType = .privateChannel
}
let currentAddressName: String
if let current = state.editingPublicLinkText {
currentAddressName = current
} else {
currentAddressName = ""
}
entries.append(.typeHeader(presentationData.theme, presentationData.strings.Group_Setup_TypeHeader.uppercased()))
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
switch selectedType {
case .publicChannel:
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
case .privateChannel:
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
}
switch selectedType {
case .publicChannel:
let displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty)
if displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke {
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
}
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
} else {
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
}
} else {
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
}
} else {
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName))
if let status = state.addressNameValidationStatus {
let text: String
switch status {
case let .invalidFormat(error):
switch error {
case .startsWithDigit:
text = presentationData.strings.Group_Username_InvalidStartsWithNumber
case .startsWithUnderscore:
text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore
case .endsWithUnderscore:
text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore
case .tooShort:
text = presentationData.strings.Group_Username_InvalidTooShort
case .invalidCharacters:
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName))
if let status = state.addressNameValidationStatus {
let text: String
switch status {
case let .invalidFormat(error):
switch error {
case .startsWithDigit:
text = presentationData.strings.Group_Username_InvalidStartsWithNumber
case .startsWithUnderscore:
text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore
case .endsWithUnderscore:
text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore
case .tooShort:
text = presentationData.strings.Group_Username_InvalidTooShort
case .invalidCharacters:
text = presentationData.strings.Channel_Username_InvalidCharacters
}
case let .availability(availability):
switch availability {
case .available:
text = presentationData.strings.Channel_Username_UsernameIsAvailable(currentAddressName).string
case .invalid:
text = presentationData.strings.Channel_Username_InvalidCharacters
case .taken:
text = presentationData.strings.Channel_Username_InvalidTaken
}
case let .availability(availability):
switch availability {
case .available:
text = presentationData.strings.Channel_Username_UsernameIsAvailable(currentAddressName).string
case .invalid:
text = presentationData.strings.Channel_Username_InvalidCharacters
case .taken:
text = presentationData.strings.Channel_Username_InvalidTaken
case .checking:
text = presentationData.strings.Channel_Username_CheckingUsername
}
case .checking:
text = presentationData.strings.Channel_Username_CheckingUsername
entries.append(.publicLinkStatus(presentationData.theme, text, status))
}
entries.append(.publicLinkStatus(presentationData.theme, text, status))
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
}
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
}
case .privateChannel:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
case .privateChannel:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
}
}
let forwardingEnabled: Bool
if let enabled = state.forwardingEnabled {
forwardingEnabled = enabled
} else {
if peer.flags.contains(.copyProtectionEnabled) {
forwardingEnabled = false
let forwardingEnabled: Bool
if let enabled = state.forwardingEnabled {
forwardingEnabled = enabled
} else {
forwardingEnabled = true
if peer.flags.contains(.copyProtectionEnabled) {
forwardingEnabled = false
} else {
forwardingEnabled = true
}
}
entries.append(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled))
}
entries.append(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled))
}
return entries
@ -1079,16 +1084,24 @@ public enum ChannelVisibilityControllerMode {
case initialSetup
case generic
case privateLink
case revokeNames
case revokeNames([Peer])
}
public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil) -> ViewController {
public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil, revokedPeerAddressName: ((PeerId) -> Void)? = nil) -> ViewController {
let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelVisibilityControllerState())
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let adminedPublicChannels = Promise<[Peer]?>()
if case let .revokeNames(peers) = mode {
adminedPublicChannels.set(.single(peers))
} else {
adminedPublicChannels.set(context.engine.peers.adminedPublicChannels(scope: .all)
|> map(Optional.init))
}
let peersDisablingAddressNameAssignment = Promise<[Peer]?>()
peersDisablingAddressNameAssignment.set(.single(nil) |> then(context.engine.peers.channelAddressNameAssignmentAvailability(peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in
if case .addressNameLimitReached = result {
@ -1133,8 +1146,42 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
actionsDisposable.add(toggleRequestToJoinDisposable)
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
updateState { state in
return state.withUpdatedSelectedType(type)
if type == .publicChannel {
let _ = combineLatest(
queue: Queue.mainQueue(),
adminedPublicChannels.get() |> filter { $0 != nil } |> take(1),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
)
).start(next: { peers, accountPeer, data in
let (limits, premiumLimits) = data
let isPremium = accountPeer?.isPremium ?? false
if let peers = peers {
let count = Int32(peers.count)
if count < limits.maxPublicLinksCount || (count < premiumLimits.maxPublicLinksCount && isPremium) {
updateState { state in
return state.withUpdatedSelectedType(type)
}
} else {
let controller = channelVisibilityController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, mode: .revokeNames(peers), upgradedToSupergroup: { _, _ in }, revokedPeerAddressName: { revokedPeerId in
let updatedPublicChannels = peers.filter { $0.id != revokedPeerId }
adminedPublicChannels.set(.single(updatedPublicChannels) |> then(
context.engine.peers.adminedPublicChannels(scope: .all) |> map(Optional.init))
)
})
controller.navigationPresentation = .modal
pushControllerImpl?(controller)
}
} else {
}
})
} else {
updateState { state in
return state.withUpdatedSelectedType(type)
}
}
}, updatePublicLinkText: { currentText, text in
if text.isEmpty {
@ -1179,11 +1226,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
return state.withUpdatedRevokingPeerId(nil)
}
}, completed: {
peersDisablingAddressNameAssignment.set(.single([]) |> delay(0.2, queue: Queue.mainQueue()) |> afterNext { _ in
updateState { state in
return state.withUpdatedRevokingPeerId(nil)
}
})
revokedPeerAddressName?(peerId)
dismissImpl?()
}))
}, copyLink: { invite in
UIPasteboard.general.string = invite.link
@ -1353,7 +1397,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
presentationData,
statePromise.get() |> deliverOnMainQueue,
peerView,
peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue,
adminedPublicChannels.get(),
importersContext,
importersState.get(),
context.engine.data.get(
@ -1373,7 +1417,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
var rightNavigationButton: ItemListNavigationButton?
if case .revokeNames = mode {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
let controller = PremiumIntroScreen(context: context, source: .publicLinks)
pushControllerImpl?(controller)
})
} else {
if let peer = peer as? TelegramChannel {
var doneEnabled = true
@ -1400,7 +1447,14 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
}
}
rightNavigationButton = ItemListNavigationButton(content: .text(mode == .initialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
let isInitialSetup: Bool
if case .initialSetup = mode {
isInitialSetup = true
} else {
isInitialSetup = false
}
rightNavigationButton = ItemListNavigationButton(content: .text(isInitialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String?
updateState { state in
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData)
@ -1618,14 +1672,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke, !crossfade {
crossfade = hadNamesToRevoke != hasNamesToRevoke
}
if hasNamesToRevoke && selectedType == .publicChannel {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
let controller = PremiumIntroScreen(context: context, source: .publicLinks)
pushControllerImpl?(controller)
})
}
if let hadNamesToRevoke = hadNamesToRevoke {
animateChanges = hadNamesToRevoke != hasNamesToRevoke
}
@ -1645,7 +1692,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
title = presentationData.strings.Premium_LimitReached
}
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium)
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled)
var focusItemTag: ItemListItemTag?
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {

View File

@ -81,6 +81,9 @@ private let boldTextFont = Font.semibold(15.0)
class IncreaseLimitHeaderItemNode: ListViewItemNode {
private var hostView: ComponentHostView<Empty>?
private var params: (AnyComponent<Empty>, CGSize, ListViewItemNodeLayout, CGSize)?
private let titleNode: TextNode
private let textNode: TextNode
@ -109,6 +112,19 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
let hostView = ComponentHostView<Empty>()
self.hostView = hostView
self.view.addSubview(hostView)
if let (component, containerSize, layout, textSize) = self.params {
let size = hostView.update(
transition: .immediate,
component: component,
environment: {},
containerSize: containerSize
)
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
let textSpacing: CGFloat = -6.0
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textSize.width) / 2.0), y: size.height + textSpacing), size: textSize)
}
}
func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
@ -161,31 +177,36 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
]
}
let component = AnyComponent(PremiumLimitDisplayComponent(
inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
activeColors: gradientColors,
inactiveTitle: item.strings.Premium_Free,
inactiveValue: item.count > item.limit ? "\(item.limit)" : "",
inactiveTitleColor: item.theme.list.itemPrimaryTextColor,
activeTitle: item.strings.Premium_Premium,
activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)",
activeTitleColor: .white,
badgeIconName: badgeIconName,
badgeText: "\(item.count)",
badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount),
isPremiumDisabled: item.isPremiumDisabled
))
let containerSize = CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
let _ = textApply()
if let hostView = strongSelf.hostView {
let size = hostView.update(
transition: .immediate,
component: AnyComponent(PremiumLimitDisplayComponent(
inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
activeColors: gradientColors,
inactiveTitle: item.strings.Premium_Free,
inactiveValue: item.count > item.limit ? "\(item.limit)" : "",
inactiveTitleColor: item.theme.list.itemPrimaryTextColor,
activeTitle: item.strings.Premium_Premium,
activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)",
activeTitleColor: .white,
badgeIconName: badgeIconName,
badgeText: "\(item.count)",
badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount),
isPremiumDisabled: item.isPremiumDisabled
)),
component: component,
environment: {},
containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
containerSize: containerSize
)
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size)
}
strongSelf.params = (component, containerSize, layout, textLayout.size)
}
})
}

View File

@ -807,7 +807,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
super.init()
self.disposable = (context.engine.data.get(
self.disposable = (context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Configuration.App(),
TelegramEngine.EngineData.Item.Configuration.PremiumPromo()
)
@ -841,6 +841,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
})
let _ = updatePremiumPromoConfigurationOnce(account: context.account).start()
let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers)
self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey])
|> deliverOnMainQueue).start(next: { [weak self] views in
@ -1362,9 +1364,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.disposable = combineLatest(
queue: Queue.mainQueue(),
availableProducts,
context.account.postbox.peerView(id: context.account.peerId)
|> map { view -> Bool in
return view.peers[view.peerId]?.isPremium ?? false
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
return peer?.isPremium ?? false
},
otherPeerName
).start(next: { [weak self] products, isPremium, otherPeerName in
@ -1418,6 +1420,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}, completed: { [weak self] in
if let strongSelf = self {
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start()
strongSelf.isPremium = true
strongSelf.updated(transition: .easeInOut(duration: 0.25))
strongSelf.completion()

View File

@ -49,7 +49,7 @@ final class ReactionsCarouselComponent: Component {
public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
if self.node == nil {
if self.node == nil && !component.reactions.isEmpty {
let node = ReactionCarouselNode(
context: component.context,
theme: component.theme,

View File

@ -45,7 +45,7 @@ final class StickersCarouselComponent: Component {
public func update(component: StickersCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
if self.node == nil {
if self.node == nil && !component.stickers.isEmpty {
let node = StickersCarouselNode(
context: component.context,
stickers: component.stickers

View File

@ -6227,11 +6227,13 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.payments {
static func assignAppStoreTransaction(transactionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func assignAppStoreTransaction(flags: Int32, transactionId: String, receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1654235439)
buffer.appendInt32(267129798)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(transactionId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("transactionId", String(describing: transactionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
serializeBytes(receipt, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("flags", String(describing: flags)), ("transactionId", String(describing: transactionId)), ("receipt", String(describing: receipt))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
@ -6381,21 +6383,6 @@ public extension Api.functions.payments {
})
}
}
public extension Api.functions.payments {
static func restoreAppStoreReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-2132923705)
serializeBytes(receipt, buffer: buffer, boxed: false)
return (FunctionDescription(name: "payments.restoreAppStoreReceipt", parameters: [("receipt", String(describing: receipt))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.payments {
static func restorePlayMarketReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()

View File

@ -495,6 +495,8 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = 58.0 + params.leftInset
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var titleIconsWidth: CGFloat = 0.0
var currentCredibilityIconImage: UIImage?
var credibilityIconOffset: CGFloat = 0.0
@ -507,6 +509,9 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
} else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
}
if let currentCredibilityIconImage = currentCredibilityIconImage {

View File

@ -829,6 +829,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let verticalInset: CGFloat = 8.0
let verticalOffset: CGFloat = 0.0
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
var titleIconsWidth: CGFloat = 0.0
var currentCredibilityIconImage: UIImage?
var credibilityIconOffset: CGFloat = 0.0
@ -841,6 +843,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
}
if let currentCredibilityIconImage = currentCredibilityIconImage {

View File

@ -8,26 +8,18 @@ public enum AssignAppStoreTransactionError {
case generic
}
func _internal_assignAppStoreTransaction(account: Account, transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> {
return account.network.request(Api.functions.payments.assignAppStoreTransaction(transactionId: transactionId))
func _internal_assignAppStoreTransaction(account: Account, transactionId: String, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {
var flags: Int32 = 0
if restore {
flags |= (1 << 0)
}
return account.network.request(Api.functions.payments.assignAppStoreTransaction(flags: flags, transactionId: transactionId, receipt: Buffer(data: receipt)))
|> mapError { _ -> AssignAppStoreTransactionError in
return .generic
}
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
account.stateManager.addUpdates(updates)
return account.postbox.peerView(id: account.peerId)
|> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium {
return SignalTakeAction(passthrough: false, complete: true)
} else {
return SignalTakeAction(passthrough: false, complete: false)
}
})
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
}
return .complete()
}
}

View File

@ -1,3 +1,4 @@
import Foundation
import SwiftSignalKit
import Postbox
@ -37,8 +38,8 @@ public extension TelegramEngine {
return _internal_clearBotPaymentInfo(network: self.account.network, info: info)
}
public func assignAppStoreTransaction(transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId)
public func assignAppStoreTransaction(transactionId: String, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId, receipt: receipt, restore: restore)
}
public func canPurchasePremium() -> Signal<Bool, NoError> {

View File

@ -2300,6 +2300,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let themeUpdated = self.presentationData?.theme !== presentationData.theme
self.presentationData = presentationData
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
let credibilityIcon: CredibilityIcon
if let peer = peer {
if peer.isFake {
@ -2308,7 +2310,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
credibilityIcon = .scam
} else if peer.isVerified {
credibilityIcon = .verified
} else if peer.isPremium {
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .premium
} else {
credibilityIcon = .none