mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '3bacb78f084eda80c52a9245a94c980073fbcab8'
# Conflicts: # submodules/DebugSettingsUI/Sources/DebugController.swift
This commit is contained in:
commit
a6292e752a
@ -890,7 +890,7 @@ public protocol AccountContext: AnyObject {
|
|||||||
|
|
||||||
public struct PremiumConfiguration {
|
public struct PremiumConfiguration {
|
||||||
public static var defaultValue: PremiumConfiguration {
|
public static var defaultValue: PremiumConfiguration {
|
||||||
return PremiumConfiguration(isPremiumDisabled: false)
|
return PremiumConfiguration(isPremiumDisabled: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public let isPremiumDisabled: Bool
|
public let isPremiumDisabled: Bool
|
||||||
|
@ -1034,14 +1034,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in
|
let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in
|
||||||
skipStateAnimation = true
|
skipStateAnimation = true
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var updatedState = state
|
||||||
state.additionallyIncludePeers = filter.data?.includePeers.peers ?? []
|
updatedState.additionallyIncludePeers = filter.data?.includePeers.peers ?? []
|
||||||
state.additionallyExcludePeers = filter.data?.excludePeers ?? []
|
updatedState.additionallyExcludePeers = filter.data?.excludePeers ?? []
|
||||||
state.includeCategories = filter.data?.categories ?? []
|
updatedState.includeCategories = filter.data?.categories ?? []
|
||||||
state.excludeRead = filter.data?.excludeRead ?? false
|
updatedState.excludeRead = filter.data?.excludeRead ?? false
|
||||||
state.excludeMuted = filter.data?.excludeMuted ?? false
|
updatedState.excludeMuted = filter.data?.excludeMuted ?? false
|
||||||
state.excludeArchived = filter.data?.excludeArchived ?? false
|
updatedState.excludeArchived = filter.data?.excludeArchived ?? false
|
||||||
return state
|
return updatedState
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
@ -1125,7 +1125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
var found = false
|
var found = false
|
||||||
for i in 0 ..< filters.count {
|
for i in 0 ..< filters.count {
|
||||||
if filters[i].id == updatedFilter.id, case let .filter(_, _, _, data) = filters[i] {
|
if filters[i].id == updatedFilter.id, case let .filter(_, _, _, data) = filters[i] {
|
||||||
var updatedData = data
|
var updatedData = updatedFilter.data ?? data
|
||||||
var includePeers = updatedData.includePeers
|
var includePeers = updatedData.includePeers
|
||||||
includePeers.setPeers(state.additionallyIncludePeers)
|
includePeers.setPeers(state.additionallyIncludePeers)
|
||||||
updatedData.includePeers = includePeers
|
updatedData.includePeers = includePeers
|
||||||
|
@ -1484,6 +1484,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme)
|
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 !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId {
|
||||||
if displayAsMessage {
|
if displayAsMessage {
|
||||||
switch item.content {
|
switch item.content {
|
||||||
@ -1495,7 +1496,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
} else if peer.isPremium {
|
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
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)
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
} else if peer.isPremium {
|
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,14 +106,19 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
|
|
||||||
let initialVelocity = !delta.isZero ? velocity.y / delta : 0.0
|
let initialVelocity = !delta.isZero ? velocity.y / delta : 0.0
|
||||||
|
|
||||||
targetContentOffset.pointee = scrollView.contentOffset
|
let currentContentOffset = scrollView.contentOffset
|
||||||
|
targetContentOffset.pointee = currentContentOffset
|
||||||
if velocity.y > 300.0 {
|
if velocity.y > 300.0 {
|
||||||
self.animateOut(initialVelocity: initialVelocity, completion: {
|
self.animateOut(initialVelocity: initialVelocity, completion: {
|
||||||
self.dismiss?(false)
|
self.dismiss?(false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if contentOffset < scrollView.contentSize.height * 0.333 {
|
if contentOffset < scrollView.contentSize.height * 0.1 {
|
||||||
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.contentInset.top), animated: true)
|
if contentOffset < 0.0 {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.contentInset.top), animated: true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.animateOut(initialVelocity: initialVelocity, completion: {
|
self.animateOut(initialVelocity: initialVelocity, completion: {
|
||||||
self.dismiss?(false)
|
self.dismiss?(false)
|
||||||
|
@ -563,6 +563,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
var currentCredibilityIconImage: UIImage?
|
var currentCredibilityIconImage: UIImage?
|
||||||
switch item.peer {
|
switch item.peer {
|
||||||
case let .peer(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)
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
} else if peer.isPremium {
|
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ swift_library(
|
|||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
"//submodules/GZip:GZip",
|
"//submodules/GZip:GZip",
|
||||||
"//third-party/ZipArchive:ZipArchive",
|
"//third-party/ZipArchive:ZipArchive",
|
||||||
|
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -15,6 +15,7 @@ import AccountContext
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import ZipArchive
|
import ZipArchive
|
||||||
import WebKit
|
import WebKit
|
||||||
|
import InAppPurchaseManager
|
||||||
|
|
||||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
@ -87,13 +88,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case inlineStickers(Bool)
|
case inlineStickers(Bool)
|
||||||
case localTranscription(Bool)
|
case localTranscription(Bool)
|
||||||
case enableReactionOverrides(Bool)
|
case enableReactionOverrides(Bool)
|
||||||
case snow(Bool)
|
|
||||||
case playerEmbedding(Bool)
|
case playerEmbedding(Bool)
|
||||||
case playlistPlayback(Bool)
|
case playlistPlayback(Bool)
|
||||||
case voiceConference
|
case voiceConference
|
||||||
case preferredVideoCodec(Int, String, String?, Bool)
|
case preferredVideoCodec(Int, String, String?, Bool)
|
||||||
case disableVideoAspectScaling(Bool)
|
case disableVideoAspectScaling(Bool)
|
||||||
case enableVoipTcp(Bool)
|
case enableVoipTcp(Bool)
|
||||||
|
case resetInAppPurchases(PresentationTheme)
|
||||||
case hostInfo(PresentationTheme, String)
|
case hostInfo(PresentationTheme, String)
|
||||||
case versionInfo(PresentationTheme)
|
case versionInfo(PresentationTheme)
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||||
return DebugControllerSection.experiments.rawValue
|
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, .enableReactionOverrides, .snow:
|
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, . enableReactionOverrides, .resetInAppPurchases:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .preferredVideoCodec:
|
case .preferredVideoCodec:
|
||||||
return DebugControllerSection.videoExperiments.rawValue
|
return DebugControllerSection.videoExperiments.rawValue
|
||||||
@ -190,7 +191,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 32
|
return 32
|
||||||
case .enableReactionOverrides:
|
case .enableReactionOverrides:
|
||||||
return 33
|
return 33
|
||||||
case .snow:
|
case .resetInAppPurchases:
|
||||||
return 34
|
return 34
|
||||||
case .playerEmbedding:
|
case .playerEmbedding:
|
||||||
return 35
|
return 35
|
||||||
@ -980,21 +981,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
settings.enableReactionOverrides = value
|
settings.enableReactionOverrides = value
|
||||||
if !value {
|
if !value {
|
||||||
settings.accountReactionEffectOverrides.removeAll()
|
settings.accountReactionEffectOverrides.removeAll()
|
||||||
|
settings.accountStickerEffectOverrides.removeAll()
|
||||||
}
|
}
|
||||||
return PreferencesEntry(settings)
|
return PreferencesEntry(settings)
|
||||||
})
|
})
|
||||||
}).start()
|
}).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):
|
case let .playerEmbedding(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
@ -1051,6 +1043,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).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):
|
case let .hostInfo(_, string):
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
|
||||||
case .versionInfo:
|
case .versionInfo:
|
||||||
@ -1115,6 +1111,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
if case .internal = sharedContext.applicationBindings.appBuildType {
|
if case .internal = sharedContext.applicationBindings.appBuildType {
|
||||||
entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides))
|
entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides))
|
||||||
}
|
}
|
||||||
|
entries.append(.resetInAppPurchases(presentationData.theme))
|
||||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,14 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
return self.productsPromise.get()
|
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> {
|
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseState, PurchaseError> {
|
||||||
let payment = SKPayment(product: product.skProduct)
|
let payment = SKPayment(product: product.skProduct)
|
||||||
SKPaymentQueue.default().add(payment)
|
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 {
|
extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
||||||
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||||
for transaction in transactions {
|
for transaction in transactions {
|
||||||
@ -167,11 +187,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
let transactionState: TransactionState?
|
let transactionState: TransactionState?
|
||||||
switch transaction.transactionState {
|
switch transaction.transactionState {
|
||||||
case .purchased:
|
case .purchased:
|
||||||
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
|
let transactionIdentifier = transaction.original?.transactionIdentifier ?? transaction.transactionIdentifier
|
||||||
if let transactionIdentifier = transaction.transactionIdentifier {
|
transactionState = .purchased(transactionId: transactionIdentifier)
|
||||||
|
if let transactionIdentifier = transactionIdentifier {
|
||||||
self.disposableSet.set(
|
self.disposableSet.set(
|
||||||
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier).start(error: { error in
|
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in
|
||||||
|
queue.finishTransaction(transaction)
|
||||||
}, completed: {
|
}, completed: {
|
||||||
queue.finishTransaction(transaction)
|
queue.finishTransaction(transaction)
|
||||||
}),
|
}),
|
||||||
@ -179,11 +200,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case .restored:
|
case .restored:
|
||||||
transactionState = .restored(transactionId: transaction.transactionIdentifier)
|
let transactionIdentifier = transaction.original?.transactionIdentifier ?? transaction.transactionIdentifier
|
||||||
if let transactionIdentifier = transaction.transactionIdentifier {
|
transactionState = .restored(transactionId: transactionIdentifier)
|
||||||
|
if let transactionIdentifier = transactionIdentifier {
|
||||||
self.disposableSet.set(
|
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: {
|
}, completed: {
|
||||||
queue.finishTransaction(transaction)
|
queue.finishTransaction(transaction)
|
||||||
}),
|
}),
|
||||||
|
@ -358,6 +358,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||||||
updatedTheme = item.presentationData.theme
|
updatedTheme = item.presentationData.theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.accountContext.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
var credibilityIconImage: UIImage?
|
var credibilityIconImage: UIImage?
|
||||||
var credibilityIconOffset: CGFloat = 4.0
|
var credibilityIconOffset: CGFloat = 4.0
|
||||||
if let peer = item.peer {
|
if let peer = item.peer {
|
||||||
@ -369,6 +371,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||||||
credibilityIconOffset = 2.0
|
credibilityIconOffset = 2.0
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
|
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
|
||||||
|
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
|
credibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,6 +603,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
var updatedLabelBadgeImage: UIImage?
|
var updatedLabelBadgeImage: UIImage?
|
||||||
var currentCredibilityIconImage: 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 {
|
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -612,7 +614,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||||
} else if item.peer.isVerified {
|
} else if item.peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
} else if item.peer.isPremium {
|
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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] = []
|
var entries: [ChannelVisibilityEntry] = []
|
||||||
|
|
||||||
|
let isInitialSetup: Bool
|
||||||
|
if case .initialSetup = mode {
|
||||||
|
isInitialSetup = true
|
||||||
|
} else {
|
||||||
|
isInitialSetup = false
|
||||||
|
}
|
||||||
|
|
||||||
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
||||||
var isGroup = false
|
var isGroup = false
|
||||||
if case .group = peer.info {
|
if case .group = peer.info {
|
||||||
@ -721,36 +728,30 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selectedType {
|
if case .revokeNames = mode {
|
||||||
case .publicChannel:
|
let count = Int32(publicChannelsToRevoke?.count ?? 0)
|
||||||
var displayAvailability = false
|
entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
|
||||||
if peer.addressName == nil {
|
|
||||||
displayAvailability = publicChannelsToRevoke != nil && !(publicChannelsToRevoke!.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !"".isEmpty && displayAvailability {
|
if let publicChannelsToRevoke = publicChannelsToRevoke {
|
||||||
if let publicChannelsToRevoke = publicChannelsToRevoke {
|
var index: Int32 = 0
|
||||||
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
|
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
|
||||||
|
var lhsDate: Int32 = 0
|
||||||
var index: Int32 = 0
|
var rhsDate: Int32 = 0
|
||||||
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
|
if let lhs = lhs as? TelegramChannel {
|
||||||
var lhsDate: Int32 = 0
|
lhsDate = lhs.creationDate
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
} 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))
|
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentAddressName))
|
||||||
if let status = state.addressNameValidationStatus {
|
if let status = state.addressNameValidationStatus {
|
||||||
let text: String
|
let text: String
|
||||||
@ -815,199 +816,203 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
|||||||
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
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:
|
case .privateChannel:
|
||||||
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selectedType {
|
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:
|
case .publicChannel:
|
||||||
let displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty)
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
|
||||||
|
case .privateChannel:
|
||||||
|
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
|
||||||
|
}
|
||||||
|
|
||||||
if displayAvailability {
|
switch selectedType {
|
||||||
if let publicChannelsToRevoke = publicChannelsToRevoke {
|
case .publicChannel:
|
||||||
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
|
let displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty)
|
||||||
|
|
||||||
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
|
if displayAvailability {
|
||||||
var index: Int32 = 0
|
if let publicChannelsToRevoke = publicChannelsToRevoke {
|
||||||
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
|
// entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount))
|
||||||
var lhsDate: Int32 = 0
|
|
||||||
var rhsDate: Int32 = 0
|
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false))
|
||||||
if let lhs = lhs as? TelegramChannel {
|
var index: Int32 = 0
|
||||||
lhsDate = lhs.creationDate
|
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 {
|
} else {
|
||||||
rhsDate = rhs.creationDate
|
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp, true))
|
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName))
|
||||||
}
|
if let status = state.addressNameValidationStatus {
|
||||||
} else {
|
let text: String
|
||||||
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentAddressName))
|
switch status {
|
||||||
if let status = state.addressNameValidationStatus {
|
case let .invalidFormat(error):
|
||||||
let text: String
|
switch error {
|
||||||
switch status {
|
case .startsWithDigit:
|
||||||
case let .invalidFormat(error):
|
text = presentationData.strings.Group_Username_InvalidStartsWithNumber
|
||||||
switch error {
|
case .startsWithUnderscore:
|
||||||
case .startsWithDigit:
|
text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore
|
||||||
text = presentationData.strings.Group_Username_InvalidStartsWithNumber
|
case .endsWithUnderscore:
|
||||||
case .startsWithUnderscore:
|
text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore
|
||||||
text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore
|
case .tooShort:
|
||||||
case .endsWithUnderscore:
|
text = presentationData.strings.Group_Username_InvalidTooShort
|
||||||
text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore
|
case .invalidCharacters:
|
||||||
case .tooShort:
|
text = presentationData.strings.Channel_Username_InvalidCharacters
|
||||||
text = presentationData.strings.Group_Username_InvalidTooShort
|
}
|
||||||
case .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
|
text = presentationData.strings.Channel_Username_InvalidCharacters
|
||||||
|
case .taken:
|
||||||
|
text = presentationData.strings.Channel_Username_InvalidTaken
|
||||||
}
|
}
|
||||||
case let .availability(availability):
|
case .checking:
|
||||||
switch availability {
|
text = presentationData.strings.Channel_Username_CheckingUsername
|
||||||
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
|
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
|
||||||
}
|
}
|
||||||
|
case .privateChannel:
|
||||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
|
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
|
||||||
}
|
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||||
case .privateChannel:
|
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
|
||||||
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
|
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
||||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
switch mode {
|
||||||
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
|
case .initialSetup, .revokeNames:
|
||||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
break
|
||||||
switch mode {
|
case .generic, .privateLink:
|
||||||
case .initialSetup, .revokeNames:
|
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
||||||
break
|
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
||||||
case .generic, .privateLink:
|
}
|
||||||
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
|
}
|
||||||
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let forwardingEnabled: Bool
|
let forwardingEnabled: Bool
|
||||||
if let enabled = state.forwardingEnabled {
|
if let enabled = state.forwardingEnabled {
|
||||||
forwardingEnabled = enabled
|
forwardingEnabled = enabled
|
||||||
} else {
|
|
||||||
if peer.flags.contains(.copyProtectionEnabled) {
|
|
||||||
forwardingEnabled = false
|
|
||||||
} else {
|
} 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(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased()))
|
||||||
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
|
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(.forwardingInfo(presentationData.theme, forwardingEnabled ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
@ -1079,16 +1084,24 @@ public enum ChannelVisibilityControllerMode {
|
|||||||
case initialSetup
|
case initialSetup
|
||||||
case generic
|
case generic
|
||||||
case privateLink
|
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 statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: ChannelVisibilityControllerState())
|
let stateValue = Atomic(value: ChannelVisibilityControllerState())
|
||||||
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
|
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
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]?>()
|
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
|
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 {
|
if case .addressNameLimitReached = result {
|
||||||
@ -1133,8 +1146,42 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
actionsDisposable.add(toggleRequestToJoinDisposable)
|
actionsDisposable.add(toggleRequestToJoinDisposable)
|
||||||
|
|
||||||
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
|
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
|
||||||
updateState { state in
|
if type == .publicChannel {
|
||||||
return state.withUpdatedSelectedType(type)
|
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
|
}, updatePublicLinkText: { currentText, text in
|
||||||
if text.isEmpty {
|
if text.isEmpty {
|
||||||
@ -1179,11 +1226,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
return state.withUpdatedRevokingPeerId(nil)
|
return state.withUpdatedRevokingPeerId(nil)
|
||||||
}
|
}
|
||||||
}, completed: {
|
}, completed: {
|
||||||
peersDisablingAddressNameAssignment.set(.single([]) |> delay(0.2, queue: Queue.mainQueue()) |> afterNext { _ in
|
revokedPeerAddressName?(peerId)
|
||||||
updateState { state in
|
dismissImpl?()
|
||||||
return state.withUpdatedRevokingPeerId(nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
}, copyLink: { invite in
|
}, copyLink: { invite in
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
@ -1348,12 +1392,14 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||||
let signal = combineLatest(
|
let signal = combineLatest(
|
||||||
presentationData,
|
presentationData,
|
||||||
statePromise.get() |> deliverOnMainQueue,
|
statePromise.get() |> deliverOnMainQueue,
|
||||||
peerView,
|
peerView,
|
||||||
peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue,
|
adminedPublicChannels.get(),
|
||||||
importersContext,
|
importersContext,
|
||||||
importersState.get(),
|
importersState.get(),
|
||||||
context.engine.data.get(
|
context.engine.data.get(
|
||||||
@ -1373,7 +1419,12 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
|
|
||||||
var rightNavigationButton: ItemListNavigationButton?
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
if case .revokeNames = mode {
|
if case .revokeNames = mode {
|
||||||
|
if !premiumConfiguration.isPremiumDisabled {
|
||||||
|
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
|
||||||
|
let controller = PremiumIntroScreen(context: context, source: .publicLinks)
|
||||||
|
pushControllerImpl?(controller)
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let peer = peer as? TelegramChannel {
|
if let peer = peer as? TelegramChannel {
|
||||||
var doneEnabled = true
|
var doneEnabled = true
|
||||||
@ -1400,7 +1451,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?
|
var updatedAddressNameValue: String?
|
||||||
updateState { state in
|
updateState { state in
|
||||||
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData)
|
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData)
|
||||||
@ -1619,13 +1677,6 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
crossfade = hadNamesToRevoke != hasNamesToRevoke
|
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 {
|
if let hadNamesToRevoke = hadNamesToRevoke {
|
||||||
animateChanges = hadNamesToRevoke != hasNamesToRevoke
|
animateChanges = hadNamesToRevoke != hasNamesToRevoke
|
||||||
}
|
}
|
||||||
@ -1645,7 +1696,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
|||||||
title = presentationData.strings.Premium_LimitReached
|
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 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?
|
var focusItemTag: ItemListItemTag?
|
||||||
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
||||||
|
@ -81,6 +81,9 @@ private let boldTextFont = Font.semibold(15.0)
|
|||||||
|
|
||||||
class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
||||||
private var hostView: ComponentHostView<Empty>?
|
private var hostView: ComponentHostView<Empty>?
|
||||||
|
|
||||||
|
private var params: (AnyComponent<Empty>, CGSize, ListViewItemNodeLayout, CGSize)?
|
||||||
|
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
|
|
||||||
@ -109,6 +112,23 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||||||
let hostView = ComponentHostView<Empty>()
|
let hostView = ComponentHostView<Empty>()
|
||||||
self.hostView = hostView
|
self.hostView = hostView
|
||||||
self.view.addSubview(hostView)
|
self.view.addSubview(hostView)
|
||||||
|
|
||||||
|
if let (component, containerSize, layout, textSize) = self.params {
|
||||||
|
var 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)
|
||||||
|
|
||||||
|
if let item = self.item, item.isPremiumDisabled {
|
||||||
|
size.height -= 54.0
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
@ -128,7 +148,11 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + textSpacing + textLayout.size.height + bottomInset)
|
var contentSize = CGSize(width: params.width, height: topInset + badgeHeight + textSpacing + textLayout.size.height + bottomInset)
|
||||||
|
if item.isPremiumDisabled {
|
||||||
|
contentSize.height -= 54.0
|
||||||
|
}
|
||||||
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
@ -161,31 +185,39 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if let hostView = strongSelf.hostView {
|
let component = AnyComponent(PremiumLimitDisplayComponent(
|
||||||
let size = hostView.update(
|
inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
|
||||||
transition: .immediate,
|
activeColors: gradientColors,
|
||||||
component: AnyComponent(PremiumLimitDisplayComponent(
|
inactiveTitle: item.strings.Premium_Free,
|
||||||
inactiveColor: item.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
|
inactiveValue: item.count > item.limit ? "\(item.limit)" : "",
|
||||||
activeColors: gradientColors,
|
inactiveTitleColor: item.theme.list.itemPrimaryTextColor,
|
||||||
inactiveTitle: item.strings.Premium_Free,
|
activeTitle: item.strings.Premium_Premium,
|
||||||
inactiveValue: item.count > item.limit ? "\(item.limit)" : "",
|
activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)",
|
||||||
inactiveTitleColor: item.theme.list.itemPrimaryTextColor,
|
activeTitleColor: .white,
|
||||||
activeTitle: item.strings.Premium_Premium,
|
badgeIconName: badgeIconName,
|
||||||
activeValue: item.count >= item.premiumCount ? "" : "\(item.premiumCount)",
|
badgeText: "\(item.count)",
|
||||||
activeTitleColor: .white,
|
badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount),
|
||||||
badgeIconName: badgeIconName,
|
isPremiumDisabled: item.isPremiumDisabled
|
||||||
badgeText: "\(item.count)",
|
))
|
||||||
badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount),
|
let containerSize = CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
|
||||||
isPremiumDisabled: item.isPremiumDisabled
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
|
|
||||||
)
|
|
||||||
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
|
|
||||||
|
|
||||||
let _ = textApply()
|
let _ = textApply()
|
||||||
|
|
||||||
|
if let hostView = strongSelf.hostView {
|
||||||
|
var size = hostView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: component,
|
||||||
|
environment: {},
|
||||||
|
containerSize: containerSize
|
||||||
|
)
|
||||||
|
if item.isPremiumDisabled {
|
||||||
|
size.height -= 54.0
|
||||||
|
}
|
||||||
|
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size)
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size)
|
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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -279,6 +279,8 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
|
|||||||
|
|
||||||
var previousPeersWereEmpty = true
|
var previousPeersWereEmpty = true
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||||
let signal = combineLatest(
|
let signal = combineLatest(
|
||||||
queue: Queue.mainQueue(),
|
queue: Queue.mainQueue(),
|
||||||
@ -337,7 +339,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
|
|||||||
}
|
}
|
||||||
|
|
||||||
let footerItem: IncreaseLimitFooterItem?
|
let footerItem: IncreaseLimitFooterItem?
|
||||||
if state.isSearching && state.selectedPeers.count == 0 {
|
if (state.isSearching || premiumConfiguration.isPremiumDisabled) && state.selectedPeers.count == 0 {
|
||||||
footerItem = nil
|
footerItem = nil
|
||||||
} else {
|
} else {
|
||||||
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: {
|
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: {
|
||||||
|
BIN
submodules/PremiumUI/Resources/badge.scn
Normal file
BIN
submodules/PremiumUI/Resources/badge.scn
Normal file
Binary file not shown.
BIN
submodules/PremiumUI/Resources/badgestar.png
Normal file
BIN
submodules/PremiumUI/Resources/badgestar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
@ -12,6 +12,7 @@ import RadialStatusNode
|
|||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
import TelegramUniversalVideoContent
|
import TelegramUniversalVideoContent
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
private let phoneSize = CGSize(width: 262.0, height: 539.0)
|
private let phoneSize = CGSize(width: 262.0, height: 539.0)
|
||||||
private var phoneBorderImage = {
|
private var phoneBorderImage = {
|
||||||
@ -32,7 +33,30 @@ private var phoneBorderImage = {
|
|||||||
try? drawSvgPath(context, path: "M222.923,4.08542 C217.697,3.65815 211.263,3.65823 203.378,3.65833 H58.6219 C50.7366,3.65823 44.3026,3.65815 39.0768,4.08542 C33.6724,4.52729 28.8133,5.46834 24.2823,7.77863 C17.1741,11.4029 11.395,17.1861 7.77325,24.2992 C5.46457,28.8334 4.52418,33.6959 4.08262,39.1041 C3.65565,44.3336 3.65573,50.7721 3.65583,58.6628 V480.337 C3.65573,488.228 3.65565,494.666 4.08262,499.896 C4.52418,505.304 5.46457,510.167 7.77325,514.701 C11.395,521.814 17.1741,527.597 24.2823,531.221 C28.8133,533.532 33.6724,534.473 39.0768,534.915 C44.3028,535.342 50.737,535.342 58.6226,535.342 H203.377 C211.263,535.342 217.697,535.342 222.923,534.915 C228.328,534.473 233.187,533.532 237.718,531.221 C244.826,527.597 250.605,521.814 254.227,514.701 C256.535,510.167 257.476,505.304 257.917,499.896 C258.344,494.667 258.344,488.228 258.344,480.338 V58.6617 C258.344,50.7714 258.344,44.3333 257.917,39.1041 C257.476,33.6959 256.535,28.8334 254.227,24.2992 C250.605,17.1861 244.826,11.4029 237.718,7.77863 C233.187,5.46834 228.328,4.52729 222.923,4.08542 Z ")
|
try? drawSvgPath(context, path: "M222.923,4.08542 C217.697,3.65815 211.263,3.65823 203.378,3.65833 H58.6219 C50.7366,3.65823 44.3026,3.65815 39.0768,4.08542 C33.6724,4.52729 28.8133,5.46834 24.2823,7.77863 C17.1741,11.4029 11.395,17.1861 7.77325,24.2992 C5.46457,28.8334 4.52418,33.6959 4.08262,39.1041 C3.65565,44.3336 3.65573,50.7721 3.65583,58.6628 V480.337 C3.65573,488.228 3.65565,494.666 4.08262,499.896 C4.52418,505.304 5.46457,510.167 7.77325,514.701 C11.395,521.814 17.1741,527.597 24.2823,531.221 C28.8133,533.532 33.6724,534.473 39.0768,534.915 C44.3028,535.342 50.737,535.342 58.6226,535.342 H203.377 C211.263,535.342 217.697,535.342 222.923,534.915 C228.328,534.473 233.187,533.532 237.718,531.221 C244.826,527.597 250.605,521.814 254.227,514.701 C256.535,510.167 257.476,505.304 257.917,499.896 C258.344,494.667 258.344,488.228 258.344,480.338 V58.6617 C258.344,50.7714 258.344,44.3333 257.917,39.1041 C257.476,33.6959 256.535,28.8334 254.227,24.2992 C250.605,17.1861 244.826,11.4029 237.718,7.77863 C233.187,5.46834 228.328,4.52729 222.923,4.08542 Z ")
|
||||||
|
|
||||||
context.setBlendMode(.clear)
|
context.setBlendMode(.clear)
|
||||||
try? drawSvgPath(context, path: "M12.1861,59.0217 C12.1861,42.6306 12.1861,34.4351 15.3737,28.1746 C18.1777,22.6676 22.6519,18.1904 28.1549,15.3844 C34.4111,12.1945 42.6009,12.1945 58.9805,12.1945 H76.6868 L76.8652,12.1966 C78.1834,12.2201 79.0316,12.4428 79.7804,12.8418 C80.5733,13.2644 81.1963,13.8848 81.6226,14.6761 C81.9735,15.3276 82.1908,16.0553 82.2606,17.1064 C82.3128,22.5093 82.9306,24.5829 84.0474,26.6727 C85.2157,28.8587 86.9301,30.5743 89.1145,31.7434 C91.299,32.9124 93.4658,33.535 99.441,33.535 H162.561 C168.537,33.535 170.703,32.9124 172.888,31.7434 C175.072,30.5743 176.787,28.8587 177.955,26.6727 C179.072,24.5829 179.69,22.5093 179.742,17.1051 C179.812,16.0553 180.029,15.3276 180.38,14.6761 C180.806,13.8848 181.429,13.2644 182.222,12.8418 C182.971,12.4428 183.819,12.2201 185.137,12.1966 L185.316,12.1945 H203.02 C219.399,12.1945 227.589,12.1945 233.845,15.3844 C239.348,18.1904 243.822,22.6676 246.626,28.1746 C249.814,34.4351 249.814,42.6306 249.814,59.0217 V479.978 C249.814,496.369 249.814,504.565 246.626,510.825 C243.822,516.332 239.348,520.81 233.845,523.615 C227.589,526.805 219.399,526.805 203.02,526.805 H58.9805 C42.6009,526.805 34.4111,526.805 28.1549,523.615 C22.6519,520.81 18.1777,516.332 15.3737,510.825 C12.1861,504.565 12.1861,496.369 12.1861,479.978 V59.0217 Z")
|
try? drawSvgPath(context, path: "M12.1861,59.0217 C12.1861,42.6306 12.1861,34.4351 15.3737,28.1746 C18.1777,22.6676 22.6519,18.1904 28.1549,15.3844 C34.4111,12.1945 42.6009,12.1945 58.9805,12.1945 H76.6868 L76.8652,12.1966 C78.1834,12.2201 79.0316,12.4428 79.7804,12.8418 C80.5733,13.2644 81.1963,13.8848 81.6226,14.6761 C81.9735,15.3276 82.1908,16.0553 82.2606,17.1064 C82.3128,22.5093 82.9306,24.5829 84.0474,26.6727 C85.2157,28.8587 86.9301,30.5743 89.1145,31.7434 C91.299,32.9124 93.4658,33.535 99.441,33.535 H162.561 C168.537,33.535 170.703,32.9124 172.888,31.7434 C175.072,30.5743 176.787,28.8587 177.955,26.6727 C179.072,24.5829 179.69,22.5093 179.742,17.1051 C179.812,16.0553 180.029,15.3276 180.38,14.6761 C180.806,13.8848 181.429,13.2644 182.222,12.8418 C182.971,12.4428 183.819,12.2201 185.137,12.1966 L185.316,12.1945 H203.02 C219.399,12.1945 227.589,12.1945 233.845,15.3844 C239.348,18.1904 243.822,22.6676 246.626,28.1746 C249.814,34.4351 249.814,42.6306 249.814,59.0217 V479.978 C249.814,496.369 249.814,504.565 246.626,510.825 C243.822,516.332 239.348,520.81 233.845,523.615 C227.589,526.805 219.399,526.805 203.02,526.805 H58.9805 C42.6009,526.805 34.4111,526.805 28.1549,523.615 C22.6519,520.81 18.1777,516.332 15.3737,510.825 C12.1861,504.565 12.1861,496.369 12.1861,479.978 V59.0217 Z ")
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var phoneBorderMaskImage = {
|
||||||
|
generateImage(phoneSize, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
context.setLineWidth(2.0)
|
||||||
|
|
||||||
|
context.translateBy(x: 12.0, y: 12.0 - UIScreenPixel)
|
||||||
|
|
||||||
|
try? drawSvgPath(context, path: "M1.17188,47.3156 C1.17188,39.1084 1.17265,33.013 1.56706,28.1857 C1.96052,23.3701 2.74071,19.9044 4.25094,16.9404 C6.95936,11.6248 11.2811,7.30311 16.5966,4.59469 C19.5606,3.08446 23.0263,2.30427 27.842,1.91081 C32.6693,1.5164 38.7646,1.51562 46.9719,1.51562 H64.6745 H64.6803 L64.8409,1.51754 C64.8419,1.51756 64.8429,1.51758 64.8439,1.5176 C66.0418,1.53925 66.7261,1.73731 67.3042,2.04519 L67.7736,1.16377 L67.3042,2.04519 C67.9232,2.37486 68.4036,2.8529 68.7364,3.47024 C69.0069,3.97209 69.1915,4.54972 69.2551,5.46352 C69.3102,10.9333 69.9419,13.1793 71.16,15.457 C72.4216,17.816 74.2789,19.6733 76.6379,20.9349 C79.0269,22.2126 81.3803,22.8438 87.4372,22.8438 H150.565 C156.622,22.8438 158.976,22.2126 161.364,20.9349 C163.723,19.6733 165.581,17.816 166.842,15.457 C168.061,13.1793 168.692,10.9334 168.747,5.46231 C168.811,4.54985 168.995,3.97217 169.266,3.47025 C169.599,2.8529 170.079,2.37486 170.698,2.04519 C171.276,1.7373 171.961,1.53925 173.159,1.5176 C173.16,1.51758 173.161,1.51756 173.162,1.51754 L173.322,1.51562 H173.328 H191.028 C199.235,1.51562 205.331,1.5164 210.158,1.91081 C214.974,2.30427 218.439,3.08446 221.403,4.59469 C226.719,7.30311 231.041,11.6248 233.749,16.9404 C235.259,19.9044 236.039,23.3701 236.433,28.1857 C236.827,33.013 236.828,39.1084 236.828,47.3156 V468.028 C236.828,476.235 236.827,482.331 236.433,487.158 C236.039,491.974 235.259,495.439 233.749,498.403 C231.041,503.719 226.719,508.041 221.403,510.749 C218.439,512.259 214.974,513.039 210.158,513.433 C205.331,513.827 199.235,513.828 191.028,513.828 H46.9719 C38.7646,513.828 32.6693,513.827 27.842,513.433 C23.0263,513.039 19.5606,512.259 16.5966,510.749 C11.2811,508.041 6.95936,503.719 4.25094,498.403 C2.74071,495.439 1.96052,491.974 1.56706,487.158 C1.17265,482.331 1.17188,476.235 1.17188,468.028 V47.3156 S ")
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var starMaskImage = {
|
||||||
|
return generateImage(CGSize(width: 88.0, height: 84.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
|
|
||||||
|
try? drawSvgPath(context, path: "M41.7419,71.1897 L22.1639,83.1833 C20.1282,84.4304 17.4669,83.7911 16.2198,81.7553 C15.6107,80.7611 15.4291,79.5629 15.7162,78.4328 L18.7469,66.504 C19.8409,62.1979 22.7876,58.5983 26.7928,56.6754 L48.1514,46.4207 C49.1472,45.9426 49.5668,44.7479 49.0887,43.7521 C48.7016,42.9457 47.826,42.4945 46.9446,42.6471 L23.1697,46.7631 C18.3368,47.5998 13.3807,46.2653 9.62146,43.1149 L2.11077,36.8207 C0.28097,35.2873 0.0407101,32.5609 1.57413,30.7311 C2.31994,29.8411 3.39241,29.2886 4.55001,29.198 L27.4974,27.4022 C29.1186,27.2753 30.5314,26.2494 31.1537,24.747 L40.0064,3.37722 C40.9201,1.17161 43.4488,0.124313 45.6544,1.03801 C46.7135,1.47673 47.5549,2.31816 47.9936,3.37722 L56.8463,24.747 C57.4686,26.2494 58.8815,27.2753 60.5026,27.4022 L83.5761,29.2079 C85.9562,29.3942 87.7347,31.4746 87.5484,33.8547 C87.4588,34.9997 86.9172,36.0619 86.0433,36.807 L68.4461,51.809 C67.2073,52.8651 66.6669,54.5275 67.0478,56.1102 L72.4577,78.5841 C73.0165,80.9052 71.5878,83.2397 69.2667,83.7985 C68.1515,84.0669 66.9752,83.8811 65.997,83.2818 L46.2581,71.1897 C44.8724,70.3408 43.1277,70.3408 41.7419,71.1897 Z ")
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -42,8 +66,17 @@ private final class PhoneView: UIView {
|
|||||||
let overlayView: UIView
|
let overlayView: UIView
|
||||||
let borderView: UIImageView
|
let borderView: UIImageView
|
||||||
|
|
||||||
|
let backShimmerView: UIView
|
||||||
|
let backShimmerEffectView: ShimmerEffectForegroundView
|
||||||
|
let backShimmerFadeView: UIView
|
||||||
|
|
||||||
|
let frontShimmerView: UIView
|
||||||
|
let shimmerEffectView: ShimmerEffectForegroundView
|
||||||
|
let shimmerMaskView: UIView
|
||||||
|
let shimmerBorderView: UIImageView
|
||||||
|
let shimmerStarView: UIImageView
|
||||||
|
|
||||||
fileprivate var videoNode: UniversalVideoNode?
|
fileprivate var videoNode: UniversalVideoNode?
|
||||||
private let statusNode: RadialStatusNode
|
|
||||||
|
|
||||||
var playbackStatus: Signal<MediaPlayerStatus?, NoError> {
|
var playbackStatus: Signal<MediaPlayerStatus?, NoError> {
|
||||||
return self.playbackStatusPromise.get()
|
return self.playbackStatusPromise.get()
|
||||||
@ -75,16 +108,43 @@ private final class PhoneView: UIView {
|
|||||||
|
|
||||||
self.borderView = UIImageView(image: phoneBorderImage)
|
self.borderView = UIImageView(image: phoneBorderImage)
|
||||||
|
|
||||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6), enableBlur: false)
|
self.shimmerMaskView = UIView()
|
||||||
self.statusNode.transitionToState(.none)
|
self.shimmerBorderView = UIImageView(image: phoneBorderMaskImage)
|
||||||
self.statusNode.isUserInteractionEnabled = false
|
self.shimmerStarView = UIImageView(image: starMaskImage)
|
||||||
|
|
||||||
|
self.backShimmerView = UIView()
|
||||||
|
self.backShimmerView.alpha = 0.0
|
||||||
|
|
||||||
|
self.backShimmerEffectView = ShimmerEffectForegroundView()
|
||||||
|
self.backShimmerFadeView = UIView()
|
||||||
|
self.backShimmerFadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.2)
|
||||||
|
|
||||||
|
self.frontShimmerView = UIView()
|
||||||
|
self.frontShimmerView.alpha = 0.0
|
||||||
|
self.shimmerEffectView = ShimmerEffectForegroundView()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.contentContainerView)
|
self.addSubview(self.contentContainerView)
|
||||||
self.contentContainerView.addSubview(self.statusNode.view)
|
|
||||||
self.contentContainerView.addSubview(self.overlayView)
|
self.contentContainerView.addSubview(self.overlayView)
|
||||||
|
self.contentContainerView.addSubview(self.backShimmerView)
|
||||||
self.addSubview(self.borderView)
|
self.addSubview(self.borderView)
|
||||||
|
self.addSubview(self.frontShimmerView)
|
||||||
|
|
||||||
|
self.backShimmerView.addSubview(self.backShimmerEffectView)
|
||||||
|
self.backShimmerView.addSubview(self.backShimmerFadeView)
|
||||||
|
|
||||||
|
self.shimmerMaskView.addSubview(self.shimmerBorderView)
|
||||||
|
self.shimmerMaskView.addSubview(self.shimmerStarView)
|
||||||
|
|
||||||
|
self.frontShimmerView.mask = self.shimmerMaskView
|
||||||
|
self.frontShimmerView.addSubview(self.shimmerEffectView)
|
||||||
|
|
||||||
|
self.backShimmerEffectView.update(backgroundColor: .clear, foregroundColor: UIColor.white.withAlphaComponent(0.35), gradientSize: 70.0, globalTimeOffset: true, duration: 3.0, horizontal: true)
|
||||||
|
self.backShimmerEffectView.layer.compositingFilter = "overlayBlendMode"
|
||||||
|
|
||||||
|
self.shimmerEffectView.update(backgroundColor: .clear, foregroundColor: UIColor.white.withAlphaComponent(0.65), gradientSize: 70.0, globalTimeOffset: true, duration: 3.0, horizontal: true)
|
||||||
|
self.shimmerEffectView.layer.compositingFilter = "overlayBlendMode"
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -143,22 +203,23 @@ private final class PhoneView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updatePlaybackStatus() {
|
private func updatePlaybackStatus() {
|
||||||
var state: RadialStatusNodeState?
|
var isDisplayingProgress = false
|
||||||
if let playbackStatus = self.playbackStatusValue {
|
if let playbackStatus = self.playbackStatusValue {
|
||||||
if case let .buffering(initial, _, progress, _) = playbackStatus.status, initial || !progress.isZero {
|
if case let .buffering(initial, _, progress, _) = playbackStatus.status, initial || !progress.isZero {
|
||||||
let adjustedProgress = max(progress, 0.027)
|
isDisplayingProgress = true
|
||||||
state = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: false, animateRotation: true)
|
|
||||||
} else if playbackStatus.status == .playing {
|
} else if playbackStatus.status == .playing {
|
||||||
state = RadialStatusNodeState.none
|
isDisplayingProgress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let state = state {
|
let targetAlpha = isDisplayingProgress ? 1.0 : 0.0
|
||||||
self.statusNode.transitionToState(state, completion: { [weak self] in
|
if self.frontShimmerView.alpha != targetAlpha {
|
||||||
if case .none = state {
|
let sourceAlpha = self.frontShimmerView.alpha
|
||||||
self?.statusNode.removeFromSupernode()
|
self.frontShimmerView.alpha = targetAlpha
|
||||||
}
|
self.frontShimmerView.layer.animateAlpha(from: sourceAlpha, to: targetAlpha, duration: 0.2)
|
||||||
})
|
|
||||||
|
self.backShimmerView.alpha = targetAlpha
|
||||||
|
self.backShimmerView.layer.animateAlpha(from: sourceAlpha, to: targetAlpha, duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,24 +247,40 @@ private final class PhoneView: UIView {
|
|||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
if let phoneImage = self.borderView.image {
|
if let phoneImage = self.borderView.image {
|
||||||
self.borderView.frame = CGRect(origin: .zero, size: phoneImage.size)
|
let phoneBounds = CGRect(origin: .zero, size: phoneImage.size)
|
||||||
|
self.borderView.frame = phoneBounds
|
||||||
|
|
||||||
self.contentContainerView.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: phoneImage.size.width - 24.0, height: phoneImage.size.height - 24.0))
|
self.contentContainerView.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: phoneImage.size.width - 24.0, height: phoneImage.size.height - 24.0))
|
||||||
self.overlayView.frame = self.contentContainerView.bounds
|
self.overlayView.frame = self.contentContainerView.bounds
|
||||||
|
|
||||||
|
let videoSize = CGSize(width: self.contentContainerView.frame.width, height: 354.0)
|
||||||
if let videoNode = self.videoNode {
|
if let videoNode = self.videoNode {
|
||||||
let videoSize = CGSize(width: self.contentContainerView.frame.width, height: 354.0)
|
|
||||||
videoNode.view.frame = CGRect(origin: CGPoint(x: 0.0, y: self.position == .top ? 0.0 : self.contentContainerView.frame.height - videoSize.height), size: videoSize)
|
videoNode.view.frame = CGRect(origin: CGPoint(x: 0.0, y: self.position == .top ? 0.0 : self.contentContainerView.frame.height - videoSize.height), size: videoSize)
|
||||||
videoNode.updateLayout(size: videoSize, transition: .immediate)
|
videoNode.updateLayout(size: videoSize, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
let notchHeight: CGFloat = 20.0
|
self.backShimmerView.frame = phoneBounds.insetBy(dx: -12.0, dy: -12.0)
|
||||||
let radialStatusSize: CGFloat = 40.0
|
self.backShimmerEffectView.frame = phoneBounds
|
||||||
self.statusNode.frame = CGRect(x: floor((videoSize.width - radialStatusSize) / 2.0), y: self.position == .top ? notchHeight + floor((videoSize.height - notchHeight - radialStatusSize) / 2.0) : self.contentContainerView.frame.height - videoSize.height + floor((videoSize.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize)
|
self.backShimmerFadeView.frame = phoneBounds
|
||||||
|
|
||||||
|
self.frontShimmerView.frame = phoneBounds
|
||||||
|
self.shimmerEffectView.frame = phoneBounds
|
||||||
|
self.shimmerMaskView.frame = phoneBounds
|
||||||
|
self.shimmerBorderView.frame = phoneBounds
|
||||||
|
|
||||||
|
self.backShimmerEffectView.updateAbsoluteRect(CGRect(origin: CGPoint(x: phoneBounds.width * 12.0, y: 0.0), size: phoneBounds.size), within: CGSize(width: phoneBounds.width * 25.0, height: phoneBounds.height))
|
||||||
|
self.shimmerEffectView.updateAbsoluteRect(CGRect(origin: CGPoint(x: phoneBounds.width * 12.0, y: 0.0), size: phoneBounds.size), within: CGSize(width: phoneBounds.width * 25.0, height: phoneBounds.height))
|
||||||
|
|
||||||
|
let notchHeight: CGFloat = 20.0
|
||||||
|
if let starImage = self.shimmerStarView.image {
|
||||||
|
let starSize = starImage.size
|
||||||
|
self.shimmerStarView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((phoneImage.size.width - starSize.width) / 2.0), y: self.position == .top ? notchHeight + floor((videoSize.height - notchHeight - starSize.height) / 2.0) : self.contentContainerView.frame.height - videoSize.height + floor((videoSize.height - starSize.height) / 2.0)), size: starSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class StarsView: UIView {
|
private final class FasterStarsView: UIView {
|
||||||
private let sceneView: SCNView
|
private let sceneView: SCNView
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
@ -289,6 +366,41 @@ private final class StarsView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class BadgeStarsView: UIView {
|
||||||
|
private let sceneView: SCNView
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
|
self.sceneView.backgroundColor = .clear
|
||||||
|
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
|
||||||
|
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||||
|
}
|
||||||
|
self.sceneView.isUserInteractionEnabled = false
|
||||||
|
self.sceneView.preferredFramesPerSecond = 60
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.alpha = 0.0
|
||||||
|
|
||||||
|
self.addSubview(self.sceneView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setVisible(_ visible: Bool) {
|
||||||
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
|
||||||
|
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.75 : 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class PhoneDemoComponent: Component {
|
final class PhoneDemoComponent: Component {
|
||||||
typealias EnvironmentType = DemoPageEnvironment
|
typealias EnvironmentType = DemoPageEnvironment
|
||||||
|
|
||||||
@ -297,21 +409,27 @@ final class PhoneDemoComponent: Component {
|
|||||||
case bottom
|
case bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BackgroundDecoration {
|
||||||
|
case none
|
||||||
|
case fasterStars
|
||||||
|
case badgeStars
|
||||||
|
}
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let position: Position
|
let position: Position
|
||||||
let videoFile: TelegramMediaFile?
|
let videoFile: TelegramMediaFile?
|
||||||
let hasStars: Bool
|
let decoration: BackgroundDecoration
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
position: PhoneDemoComponent.Position,
|
position: PhoneDemoComponent.Position,
|
||||||
videoFile: TelegramMediaFile?,
|
videoFile: TelegramMediaFile?,
|
||||||
hasStars: Bool = false
|
decoration: BackgroundDecoration = .none
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.position = position
|
self.position = position
|
||||||
self.videoFile = videoFile
|
self.videoFile = videoFile
|
||||||
self.hasStars = hasStars
|
self.decoration = decoration
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PhoneDemoComponent, rhs: PhoneDemoComponent) -> Bool {
|
public static func ==(lhs: PhoneDemoComponent, rhs: PhoneDemoComponent) -> Bool {
|
||||||
@ -324,7 +442,7 @@ final class PhoneDemoComponent: Component {
|
|||||||
if lhs.videoFile != rhs.videoFile {
|
if lhs.videoFile != rhs.videoFile {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.hasStars != rhs.hasStars {
|
if lhs.decoration != rhs.decoration {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -346,9 +464,11 @@ final class PhoneDemoComponent: Component {
|
|||||||
|
|
||||||
private let starsContainerView: UIView
|
private let starsContainerView: UIView
|
||||||
private let containerView: UIView
|
private let containerView: UIView
|
||||||
private var starsView: StarsView?
|
|
||||||
private let phoneView: PhoneView
|
private let phoneView: PhoneView
|
||||||
|
|
||||||
|
private var fasterStarsView: FasterStarsView?
|
||||||
|
private var badgeStarsView: BadgeStarsView?
|
||||||
|
|
||||||
private var starsDisposable: Disposable?
|
private var starsDisposable: Disposable?
|
||||||
|
|
||||||
public var ready: Signal<Bool, NoError> {
|
public var ready: Signal<Bool, NoError> {
|
||||||
@ -393,26 +513,32 @@ final class PhoneDemoComponent: Component {
|
|||||||
self.starsContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
|
self.starsContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
|
||||||
self.phoneView.bounds = CGRect(origin: .zero, size: phoneSize)
|
self.phoneView.bounds = CGRect(origin: .zero, size: phoneSize)
|
||||||
|
|
||||||
if component.hasStars {
|
switch component.decoration {
|
||||||
if self.starsView == nil {
|
case .none:
|
||||||
let starsView = StarsView(frame: self.starsContainerView.bounds)
|
break
|
||||||
self.starsView = starsView
|
case .fasterStars:
|
||||||
self.starsContainerView.addSubview(starsView)
|
if self.fasterStarsView == nil {
|
||||||
|
let starsView = FasterStarsView(frame: self.starsContainerView.bounds)
|
||||||
|
self.fasterStarsView = starsView
|
||||||
|
self.starsContainerView.addSubview(starsView)
|
||||||
|
|
||||||
self.starsDisposable = (self.phoneView.playbackStatus
|
self.starsDisposable = (self.phoneView.playbackStatus
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
if let strongSelf = self, let status = status {
|
if let strongSelf = self, let status = status {
|
||||||
if status.timestamp > 8.0 {
|
if status.timestamp > 8.0 {
|
||||||
strongSelf.starsView?.stopAnimation()
|
strongSelf.fasterStarsView?.stopAnimation()
|
||||||
} else if status.timestamp > 0.85 {
|
} else if status.timestamp > 0.85 {
|
||||||
strongSelf.starsView?.startAnimation()
|
strongSelf.fasterStarsView?.startAnimation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
case .badgeStars:
|
||||||
} else if let starsView = self.starsView {
|
if self.badgeStarsView == nil {
|
||||||
self.starsView = nil
|
let starsView = BadgeStarsView(frame: self.starsContainerView.bounds)
|
||||||
starsView.removeFromSuperview()
|
self.badgeStarsView = starsView
|
||||||
|
self.starsContainerView.addSubview(starsView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)
|
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)
|
||||||
@ -435,7 +561,8 @@ final class PhoneDemoComponent: Component {
|
|||||||
let isCentral = environment[DemoPageEnvironment.self].isCentral
|
let isCentral = environment[DemoPageEnvironment.self].isCentral
|
||||||
self.isCentral = isCentral
|
self.isCentral = isCentral
|
||||||
|
|
||||||
self.starsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
|
self.fasterStarsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
|
||||||
|
self.badgeStarsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
|
||||||
|
|
||||||
self.phoneView.center = CGPoint(x: availableSize.width / 2.0 + phoneX, y: phoneY)
|
self.phoneView.center = CGPoint(x: availableSize.width / 2.0 + phoneX, y: phoneY)
|
||||||
self.phoneView.screenRotation = mappedPosition * -0.7
|
self.phoneView.screenRotation = mappedPosition * -0.7
|
||||||
@ -448,7 +575,7 @@ final class PhoneDemoComponent: Component {
|
|||||||
self.phoneView.play()
|
self.phoneView.play()
|
||||||
} else if !isVisible {
|
} else if !isVisible {
|
||||||
self.phoneView.reset()
|
self.phoneView.reset()
|
||||||
self.starsView?.stopAnimation()
|
self.fasterStarsView?.stopAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = transition.userData(DemoAnimateInTransition.self), abs(mappedPosition) < .ulpOfOne {
|
if let _ = transition.userData(DemoAnimateInTransition.self), abs(mappedPosition) < .ulpOfOne {
|
||||||
|
@ -342,12 +342,15 @@ private final class DemoPagerComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var ignoreContentOffsetChange = false
|
||||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
guard let component = self.component else {
|
guard let component = self.component, !self.ignoreContentOffsetChange else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.ignoreContentOffsetChange = true
|
||||||
let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate)
|
let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate)
|
||||||
|
self.ignoreContentOffsetChange = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: DemoPagerComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
func update(component: DemoPagerComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
@ -740,7 +743,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
position: .top,
|
position: .top,
|
||||||
videoFile: configuration.videos["faster_download"],
|
videoFile: configuration.videos["faster_download"],
|
||||||
hasStars: true
|
decoration: .fasterStars
|
||||||
)),
|
)),
|
||||||
title: strings.Premium_FasterSpeed,
|
title: strings.Premium_FasterSpeed,
|
||||||
text: strings.Premium_FasterSpeedInfo,
|
text: strings.Premium_FasterSpeedInfo,
|
||||||
@ -845,7 +848,8 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
content: AnyComponent(PhoneDemoComponent(
|
content: AnyComponent(PhoneDemoComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
position: .top,
|
position: .top,
|
||||||
videoFile: configuration.videos["profile_badge"]
|
videoFile: configuration.videos["profile_badge"],
|
||||||
|
decoration: .badgeStars
|
||||||
)),
|
)),
|
||||||
title: strings.Premium_Badge,
|
title: strings.Premium_Badge,
|
||||||
text: strings.Premium_BadgeInfo,
|
text: strings.Premium_BadgeInfo,
|
||||||
|
@ -807,7 +807,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.disposable = (context.engine.data.get(
|
self.disposable = (context.engine.data.subscribe(
|
||||||
TelegramEngine.EngineData.Item.Configuration.App(),
|
TelegramEngine.EngineData.Item.Configuration.App(),
|
||||||
TelegramEngine.EngineData.Item.Configuration.PremiumPromo()
|
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)
|
let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers)
|
||||||
self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey])
|
self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey])
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] views in
|
|> deliverOnMainQueue).start(next: { [weak self] views in
|
||||||
@ -1187,11 +1189,11 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
},
|
},
|
||||||
tapAction: { attributes, _ in
|
tapAction: { attributes, _ in
|
||||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
||||||
let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||||
if url.hasPrefix("https://") {
|
if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
|
||||||
|
controller.context.sharedContext.applicationBindings.openSubscriptions()
|
||||||
|
} else if url.hasPrefix("https://") {
|
||||||
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
||||||
} else if url == "cancel" {
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
let context = controller.context
|
let context = controller.context
|
||||||
let signal: Signal<ResolvedUrl, NoError>?
|
let signal: Signal<ResolvedUrl, NoError>?
|
||||||
@ -1362,9 +1364,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
self.disposable = combineLatest(
|
self.disposable = combineLatest(
|
||||||
queue: Queue.mainQueue(),
|
queue: Queue.mainQueue(),
|
||||||
availableProducts,
|
availableProducts,
|
||||||
context.account.postbox.peerView(id: context.account.peerId)
|
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|> map { view -> Bool in
|
|> map { peer -> Bool in
|
||||||
return view.peers[view.peerId]?.isPremium ?? false
|
return peer?.isPremium ?? false
|
||||||
},
|
},
|
||||||
otherPeerName
|
otherPeerName
|
||||||
).start(next: { [weak self] products, isPremium, otherPeerName in
|
).start(next: { [weak self] products, isPremium, otherPeerName in
|
||||||
@ -1418,6 +1420,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
}, completed: { [weak self] in
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start()
|
||||||
strongSelf.isPremium = true
|
strongSelf.isPremium = true
|
||||||
strongSelf.updated(transition: .easeInOut(duration: 0.25))
|
strongSelf.updated(transition: .easeInOut(duration: 0.25))
|
||||||
strongSelf.completion()
|
strongSelf.completion()
|
||||||
|
@ -796,7 +796,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
case .accounts:
|
case .accounts:
|
||||||
let limit = 3
|
let limit = 3
|
||||||
let premiumLimit = component.count + 1
|
let premiumLimit = limit + 1
|
||||||
iconName = "Premium/Account"
|
iconName = "Premium/Account"
|
||||||
badgeText = "\(component.count)"
|
badgeText = "\(component.count)"
|
||||||
string = component.count >= premiumLimit ? strings.Premium_MaxAccountsFinalText("\(premiumLimit)").string : strings.Premium_MaxAccountsText("\(limit)").string
|
string = component.count >= premiumLimit ? strings.Premium_MaxAccountsFinalText("\(premiumLimit)").string : strings.Premium_MaxAccountsText("\(limit)").string
|
||||||
@ -805,7 +805,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
if component.count == limit {
|
if component.count == limit {
|
||||||
badgePosition = 0.5
|
badgePosition = 0.5
|
||||||
} else {
|
} else {
|
||||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
badgePosition = min(1.0, CGFloat(component.count) / CGFloat(premiumLimit))
|
||||||
}
|
}
|
||||||
buttonAnimationName = "premium_addone"
|
buttonAnimationName = "premium_addone"
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ final class ReactionsCarouselComponent: Component {
|
|||||||
public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
|
public func update(component: ReactionsCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
|
||||||
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
|
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
|
||||||
|
|
||||||
if self.node == nil {
|
if self.node == nil && !component.reactions.isEmpty {
|
||||||
let node = ReactionCarouselNode(
|
let node = ReactionCarouselNode(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
@ -427,10 +427,17 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var scrollStartPosition: (contentOffset: CGFloat, position: CGFloat)?
|
private var scrollStartPosition: (contentOffset: CGFloat, position: CGFloat, inverse: Bool)?
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
if self.scrollStartPosition == nil {
|
var inverse = false
|
||||||
self.scrollStartPosition = (scrollView.contentOffset.x, self.currentPosition)
|
let tapLocation = scrollView.panGestureRecognizer.location(in: scrollView)
|
||||||
|
if tapLocation.y < scrollView.frame.height / 2.0 {
|
||||||
|
inverse = true
|
||||||
|
}
|
||||||
|
if let scrollStartPosition = self.scrollStartPosition {
|
||||||
|
self.scrollStartPosition = (scrollStartPosition.contentOffset, scrollStartPosition.position, inverse)
|
||||||
|
} else {
|
||||||
|
self.scrollStartPosition = (scrollView.contentOffset.x, self.currentPosition, inverse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,12 +452,15 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.animator = nil
|
self.animator = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else {
|
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition, inverse) = self.scrollStartPosition else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let delta = scrollView.contentOffset.x - startContentOffset
|
let delta = scrollView.contentOffset.x - startContentOffset
|
||||||
let positionDelta = delta * -0.001
|
var positionDelta = delta * -0.001
|
||||||
|
if inverse {
|
||||||
|
positionDelta *= -1.0
|
||||||
|
}
|
||||||
var updatedPosition = startPosition + positionDelta
|
var updatedPosition = startPosition + positionDelta
|
||||||
while updatedPosition >= 1.0 {
|
while updatedPosition >= 1.0 {
|
||||||
updatedPosition -= 1.0
|
updatedPosition -= 1.0
|
||||||
@ -470,12 +480,14 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let size = self.validLayout {
|
if let size = self.validLayout {
|
||||||
|
self.ignoreContentOffsetChange = true
|
||||||
self.updateLayout(size: size, transition: .immediate)
|
self.updateLayout(size: size, transition: .immediate)
|
||||||
|
self.ignoreContentOffsetChange = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||||
guard let (startContentOffset, _) = self.scrollStartPosition, abs(velocity.x) > 0.0 else {
|
guard let (startContentOffset, _, _) = self.scrollStartPosition, abs(velocity.x) > 0.0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ final class StickersCarouselComponent: Component {
|
|||||||
public func update(component: StickersCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
|
public func update(component: StickersCarouselComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
|
||||||
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
|
let isDisplaying = environment[DemoPageEnvironment.self].isDisplaying
|
||||||
|
|
||||||
if self.node == nil {
|
if self.node == nil && !component.stickers.isEmpty {
|
||||||
let node = StickersCarouselNode(
|
let node = StickersCarouselNode(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
stickers: component.stickers
|
stickers: component.stickers
|
||||||
@ -80,7 +80,7 @@ final class StickersCarouselComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let itemSize = CGSize(width: 220.0, height: 220.0)
|
private let itemSize = CGSize(width: 200.0, height: 200.0)
|
||||||
|
|
||||||
private class StickerNode: ASDisplayNode {
|
private class StickerNode: ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -109,7 +109,7 @@ private class StickerNode: ASDisplayNode {
|
|||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
|
|
||||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 240.0, height: 240.0))
|
||||||
|
|
||||||
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||||
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width * 1.6), height: Int(fittedDimensions.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width * 1.6), height: Int(fittedDimensions.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
||||||
@ -242,7 +242,7 @@ private class StickerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
let boundingSize = CGSize(width: 240.0, height: 240.0)
|
let boundingSize = itemSize
|
||||||
|
|
||||||
if let dimensitons = self.file.dimensions {
|
if let dimensitons = self.file.dimensions {
|
||||||
let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
|
let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
|
||||||
|
@ -140,16 +140,18 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|
|||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
).start(next: { accountAndPeer, accountsAndPeers in
|
).start(next: { accountAndPeer, accountsAndPeers in
|
||||||
var maximumAvailableAccounts: Int = 3
|
var maximumAvailableAccounts: Int = 3
|
||||||
if accountAndPeer?.1.isPremium == true {
|
if accountAndPeer?.1.isPremium == true && !context.account.testingEnvironment {
|
||||||
maximumAvailableAccounts = 4
|
maximumAvailableAccounts = 4
|
||||||
}
|
}
|
||||||
var count: Int = 1
|
var count: Int = 1
|
||||||
for (_, peer, _) in accountsAndPeers {
|
for (accountContext, peer, _) in accountsAndPeers {
|
||||||
if peer.isPremium {
|
if !accountContext.account.testingEnvironment {
|
||||||
maximumAvailableAccounts = 4
|
if peer.isPremium {
|
||||||
|
maximumAvailableAccounts = 4
|
||||||
|
}
|
||||||
|
count += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count += accountsAndPeers.count
|
|
||||||
|
|
||||||
if count >= maximumAvailableAccounts {
|
if count >= maximumAvailableAccounts {
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
var replaceImpl: ((ViewController) -> Void)?
|
||||||
|
@ -186,7 +186,8 @@ private func quickReactionSetupControllerEntries(
|
|||||||
availableReactions: AvailableReactions?,
|
availableReactions: AvailableReactions?,
|
||||||
images: [String: (image: UIImage, isAnimation: Bool)],
|
images: [String: (image: UIImage, isAnimation: Bool)],
|
||||||
reactionSettings: ReactionSettings,
|
reactionSettings: ReactionSettings,
|
||||||
state: QuickReactionSetupControllerState
|
state: QuickReactionSetupControllerState,
|
||||||
|
isPremium: Bool
|
||||||
) -> [QuickReactionSetupControllerEntry] {
|
) -> [QuickReactionSetupControllerEntry] {
|
||||||
var entries: [QuickReactionSetupControllerEntry] = []
|
var entries: [QuickReactionSetupControllerEntry] = []
|
||||||
|
|
||||||
@ -210,6 +211,10 @@ private func quickReactionSetupControllerEntries(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isPremium && availableReaction.isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
entries.append(.item(
|
entries.append(.item(
|
||||||
index: index,
|
index: index,
|
||||||
value: availableReaction.value,
|
value: availableReaction.value,
|
||||||
@ -332,10 +337,12 @@ public func quickReactionSetupController(
|
|||||||
statePromise.get(),
|
statePromise.get(),
|
||||||
context.engine.stickers.availableReactions(),
|
context.engine.stickers.availableReactions(),
|
||||||
settings,
|
settings,
|
||||||
images
|
images,
|
||||||
|
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData, state, availableReactions, settings, images -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, availableReactions, settings, images, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
|
let isPremium = accountPeer?.isPremium ?? false
|
||||||
let title: String = presentationData.strings.Settings_QuickReactionSetup_Title
|
let title: String = presentationData.strings.Settings_QuickReactionSetup_Title
|
||||||
|
|
||||||
let entries = quickReactionSetupControllerEntries(
|
let entries = quickReactionSetupControllerEntries(
|
||||||
@ -343,7 +350,8 @@ public func quickReactionSetupController(
|
|||||||
availableReactions: availableReactions,
|
availableReactions: availableReactions,
|
||||||
images: images,
|
images: images,
|
||||||
reactionSettings: settings,
|
reactionSettings: settings,
|
||||||
state: state
|
state: state,
|
||||||
|
isPremium: isPremium
|
||||||
)
|
)
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(
|
let controllerState = ItemListControllerState(
|
||||||
|
@ -800,10 +800,6 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
self.buttonBackgroundNode.layer.cornerCurve = .continuous
|
self.buttonBackgroundNode.layer.cornerCurve = .continuous
|
||||||
}
|
}
|
||||||
|
|
||||||
if gloss {
|
|
||||||
self.setupGloss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder: NSCoder) {
|
required public init(coder: NSCoder) {
|
||||||
@ -1004,8 +1000,10 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
compositingFilter = nil
|
compositingFilter = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: false, duration: 3.0, horizontal: true)
|
let globalTimeOffset = self.icon == nil
|
||||||
borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: false, duration: 3.0, horizontal: true)
|
|
||||||
|
shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: globalTimeOffset, duration: 3.0, horizontal: true)
|
||||||
|
borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: globalTimeOffset, duration: 3.0, horizontal: true)
|
||||||
|
|
||||||
shimmerView.layer.compositingFilter = compositingFilter
|
shimmerView.layer.compositingFilter = compositingFilter
|
||||||
borderShimmerView.layer.compositingFilter = compositingFilter
|
borderShimmerView.layer.compositingFilter = compositingFilter
|
||||||
@ -1050,6 +1048,8 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
private func updateLayout(width: CGFloat, previousSubtitle: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
private func updateLayout(width: CGFloat, previousSubtitle: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
self.validLayout = width
|
self.validLayout = width
|
||||||
|
|
||||||
|
self.setupGloss()
|
||||||
|
|
||||||
let buttonSize = CGSize(width: width, height: self.buttonHeight)
|
let buttonSize = CGSize(width: width, height: self.buttonHeight)
|
||||||
let buttonFrame = CGRect(origin: CGPoint(), size: buttonSize)
|
let buttonFrame = CGRect(origin: CGPoint(), size: buttonSize)
|
||||||
transition.updateFrame(view: self.buttonBackgroundNode, frame: buttonFrame)
|
transition.updateFrame(view: self.buttonBackgroundNode, frame: buttonFrame)
|
||||||
|
@ -6227,11 +6227,13 @@ public extension Api.functions.messages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.payments {
|
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()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(1654235439)
|
buffer.appendInt32(267129798)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(transactionId, 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)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.Updates?
|
var result: Api.Updates?
|
||||||
if let signature = reader.readInt32() {
|
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 {
|
public extension Api.functions.payments {
|
||||||
static func restorePlayMarketReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
static func restorePlayMarketReceipt(receipt: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
@ -495,6 +495,8 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let leftInset: CGFloat = 58.0 + params.leftInset
|
let leftInset: CGFloat = 58.0 + params.leftInset
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
var titleIconsWidth: CGFloat = 0.0
|
var titleIconsWidth: CGFloat = 0.0
|
||||||
var currentCredibilityIconImage: UIImage?
|
var currentCredibilityIconImage: UIImage?
|
||||||
var credibilityIconOffset: CGFloat = 0.0
|
var credibilityIconOffset: CGFloat = 0.0
|
||||||
@ -507,6 +509,9 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else if item.peer.isVerified {
|
} else if item.peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
credibilityIconOffset = 3.0
|
credibilityIconOffset = 3.0
|
||||||
|
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||||
|
credibilityIconOffset = 3.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||||
|
@ -829,6 +829,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let verticalInset: CGFloat = 8.0
|
let verticalInset: CGFloat = 8.0
|
||||||
let verticalOffset: CGFloat = 0.0
|
let verticalOffset: CGFloat = 0.0
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
var titleIconsWidth: CGFloat = 0.0
|
var titleIconsWidth: CGFloat = 0.0
|
||||||
var currentCredibilityIconImage: UIImage?
|
var currentCredibilityIconImage: UIImage?
|
||||||
var credibilityIconOffset: CGFloat = 0.0
|
var credibilityIconOffset: CGFloat = 0.0
|
||||||
@ -841,6 +843,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else if item.peer.isVerified {
|
} else if item.peer.isVerified {
|
||||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||||
credibilityIconOffset = 3.0
|
credibilityIconOffset = 3.0
|
||||||
|
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
|
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||||
|
credibilityIconOffset = 3.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||||
|
@ -8,26 +8,18 @@ public enum AssignAppStoreTransactionError {
|
|||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_assignAppStoreTransaction(account: Account, transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> {
|
func _internal_assignAppStoreTransaction(account: Account, transactionId: String, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {
|
||||||
return account.network.request(Api.functions.payments.assignAppStoreTransaction(transactionId: transactionId))
|
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
|
|> mapError { _ -> AssignAppStoreTransactionError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
|
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
|
||||||
account.stateManager.addUpdates(updates)
|
account.stateManager.addUpdates(updates)
|
||||||
|
return .complete()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import Foundation
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
@ -37,8 +38,8 @@ public extension TelegramEngine {
|
|||||||
return _internal_clearBotPaymentInfo(network: self.account.network, info: info)
|
return _internal_clearBotPaymentInfo(network: self.account.network, info: info)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func assignAppStoreTransaction(transactionId: String) -> Signal<Never, AssignAppStoreTransactionError> {
|
public func assignAppStoreTransaction(transactionId: String, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {
|
||||||
return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId)
|
return _internal_assignAppStoreTransaction(account: self.account, transactionId: transactionId, receipt: receipt, restore: restore)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func canPurchasePremium() -> Signal<Bool, NoError> {
|
public func canPurchasePremium() -> Signal<Bool, NoError> {
|
||||||
|
@ -232,12 +232,12 @@ public struct PresentationResourcesChatList {
|
|||||||
context.saveGState()
|
context.saveGState()
|
||||||
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
|
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
|
||||||
|
|
||||||
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor)
|
context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
context.restoreGState()
|
context.restoreGState()
|
||||||
|
|
||||||
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
|
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
|
||||||
context.setFillColor(theme.chatList.unreadBadgeActiveTextColor.cgColor)
|
context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
}
|
}
|
||||||
}, opaque: false)
|
}, opaque: false)
|
||||||
@ -255,7 +255,7 @@ public struct PresentationResourcesChatList {
|
|||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||||
|
|
||||||
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor)
|
context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
}
|
}
|
||||||
}, opaque: false)
|
}, opaque: false)
|
||||||
|
@ -638,12 +638,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let starStatus = data.starStatus {
|
if let starStatus = data.starStatus {
|
||||||
actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in
|
var isPremiumSticker = false
|
||||||
return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.actionSheet.primaryTextColor)
|
for media in messages[0].media {
|
||||||
}, action: { _, f in
|
if let file = media as? TelegramMediaFile, file.isPremiumSticker {
|
||||||
interfaceInteraction.toggleMessageStickerStarred(messages[0].id)
|
isPremiumSticker = true
|
||||||
f(.default)
|
break
|
||||||
})))
|
}
|
||||||
|
}
|
||||||
|
if !isPremiumSticker || chatPresentationInterfaceState.isPremium {
|
||||||
|
actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in
|
||||||
|
return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
interfaceInteraction.toggleMessageStickerStarred(messages[0].id)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.messageActions.options.contains(.rateCall) {
|
if data.messageActions.options.contains(.rateCall) {
|
||||||
|
@ -2300,6 +2300,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let themeUpdated = self.presentationData?.theme !== presentationData.theme
|
let themeUpdated = self.presentationData?.theme !== presentationData.theme
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
let credibilityIcon: CredibilityIcon
|
let credibilityIcon: CredibilityIcon
|
||||||
if let peer = peer {
|
if let peer = peer {
|
||||||
if peer.isFake {
|
if peer.isFake {
|
||||||
@ -2308,7 +2310,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
credibilityIcon = .scam
|
credibilityIcon = .scam
|
||||||
} else if peer.isVerified {
|
} else if peer.isVerified {
|
||||||
credibilityIcon = .verified
|
credibilityIcon = .verified
|
||||||
} else if peer.isPremium {
|
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||||
credibilityIcon = .premium
|
credibilityIcon = .premium
|
||||||
} else {
|
} else {
|
||||||
credibilityIcon = .none
|
credibilityIcon = .none
|
||||||
|
@ -485,6 +485,7 @@ private final class PeerInfoInteraction {
|
|||||||
let openAddMember: () -> Void
|
let openAddMember: () -> Void
|
||||||
let openQrCode: () -> Void
|
let openQrCode: () -> Void
|
||||||
let editingOpenReactionsSetup: () -> Void
|
let editingOpenReactionsSetup: () -> Void
|
||||||
|
let dismissInput: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
openUsername: @escaping (String) -> Void,
|
openUsername: @escaping (String) -> Void,
|
||||||
@ -527,7 +528,8 @@ private final class PeerInfoInteraction {
|
|||||||
openFaq: @escaping (String?) -> Void,
|
openFaq: @escaping (String?) -> Void,
|
||||||
openAddMember: @escaping () -> Void,
|
openAddMember: @escaping () -> Void,
|
||||||
openQrCode: @escaping () -> Void,
|
openQrCode: @escaping () -> Void,
|
||||||
editingOpenReactionsSetup: @escaping () -> Void
|
editingOpenReactionsSetup: @escaping () -> Void,
|
||||||
|
dismissInput: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.openUsername = openUsername
|
self.openUsername = openUsername
|
||||||
self.openPhone = openPhone
|
self.openPhone = openPhone
|
||||||
@ -570,6 +572,7 @@ private final class PeerInfoInteraction {
|
|||||||
self.openAddMember = openAddMember
|
self.openAddMember = openAddMember
|
||||||
self.openQrCode = openQrCode
|
self.openQrCode = openQrCode
|
||||||
self.editingOpenReactionsSetup = editingOpenReactionsSetup
|
self.editingOpenReactionsSetup = editingOpenReactionsSetup
|
||||||
|
self.dismissInput = dismissInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -817,6 +820,8 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
|
|||||||
if let cachedData = data.cachedData as? CachedUserData {
|
if let cachedData = data.cachedData as? CachedUserData {
|
||||||
items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in
|
items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in
|
||||||
interaction.updateBio(updatedText)
|
interaction.updateBio(updatedText)
|
||||||
|
}, action: {
|
||||||
|
interaction.dismissInput()
|
||||||
}, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70)))
|
}, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70)))
|
||||||
items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_Help))
|
items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_Help))
|
||||||
}
|
}
|
||||||
@ -1838,6 +1843,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
},
|
},
|
||||||
editingOpenReactionsSetup: { [weak self] in
|
editingOpenReactionsSetup: { [weak self] in
|
||||||
self?.editingOpenReactionsSetup()
|
self?.editingOpenReactionsSetup()
|
||||||
|
},
|
||||||
|
dismissInput: { [weak self] in
|
||||||
|
self?.view.endEditing(true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -6268,17 +6276,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
push(usernameSetupController(context: self.context))
|
push(usernameSetupController(context: self.context))
|
||||||
case .addAccount:
|
case .addAccount:
|
||||||
var maximumAvailableAccounts: Int = 3
|
var maximumAvailableAccounts: Int = 3
|
||||||
if self.data?.peer?.isPremium == true {
|
if self.data?.peer?.isPremium == true && !self.context.account.testingEnvironment {
|
||||||
maximumAvailableAccounts = 4
|
maximumAvailableAccounts = 4
|
||||||
}
|
}
|
||||||
var count: Int = 1
|
var count: Int = 1
|
||||||
if let settings = self.data?.globalSettings {
|
if let settings = self.data?.globalSettings {
|
||||||
for (_, peer, _) in settings.accountsAndPeers {
|
for (accountContext, peer, _) in settings.accountsAndPeers {
|
||||||
if peer.isPremium {
|
if !accountContext.account.testingEnvironment {
|
||||||
maximumAvailableAccounts = 4
|
if peer.isPremium {
|
||||||
|
maximumAvailableAccounts = 4
|
||||||
|
}
|
||||||
|
count += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count += settings.accountsAndPeers.count
|
|
||||||
}
|
}
|
||||||
if count >= maximumAvailableAccounts {
|
if count >= maximumAvailableAccounts {
|
||||||
let context = self.context
|
let context = self.context
|
||||||
|
@ -8,6 +8,7 @@ final class PeerInfoScreenMultilineInputItem: PeerInfoScreenItem {
|
|||||||
let text: String
|
let text: String
|
||||||
let placeholder: String
|
let placeholder: String
|
||||||
let textUpdated: (String) -> Void
|
let textUpdated: (String) -> Void
|
||||||
|
let action: () -> Void
|
||||||
let maxLength: Int?
|
let maxLength: Int?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -15,12 +16,14 @@ final class PeerInfoScreenMultilineInputItem: PeerInfoScreenItem {
|
|||||||
text: String,
|
text: String,
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
textUpdated: @escaping (String) -> Void,
|
textUpdated: @escaping (String) -> Void,
|
||||||
|
action: @escaping () -> Void,
|
||||||
maxLength: Int?
|
maxLength: Int?
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.text = text
|
self.text = text
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
self.textUpdated = textUpdated
|
self.textUpdated = textUpdated
|
||||||
|
self.action = action
|
||||||
self.maxLength = maxLength
|
self.maxLength = maxLength
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +64,10 @@ final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode {
|
|||||||
|
|
||||||
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
|
|
||||||
let inputItem = ItemListMultilineInputItem(presentationData: ItemListPresentationData(presentationData), text: item.text, placeholder: item.placeholder, maxLength: item.maxLength.flatMap { ItemListMultilineInputItemTextLimit(value: $0, display: true) }, sectionId: 0, style: .blocks, textUpdated: { updatedText in
|
let inputItem = ItemListMultilineInputItem(presentationData: ItemListPresentationData(presentationData), text: item.text, placeholder: item.placeholder, maxLength: item.maxLength.flatMap { ItemListMultilineInputItemTextLimit(value: $0, display: true) }, sectionId: 0, style: .blocks, returnKeyType: .done, textUpdated: { updatedText in
|
||||||
item.textUpdated(updatedText)
|
item.textUpdated(updatedText)
|
||||||
|
}, action: {
|
||||||
|
item.action()
|
||||||
}, noInsets: true)
|
}, noInsets: true)
|
||||||
|
|
||||||
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)
|
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user