mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Merge commit '2af0b2a8b7' into story-folders
# Conflicts: # MODULE.bazel.lock # submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift # submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift # submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift
This commit is contained in:
commit
cb1961ff72
28 changed files with 415 additions and 145 deletions
|
|
@ -14700,6 +14700,9 @@ Sorry for the inconvenience.";
|
|||
|
||||
"AccessDenied.AgeVerificationCamera" = "Telegram needs access to your camera for age verification.\n\nOpen your device's Settings > Privacy > Camera and set Telegram to ON.";
|
||||
|
||||
"PeerInfo.Gifts.Collections.All" = "All Gifts";
|
||||
"PeerInfo.Gifts.Collections.Add" = "Add Collection";
|
||||
|
||||
"PeerInfo.Gifts.AddGiftsButton" = "Add Gifts";
|
||||
|
||||
"PeerInfo.Gifts.AddCollection" = "Add Collection";
|
||||
|
|
@ -14728,6 +14731,12 @@ Sorry for the inconvenience.";
|
|||
"PeerInfo.Gifts.EmptyCollection.Text" = "Add some gifts to this collection.";
|
||||
"PeerInfo.Gifts.EmptyCollection.Action" = "Add to Collection";
|
||||
|
||||
"PeerInfo.Gifts.AddedToCollection" = "The gift has been added to **%@**.";
|
||||
"PeerInfo.Gifts.RemovedFromCollection" = "The gift has been removed from **%@**.";
|
||||
|
||||
"PeerInfo.Gifts.AddedToCollectionUnique" = "**%1$@** has been added to **%2$@**.";
|
||||
"PeerInfo.Gifts.RemovedFromCollectionUnique" = "**%1$@** has been removed from **%2$@**.";
|
||||
|
||||
"AddGifts.Title" = "Add Gifts";
|
||||
"AddGifts.AddGifts_1" = "Add %@ Gift";
|
||||
"AddGifts.AddGifts_any" = "Add %@ Gifts";
|
||||
|
|
@ -14736,8 +14745,8 @@ Sorry for the inconvenience.";
|
|||
"Gift.Options.Gift.BuyLimitReached_any" = "You've already sent %@ of these gifts, and it's the limit.";
|
||||
|
||||
"AgeVerification.Title" = "Age Verification";
|
||||
"AgeVerification.Text" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram.";
|
||||
"AgeVerification.Text.gb" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram.";
|
||||
"AgeVerification.Text" = "To access this content, you must confirm you are at least **18** years old.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram.";
|
||||
"AgeVerification.Text.GB" = "To access this content, you must confirm you are at least **18** years old as required by UK law.\n\nThis is a one-time process using your phone's camera. Your selfie will not be stored by Telegram.";
|
||||
"AgeVerification.Verify" = "Verify My Age";
|
||||
|
||||
"AgeVerification.Success.Title" = "Age check passed!";
|
||||
|
|
|
|||
|
|
@ -1298,7 +1298,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
|
||||
func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController
|
||||
|
||||
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?)
|
||||
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?)
|
||||
|
||||
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
|
||||
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public final class BotCheckoutController: ViewController {
|
|||
public enum FetchError {
|
||||
case generic
|
||||
case disallowedStarGifts
|
||||
case starGiftsUserLimit
|
||||
}
|
||||
|
||||
public let form: BotPaymentForm
|
||||
|
|
@ -58,6 +59,8 @@ public final class BotCheckoutController: ViewController {
|
|||
switch error {
|
||||
case .disallowedStarGift:
|
||||
return .disallowedStarGifts
|
||||
case .starGiftUserLimit:
|
||||
return .starGiftsUserLimit
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1250,7 +1250,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3282,7 +3282,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
interaction.dismissSearch()
|
||||
}
|
||||
|
|
@ -4268,7 +4269,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4287,7 +4289,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
interaction.dismissSearch()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -871,7 +871,7 @@
|
|||
|
||||
if (_intent == TGMediaAssetsControllerSendMediaIntent && _selectionContext.allowGrouping)
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(!_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
|
||||
|
||||
|
||||
return [TGMediaAssetsController resultSignalsForSelectionContext:_selectionContext editingContext:_editingContext intent:_intent currentItem:currentItem storeAssets:storeAssets convertToJpeg:false descriptionGenerator:descriptionGenerator saveEditedPhotos:_saveEditedPhotos];
|
||||
}
|
||||
|
||||
|
|
@ -889,6 +889,9 @@
|
|||
if (selectedItems.count == 0 && currentItem != nil)
|
||||
[selectedItems addObject:currentItem];
|
||||
|
||||
if (intent == TGMediaAssetsControllerSendMediaIntent)
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(editingContext.isHighQualityPhoto) forKey:@"TG_photoHighQuality_v0"];
|
||||
|
||||
if (saveEditedPhotos && storeAssets && editingContext != nil)
|
||||
{
|
||||
NSMutableArray *fullSizeSignals = [[NSMutableArray alloc] init];
|
||||
|
|
|
|||
|
|
@ -2159,6 +2159,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
strongSelf.controllerNode.dismissInput()
|
||||
}
|
||||
}, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext())
|
||||
|
||||
let highQualityPhoto = UserDefaults.standard.bool(forKey: "TG_photoHighQuality_v0")
|
||||
if highQualityPhoto {
|
||||
self.interaction?.editingState.setHighQualityPhoto(highQualityPhoto)
|
||||
}
|
||||
|
||||
self.interaction?.selectionState?.grouping = true
|
||||
|
||||
self.interaction?.editingState.sendPaidMessageStars = sendPaidMessageStars ?? 0
|
||||
|
|
|
|||
|
|
@ -3096,13 +3096,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||
products.append(PremiumProduct(option: option, storeProduct: product))
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:release
|
||||
if let product = availableProducts.first(where: { $0.id.hasSuffix(".annual") }) {
|
||||
let (currency, price) = product.priceCurrencyAndAmount
|
||||
products.insert(PremiumProduct(option: PremiumPromoConfiguration.PremiumProductOption(isCurrent: false, months: 24, currency: currency, amount: price * 2, botUrl: "", transactionId: nil, availableForUpgrade: true, storeProductId: "org.telegram.telegramPremium.biannual"), storeProduct: product), at: 0)
|
||||
}
|
||||
|
||||
|
||||
strongSelf.products = products
|
||||
strongSelf.isPremium = forceHasPremium || isPremium
|
||||
strongSelf.otherPeerName = otherPeerName
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ public enum BotPaymentFormRequestError {
|
|||
case noPaymentNeeded
|
||||
case disallowedStarGift
|
||||
case starGiftResellTooEarly(Int32)
|
||||
case starGiftUserLimit
|
||||
}
|
||||
|
||||
extension BotPaymentInvoice {
|
||||
|
|
@ -488,6 +489,8 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw
|
|||
if let value = Int32(timeout) {
|
||||
return .fail(.starGiftResellTooEarly(value))
|
||||
}
|
||||
} else if error.errorDescription == "STARGIFT_USER_USAGE_LIMITED" {
|
||||
return .fail(.starGiftUserLimit)
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
|
@ -651,6 +654,7 @@ public enum SendBotPaymentFormError {
|
|||
case alreadyPaid
|
||||
case starGiftOutOfStock
|
||||
case disallowedStarGift
|
||||
case starGiftUserLimit
|
||||
}
|
||||
|
||||
public enum SendBotPaymentResult {
|
||||
|
|
|
|||
|
|
@ -405,26 +405,81 @@ public func presentAgeVerification(context: AccountContext, parentController: Vi
|
|||
if state.verificationPassed {
|
||||
completion()
|
||||
} else {
|
||||
let miniappPromise = Promise<EnginePeer?>(nil)
|
||||
var useVerifyAgeBot = false
|
||||
if let value = context.currentAppConfiguration.with({ $0 }).data?["force_verify_age_bot"] as? Bool, value {
|
||||
useVerifyAgeBot = value
|
||||
}
|
||||
if useVerifyAgeBot, let verifyAgeBotUsername = context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String {
|
||||
miniappPromise.set(context.engine.peers.resolvePeerByName(name: verifyAgeBotUsername, referrer: nil)
|
||||
|> mapToSignal { result in
|
||||
if case let .result(peer) = result {
|
||||
return .single(peer)
|
||||
}
|
||||
return .complete()
|
||||
})
|
||||
}
|
||||
let infoScreen = AgeVerificationScreen(context: context, completion: { [weak parentController] check, availability in
|
||||
if check {
|
||||
let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { [weak parentController] passed in
|
||||
if passed {
|
||||
let _ = updateAgeVerificationState(engine: context.engine, { _ in
|
||||
return AgeVerificationState(verificationPassed: passed)
|
||||
}).start()
|
||||
completion()
|
||||
|
||||
let navigationController = parentController?.navigationController
|
||||
Queue.mainQueue().after(2.0) {
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AgeVerification_Success_Title, text: presentationData.strings.AgeVerification_Success_Text, cancel: nil, destructive: false), action: { _ in return true })
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
var requiredAge = 18
|
||||
if let value = context.currentAppConfiguration.with({ $0 }).data?["verify_age_min"] as? Double {
|
||||
requiredAge = Int(value)
|
||||
}
|
||||
|
||||
let success = { [weak parentController] in
|
||||
let _ = updateAgeVerificationState(engine: context.engine, { _ in
|
||||
return AgeVerificationState(verificationPassed: true)
|
||||
}).start()
|
||||
completion()
|
||||
|
||||
let navigationController = parentController?.navigationController
|
||||
Queue.mainQueue().after(2.0) {
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AgeVerification_Success_Title, text: presentationData.strings.AgeVerification_Success_Text, cancel: nil, destructive: false), action: { _ in return true })
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .current)
|
||||
}
|
||||
}
|
||||
|
||||
let failure = { [weak parentController] in
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: presentationData.strings.AgeVerification_Fail_Title, text: presentationData.strings.AgeVerification_Fail_Text, customUndoText: nil, timeout: nil), action: { _ in return true })
|
||||
parentController?.present(controller, in: .current)
|
||||
}
|
||||
|
||||
let _ = (miniappPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let peer, let parentController {
|
||||
context.sharedContext.openWebApp(
|
||||
context: context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil,
|
||||
verifyAgeCompletion: { age in
|
||||
if age >= requiredAge {
|
||||
success()
|
||||
} else {
|
||||
failure()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: presentationData.strings.AgeVerification_Fail_Title, text: presentationData.strings.AgeVerification_Fail_Text, customUndoText: nil, timeout: nil), action: { _ in return true })
|
||||
parentController?.present(controller, in: .window(.root))
|
||||
let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { age in
|
||||
if age >= requiredAge {
|
||||
success()
|
||||
} else {
|
||||
failure()
|
||||
}
|
||||
})
|
||||
parentController?.push(scanScreen)
|
||||
}
|
||||
})
|
||||
parentController?.push(scanScreen)
|
||||
}
|
||||
})
|
||||
parentController?.push(infoScreen)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ import ZipArchive
|
|||
import PlainButtonComponent
|
||||
import MultilineTextComponent
|
||||
|
||||
private let requiredAge = 18
|
||||
|
||||
final class FaceScanScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
|
|
@ -109,7 +107,7 @@ final class FaceScanScreenComponent: Component {
|
|||
|
||||
self.backgroundColor = .black
|
||||
|
||||
self.previewLayer.backgroundColor = UIColor.red.cgColor
|
||||
//self.previewLayer.backgroundColor = UIColor.red.cgColor
|
||||
self.previewLayer.videoGravity = .resizeAspectFill
|
||||
self.layer.addSublayer(previewLayer)
|
||||
|
||||
|
|
@ -240,7 +238,7 @@ final class FaceScanScreenComponent: Component {
|
|||
let targetCenter = CGPoint(x: 0.5, y: 0.5)
|
||||
let distance = sqrt(pow(faceCenter.x - targetCenter.x, 2) + pow(faceCenter.y - targetCenter.y, 2))
|
||||
|
||||
if distance < 0.35 {
|
||||
if distance < 0.24 {
|
||||
switch processState {
|
||||
case .waitingForFace:
|
||||
self.processState = .positioning
|
||||
|
|
@ -320,7 +318,7 @@ final class FaceScanScreenComponent: Component {
|
|||
if !self.ages.isEmpty {
|
||||
let averageAge = self.ages.reduce(0, +) / Double(self.ages.count)
|
||||
if let environment = self.environment, let controller = environment.controller() as? FaceScanScreen {
|
||||
controller.completion(averageAge >= Double(requiredAge))
|
||||
controller.completion(Int(averageAge))
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -436,7 +434,7 @@ final class FaceScanScreenComponent: Component {
|
|||
|
||||
let center = CGPoint(x: availableSize.width / 2, y: environment.statusBarHeight + 10.0 + widthRadius * 1.3)
|
||||
|
||||
var previewScale = 1.0
|
||||
var previewScale = 0.85
|
||||
if self.processState == .tracking || self.processState == .readyToStart || self.processState == .completed || self.transitioningToViewFinder {
|
||||
let circlePath = CGPath(roundedRect: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2), cornerWidth: radius, cornerHeight: radius, transform: nil)
|
||||
path.addPath(circlePath)
|
||||
|
|
@ -457,7 +455,6 @@ final class FaceScanScreenComponent: Component {
|
|||
self.frameView.frame = frameViewFrame
|
||||
self.frameView.update(size: frameViewFrame.size)
|
||||
|
||||
//TODO:localize
|
||||
var instructionString = environment.strings.FaceScan_Instruction_Position
|
||||
switch self.processState {
|
||||
case .waitingForFace, .positioning:
|
||||
|
|
@ -545,12 +542,12 @@ final class FaceScanScreenComponent: Component {
|
|||
|
||||
public final class FaceScanScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
fileprivate let completion: (Bool) -> Void
|
||||
fileprivate let completion: (Int) -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
availability: Signal<AgeVerificationAvailability, NoError>,
|
||||
completion: @escaping (Bool) -> Void
|
||||
completion: @escaping (Int) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
|
|
|
|||
|
|
@ -363,6 +363,8 @@ final class GiftSetupScreenComponent: Component {
|
|||
let entities = generateChatInputTextEntities(self.textInputState.text)
|
||||
|
||||
var finalPrice: Int64
|
||||
var perUserLimit: Int32?
|
||||
var giftFile: TelegramMediaFile?
|
||||
let source: BotPaymentInvoiceSource
|
||||
switch component.subject {
|
||||
case let .premium(product):
|
||||
|
|
@ -377,6 +379,8 @@ final class GiftSetupScreenComponent: Component {
|
|||
if self.includeUpgrade, let upgradeStars = starGift.upgradeStars {
|
||||
finalPrice += upgradeStars
|
||||
}
|
||||
perUserLimit = starGift.perUserLimit?.total
|
||||
giftFile = starGift.file
|
||||
source = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities)
|
||||
}
|
||||
|
||||
|
|
@ -395,6 +399,8 @@ final class GiftSetupScreenComponent: Component {
|
|||
switch error {
|
||||
case .disallowedStarGifts:
|
||||
return .fail(.disallowedStarGift)
|
||||
case .starGiftsUserLimit:
|
||||
return .fail(.starGiftUserLimit)
|
||||
default:
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
|
@ -468,6 +474,14 @@ final class GiftSetupScreenComponent: Component {
|
|||
|
||||
var errorText: String?
|
||||
switch error {
|
||||
case .starGiftUserLimit:
|
||||
if let perUserLimit, let giftFile {
|
||||
let text = presentationData.strings.Gift_Options_Gift_BuyLimitReached(perUserLimit)
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: giftFile, loop: true, title: nil, text: text, undoText: nil, customAction: nil), action: { _ in return false })
|
||||
controller.present(undoController, in: .current)
|
||||
return
|
||||
}
|
||||
return
|
||||
case .starGiftOutOfStock:
|
||||
errorText = presentationData.strings.Gift_Send_ErrorOutOfStock
|
||||
case .disallowedStarGift:
|
||||
|
|
|
|||
|
|
@ -288,11 +288,14 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
var minRequiredAmount = StarsAmount(value: 100, nanos: 0)
|
||||
var canUpgrade = false
|
||||
if let resellStars = self.subject.arguments?.resellStars {
|
||||
minRequiredAmount = StarsAmount(value: resellStars, nanos: 0)
|
||||
} else if let arguments = self.subject.arguments, arguments.canUpgrade && arguments.upgradeStars == nil {
|
||||
canUpgrade = true
|
||||
}
|
||||
|
||||
if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < minRequiredAmount {
|
||||
if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < minRequiredAmount || canUpgrade {
|
||||
self.optionsDisposable = (context.engine.payments.starsTopUpOptions()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] options in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -1257,7 +1257,8 @@ final class AffiliateProgramSetupScreenComponent: Component {
|
|||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
} else if let navigationController = controller.navigationController as? NavigationController {
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(item.peer), subject: nil, keepStack: .always, animated: true, pushController: { [weak navigationController] chatController, animated, completion in
|
||||
|
|
|
|||
|
|
@ -1917,7 +1917,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight
|
||||
|
||||
var subtitleRatingSize: CGSize?
|
||||
if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
|
||||
if !"".isEmpty, let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
|
||||
let subtitleRating: ComponentView<Empty>
|
||||
var subtitleRatingTransition = ComponentTransition(transition)
|
||||
if let current = self.subtitleRating {
|
||||
|
|
|
|||
|
|
@ -1451,7 +1451,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
@ -11365,7 +11366,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
let canReorderEquals = lhs.2 == rhs.2
|
||||
return filterEquals && sortingEquals && canReorderEquals
|
||||
})
|
||||
|> map { [weak self, weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in
|
||||
|> map { [weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if hasVisibility {
|
||||
|
|
@ -11390,13 +11391,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
//TODO:release
|
||||
self?.openShareLink(url: "https://t.me/")
|
||||
})))
|
||||
// items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||
// }, action: { [weak self] _, f in
|
||||
// f(.default)
|
||||
// self?.openShareLink(url: "https://t.me/")
|
||||
// })))
|
||||
}
|
||||
|
||||
if canReorder {
|
||||
|
|
@ -11428,15 +11428,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
items.append(.separator)
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak giftsContext] _, f in
|
||||
f(.default)
|
||||
|
||||
giftsContext?.updateSorting(sorting == .date ? .value : .date)
|
||||
})))
|
||||
if let pane, case .all = pane.currentCollection {
|
||||
items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak giftsContext] _, f in
|
||||
f(.default)
|
||||
|
||||
giftsContext?.updateSorting(sorting == .date ? .value : .date)
|
||||
})))
|
||||
|
||||
items.append(.separator)
|
||||
items.append(.separator)
|
||||
}
|
||||
|
||||
let toggleFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in
|
||||
var updatedFilter = filter
|
||||
|
|
|
|||
|
|
@ -23,17 +23,20 @@ final class AddGiftsScreenComponent: Component {
|
|||
let context: AccountContext
|
||||
let peerId: EnginePeer.Id
|
||||
let collectionId: Int32
|
||||
let remainingCount: Int32
|
||||
let profileGifts: ProfileGiftsContext
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peerId: EnginePeer.Id,
|
||||
collectionId: Int32,
|
||||
remainingCount: Int32,
|
||||
profileGifts: ProfileGiftsContext
|
||||
) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.collectionId = collectionId
|
||||
self.remainingCount = remainingCount
|
||||
self.profileGifts = profileGifts
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +114,7 @@ final class AddGiftsScreenComponent: Component {
|
|||
var contentSize = CGSize(width: self.scrollView.bounds.width, height: contentHeight)
|
||||
contentSize.height += environment.safeInsets.bottom
|
||||
contentSize.height = max(contentSize.height, self.scrollView.bounds.size.height)
|
||||
contentSize.height += 50.0 + 24.0
|
||||
transition.setFrame(view: giftsListView, frame: CGRect(origin: CGPoint(), size: contentSize))
|
||||
|
||||
if self.scrollView.contentSize != contentSize {
|
||||
|
|
@ -128,7 +132,13 @@ final class AddGiftsScreenComponent: Component {
|
|||
if let current = self.giftsListView {
|
||||
giftsListView = current
|
||||
} else {
|
||||
giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId)
|
||||
giftsListView = GiftsListView(context: component.context, peerId: component.peerId, profileGifts: component.profileGifts, giftsCollections: nil, canSelect: true, ignoreCollection: component.collectionId, remainingSelectionCount: component.remainingCount)
|
||||
giftsListView.onContentUpdated = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
giftsListView.selectionUpdated = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -248,6 +258,7 @@ public final class AddGiftsScreen: ViewControllerComponentContainer {
|
|||
context: AccountContext,
|
||||
peerId: EnginePeer.Id,
|
||||
collectionId: Int32,
|
||||
remainingCount: Int32,
|
||||
completion: @escaping ([ProfileGiftsContext.State.StarGift]) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
|
|
@ -264,10 +275,10 @@ public final class AddGiftsScreen: ViewControllerComponentContainer {
|
|||
context: context,
|
||||
peerId: peerId,
|
||||
collectionId: collectionId,
|
||||
remainingCount: remainingCount,
|
||||
profileGifts: self.profileGifts
|
||||
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil)
|
||||
|
||||
|
||||
self.title = presentationData.strings.AddGifts_Title
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ final class GiftsListView: UIView {
|
|||
|
||||
private let canSelect: Bool
|
||||
private let ignoreCollection: Int32?
|
||||
private let remainingSelectionCount: Int32
|
||||
|
||||
private var dataDisposable: Disposable?
|
||||
|
||||
|
|
@ -124,13 +125,14 @@ final class GiftsListView: UIView {
|
|||
var contextAction: ((ProfileGiftsContext.State.StarGift, UIView, ContextGesture) -> Void)?
|
||||
var addToCollection: (() -> Void)?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil) {
|
||||
init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil, remainingSelectionCount: Int32 = 0) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.profileGifts = profileGifts
|
||||
self.giftsCollections = giftsCollections
|
||||
self.canSelect = canSelect
|
||||
self.ignoreCollection = ignoreCollection
|
||||
self.remainingSelectionCount = remainingSelectionCount
|
||||
|
||||
if let value = context.currentAppConfiguration.with({ $0 }).data?["stargifts_pinned_to_top_limit"] as? Double {
|
||||
self.maxPinnedCount = Int(value)
|
||||
|
|
@ -406,6 +408,13 @@ final class GiftsListView: UIView {
|
|||
}
|
||||
return self.updateScrolling(interactive: interactive, topInset: topInset, visibleBounds: visibleBounds, transition: transition)
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if let topInset = self.topInset, point.y < topInset {
|
||||
return false
|
||||
}
|
||||
return super.point(inside: point, with: event)
|
||||
}
|
||||
|
||||
func updateScrolling(interactive: Bool = false, topInset: CGFloat, visibleBounds: CGRect, transition: ComponentTransition) -> CGFloat {
|
||||
self.topInset = topInset
|
||||
|
|
@ -548,8 +557,10 @@ final class GiftsListView: UIView {
|
|||
if self.selectedItemIds.contains(itemReferenceId) {
|
||||
self.selectedItemIds.remove(itemReferenceId)
|
||||
} else {
|
||||
self.selectedItemIds.insert(itemReferenceId)
|
||||
self.selectedItemsMap[itemReferenceId] = product
|
||||
if self.selectedItemIds.count < self.remainingSelectionCount {
|
||||
self.selectedItemIds.insert(itemReferenceId)
|
||||
self.selectedItemsMap[itemReferenceId] = product
|
||||
}
|
||||
}
|
||||
self.selectionUpdated()
|
||||
self.updateScrolling(transition: .easeInOut(duration: 0.25))
|
||||
|
|
|
|||
|
|
@ -151,6 +151,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
|
||||
super.init()
|
||||
|
||||
self.giftsListView.onContentUpdated = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let params = self.currentParams {
|
||||
self.update(size: params.size, topInset: params.topInset, sideInset: params.sideInset, bottomInset: params.bottomInset, deviceMetrics: params.deviceMetrics, visibleHeight: params.visibleHeight, isScrollingLockedAtTop: params.isScrollingLockedAtTop, expandProgress: params.expandProgress, navigationHeight: params.navigationHeight, presentationData: params.presentationData, synchronous: true, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.scrollNode)
|
||||
|
||||
|
|
@ -184,7 +193,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
self.scrollNode.view.insertSubview(self.giftsListView, at: 0)
|
||||
if let tabSelectorView = self.tabSelector.view {
|
||||
self.scrollNode.view.insertSubview(self.giftsListView, aboveSubview: tabSelectorView)
|
||||
} else {
|
||||
self.scrollNode.view.insertSubview(self.giftsListView, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func item(at point: CGPoint) -> (AnyHashable, ComponentView<Empty>)? {
|
||||
|
|
@ -201,7 +214,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
return
|
||||
}
|
||||
|
||||
let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 20, displayCharacterLimit: true, apply: { [weak self] value in
|
||||
let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in
|
||||
guard let self, let value else {
|
||||
return
|
||||
}
|
||||
|
|
@ -211,6 +224,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
if let collection {
|
||||
self.setCurrentCollection(collection: .collection(collection.id))
|
||||
|
||||
if let tabSelectorView = self.tabSelector.view as? TabSelectorComponent.View {
|
||||
tabSelectorView.scrollToEnd()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -230,6 +247,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
|
||||
self?.setCurrentCollection(collection: .all)
|
||||
let _ = self?.profileGiftsCollections.deleteCollection(id: id).start()
|
||||
|
||||
if let tabSelectorView = self?.tabSelector.view as? TabSelectorComponent.View {
|
||||
tabSelectorView.scrollToStart()
|
||||
}
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
|
|
@ -242,7 +263,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
|
||||
public func addGiftsToCollection(id: Int32) {
|
||||
let screen = AddGiftsScreen(context: self.context, peerId: self.peerId, collectionId: id, completion: { [weak self] gifts in
|
||||
var collectionGiftsMaxCount: Int32 = 1000
|
||||
if let value = self.context.currentAppConfiguration.with({ $0 }).data?["stargifts_collection_gifts_limit"] as? Double {
|
||||
collectionGiftsMaxCount = Int32(value)
|
||||
}
|
||||
var remainingCount = collectionGiftsMaxCount
|
||||
if let currentCount = self.giftsListView.profileGifts.currentState?.count {
|
||||
remainingCount = max(0, collectionGiftsMaxCount - currentCount)
|
||||
}
|
||||
let screen = AddGiftsScreen(context: self.context, peerId: self.peerId, collectionId: id, remainingCount: remainingCount, completion: { [weak self] gifts in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -256,7 +285,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
return
|
||||
}
|
||||
|
||||
let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 20, displayCharacterLimit: true, apply: { [weak self] value in
|
||||
let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in
|
||||
guard let self, let value else {
|
||||
return
|
||||
}
|
||||
|
|
@ -460,20 +489,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
f(.default)
|
||||
|
||||
self.renameCollection(id: id)
|
||||
Queue.mainQueue().after(0.15) {
|
||||
self.renameCollection(id: id)
|
||||
}
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_ShareCollection, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
|
||||
//TODO:release
|
||||
let _ = self
|
||||
})))
|
||||
// items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_ShareCollection, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor)
|
||||
// }, action: { [weak self] _, f in
|
||||
// guard let self else {
|
||||
// return
|
||||
// }
|
||||
// f(.default)
|
||||
//
|
||||
// let _ = self
|
||||
// })))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Gifts_Reorder, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.actionSheet.primaryTextColor)
|
||||
|
|
@ -494,7 +524,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
f(.default)
|
||||
|
||||
self.deleteCollection(id: id)
|
||||
Queue.mainQueue().after(0.15) {
|
||||
self.deleteCollection(id: id)
|
||||
}
|
||||
})))
|
||||
|
||||
let contextController = ContextController(
|
||||
|
|
@ -515,13 +547,19 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
|
||||
var topInset: CGFloat = 60.0
|
||||
|
||||
if let collections = self.collections, !collections.isEmpty {
|
||||
var canEditCollections = false
|
||||
if self.peerId == self.context.account.peerId || self.canManage {
|
||||
canEditCollections = true
|
||||
}
|
||||
|
||||
let hasNonEmptyCollections = self.collections?.contains(where: { $0.count > 0 }) ?? false
|
||||
if let collections = self.collections, !collections.isEmpty && (hasNonEmptyCollections || canEditCollections) {
|
||||
var tabSelectorItems: [TabSelectorComponent.Item] = []
|
||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||
id: AnyHashable(GiftCollection.all.rawValue),
|
||||
title: "All Gifts"
|
||||
title: params.presentationData.strings.PeerInfo_Gifts_Collections_All
|
||||
))
|
||||
|
||||
|
||||
var effectiveCollections: [StarGiftCollection] = collections
|
||||
if let reorderedCollectionIds = self.reorderedCollectionIds {
|
||||
var collectionMap: [Int32: StarGiftCollection] = [:]
|
||||
|
|
@ -538,6 +576,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
|
||||
for collection in effectiveCollections {
|
||||
if !canEditCollections && collection.count == 0 {
|
||||
continue
|
||||
}
|
||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||
id: AnyHashable(GiftCollection.collection(collection.id).rawValue),
|
||||
content: .component(AnyComponent(
|
||||
|
|
@ -557,19 +598,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
))
|
||||
}
|
||||
|
||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||
id: AnyHashable(GiftCollection.create.rawValue),
|
||||
content: .component(AnyComponent(
|
||||
CollectionTabItemComponent(
|
||||
context: self.context,
|
||||
icon: .add,
|
||||
title: "Add Collection",
|
||||
theme: params.presentationData.theme
|
||||
)
|
||||
)),
|
||||
isReorderable: false
|
||||
))
|
||||
|
||||
if canEditCollections {
|
||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||
id: AnyHashable(GiftCollection.create.rawValue),
|
||||
content: .component(AnyComponent(
|
||||
CollectionTabItemComponent(
|
||||
context: self.context,
|
||||
icon: .add,
|
||||
title: params.presentationData.strings.PeerInfo_Gifts_Collections_Add,
|
||||
theme: params.presentationData.theme
|
||||
)
|
||||
)),
|
||||
isReorderable: false
|
||||
))
|
||||
}
|
||||
|
||||
let tabSelectorSize = self.tabSelector.update(
|
||||
transition: transition,
|
||||
|
|
@ -621,7 +664,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
if let tabSelectorView = self.tabSelector.view {
|
||||
if tabSelectorView.superview == nil {
|
||||
tabSelectorView.alpha = 1.0
|
||||
self.scrollNode.view.addSubview(tabSelectorView)
|
||||
self.scrollNode.view.insertSubview(tabSelectorView, at: 0)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
|
|
@ -938,10 +981,52 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}, iconPosition: collection.icon == nil ? .left : .right, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if isAdded, let giftReference = gift.reference {
|
||||
let _ = self?.profileGiftsCollections.removeGifts(id: collection.id, gifts: [giftReference]).start()
|
||||
let _ = self.profileGiftsCollections.removeGifts(id: collection.id, gifts: [giftReference]).start()
|
||||
} else {
|
||||
let _ = self?.profileGiftsCollections.addGifts(id: collection.id, gifts: [gift]).start()
|
||||
let _ = self.profileGiftsCollections.addGifts(id: collection.id, gifts: [gift]).start()
|
||||
}
|
||||
|
||||
var giftFile: TelegramMediaFile?
|
||||
var giftTitle: String?
|
||||
switch gift.gift {
|
||||
case let .generic(gift):
|
||||
giftFile = gift.file
|
||||
case let .unique(uniqueGift):
|
||||
giftTitle = uniqueGift.title + " #\(presentationStringsFormattedNumber(uniqueGift.number, currentParams.presentationData.dateTimeFormat.groupingSeparator))"
|
||||
for attribute in uniqueGift.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
giftFile = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let giftFile {
|
||||
let text: String
|
||||
if let giftTitle {
|
||||
if isAdded {
|
||||
text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollectionUnique(giftTitle, collection.title).string
|
||||
} else {
|
||||
text = currentParams.presentationData.strings.PeerInfo_Gifts_AddedToCollectionUnique(giftTitle, collection.title).string
|
||||
}
|
||||
} else {
|
||||
if isAdded {
|
||||
text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollection(collection.title).string
|
||||
} else {
|
||||
text = currentParams.presentationData.strings.PeerInfo_Gifts_AddedToCollection(collection.title).string
|
||||
}
|
||||
}
|
||||
|
||||
let undoController = UndoOverlayController(
|
||||
presentationData: currentParams.presentationData,
|
||||
content: .sticker(context: self.context, file: giftFile, loop: false, title: nil, text: text, undoText: nil, customAction: nil),
|
||||
action: { _ in return true }
|
||||
)
|
||||
self.parentController?.present(undoController, in: .current)
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
|
@ -1351,14 +1436,12 @@ private final class CollectionTabItemComponent: Component {
|
|||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(14.0), textColor: .white))
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(14.0), textColor: component.theme.list.itemSecondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
|
||||
let tintColor = component.theme.list.itemSecondaryTextColor
|
||||
|
||||
|
||||
var iconOffset: CGFloat = 0.0
|
||||
var iconSize = CGSize()
|
||||
if let icon = component.icon {
|
||||
|
|
@ -1392,7 +1475,7 @@ private final class CollectionTabItemComponent: Component {
|
|||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Input/Media/PanelBadgeAdd",
|
||||
tintColor: tintColor
|
||||
tintColor: component.theme.list.itemSecondaryTextColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
|
|
@ -1428,8 +1511,6 @@ private final class CollectionTabItemComponent: Component {
|
|||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
|
||||
transition.setTintColor(layer: titleView.layer, color: tintColor)
|
||||
}
|
||||
|
||||
let size: CGSize
|
||||
|
|
|
|||
|
|
@ -483,6 +483,14 @@ public final class TabSelectorComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public func scrollToStart() {
|
||||
self.setContentOffset(.zero, animated: true)
|
||||
}
|
||||
|
||||
public func scrollToEnd() {
|
||||
self.setContentOffset(CGPoint(x: self.contentSize.width - self.bounds.width, y: 0.0), animated: true)
|
||||
}
|
||||
|
||||
func update(component: TabSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let selectionColorUpdated = component.colors.selection != self.component?.colors.selection
|
||||
|
||||
|
|
|
|||
|
|
@ -900,7 +900,21 @@ final class AuthorizedApplicationContext {
|
|||
}
|
||||
|
||||
if openAppIfAny, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.rootController.viewControllers.last as? ViewController {
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true, payload: nil)
|
||||
self.context.sharedContext.openWebApp(
|
||||
context: self.context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
} else {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: alwaysKeepMessageId || isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,8 +128,7 @@ extension ChatControllerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
if "".isEmpty {
|
||||
//TODO:release
|
||||
if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) {
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ReplyToItem, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ func openWebAppImpl(
|
|||
simple: Bool,
|
||||
source: ChatOpenWebViewSource,
|
||||
skipTermsOfService: Bool,
|
||||
payload: String?
|
||||
payload: String?,
|
||||
verifyAgeCompletion: ((Int) -> Void)?
|
||||
) {
|
||||
if context.isFrozen {
|
||||
parentController.push(context.sharedContext.makeAccountFreezeInfoScreen(context: context))
|
||||
|
|
@ -246,7 +247,7 @@ func openWebAppImpl(
|
|||
navigationController = parentController.effectiveNavigationController
|
||||
}
|
||||
return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController)
|
||||
})
|
||||
}, verifyAgeCompletion: verifyAgeCompletion)
|
||||
controller.navigationPresentation = .flatModal
|
||||
parentController.push(controller)
|
||||
|
||||
|
|
@ -351,7 +352,21 @@ public extension ChatControllerImpl {
|
|||
}
|
||||
self.chatDisplayNode.dismissInput()
|
||||
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: EnginePeer(peer), chatPeer: EnginePeer(peer), threadId: self.chatLocation.threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: false, payload: nil)
|
||||
self.context.sharedContext.openWebApp(
|
||||
context: self.context,
|
||||
parentController: self,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
botPeer: EnginePeer(peer),
|
||||
chatPeer: EnginePeer(peer),
|
||||
threadId: self.chatLocation.threadId,
|
||||
buttonText: buttonText,
|
||||
url: url,
|
||||
simple: simple,
|
||||
source: source,
|
||||
skipTermsOfService: false,
|
||||
payload: nil,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate static func botRequestSwitchInline(context: AccountContext, controller: ChatControllerImpl?, peerId: EnginePeer.Id, botAddress: String, query: String, chatTypes: [ReplyMarkupButtonRequestPeerType]?, completion: @escaping () -> Void) -> Void {
|
||||
|
|
@ -616,7 +631,21 @@ public extension ChatControllerImpl {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
context.sharedContext.openWebApp(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: payload)
|
||||
context.sharedContext.openWebApp(
|
||||
context: context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
botPeer: botPeer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: false,
|
||||
payload: payload,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8907,7 +8907,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
commit()
|
||||
})
|
||||
} else {
|
||||
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, botPeer: peer, chatPeer: nil, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false, payload: botAppStart.payload)
|
||||
self.context.sharedContext.openWebApp(
|
||||
context: self.context,
|
||||
parentController: self,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: false,
|
||||
payload: botAppStart.payload,
|
||||
verifyAgeCompletion: nil
|
||||
)
|
||||
commit()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3882,8 +3882,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return incomingMessagePrivacyScreen(context: context, value: value, exceptions: exceptions, update: update)
|
||||
}
|
||||
|
||||
public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) {
|
||||
openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload)
|
||||
public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?) {
|
||||
openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload, verifyAgeCompletion: verifyAgeCompletion)
|
||||
}
|
||||
|
||||
public func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError> {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import PeerInfoScreen
|
|||
import PeerInfoStoryGridScreen
|
||||
import ShareWithPeersScreen
|
||||
import ChatEmptyNode
|
||||
//import FaceScanScreen
|
||||
import UndoUI
|
||||
|
||||
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
|
||||
|
|
@ -237,22 +236,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
self.accountSettingsController = accountSettingsController
|
||||
self.rootTabController = tabBarController
|
||||
self.pushViewController(tabBarController, animated: false)
|
||||
|
||||
// Queue.mainQueue().after(1.0) {
|
||||
// let context = self.context
|
||||
// let infoScreen = AgeVerificationScreen(context: context, completion: { [weak chatListController] proceed in
|
||||
// if proceed {
|
||||
// let scanScreen = FaceScanScreen(context: context, completion: { success in
|
||||
// let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: "Age check passed!", text: "You can now view this content.", cancel: nil, destructive: false), elevatedLayout: true, action: { _ in return true })
|
||||
// Queue.mainQueue().after(0.1) {
|
||||
// chatListController?.present(controller, in: .window(.root))
|
||||
// }
|
||||
// })
|
||||
// chatListController?.push(scanScreen)
|
||||
// }
|
||||
// })
|
||||
// chatListController.push(infoScreen)
|
||||
// }
|
||||
}
|
||||
|
||||
public func updateRootControllers(showCallsTab: Bool) {
|
||||
|
|
|
|||
|
|
@ -653,10 +653,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.animateTransitionIn()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
|
||||
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
|
||||
decisionHandler(.prompt)
|
||||
if self.controller?.isVerifyAgeBot == true && type == .camera {
|
||||
decisionHandler(.grant)
|
||||
} else {
|
||||
decisionHandler(.prompt)
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
|
||||
|
|
@ -1756,6 +1760,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
case "web_app_hide_keyboard":
|
||||
self.view.window?.endEditing(true)
|
||||
case "web_app_verify_age":
|
||||
if let json, self.controller?.isVerifyAgeBot == true {
|
||||
if let ageValue = json["age"] as? Double {
|
||||
self.controller?.verifyAgeCompletion?(Int(ageValue))
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -3281,6 +3291,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
public var completion: () -> Void = {}
|
||||
public var requestSwitchInline: (String, [ReplyMarkupButtonRequestPeerType]?, @escaping () -> Void) -> Void = { _, _, _ in }
|
||||
|
||||
public var verifyAgeCompletion: ((Int) -> Void)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) {
|
||||
self.context = context
|
||||
self.source = params.source
|
||||
|
|
@ -3323,16 +3335,20 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed)
|
||||
self.navigationItem.leftBarButtonItem?.target = self
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
|
||||
self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed)
|
||||
self.navigationItem.rightBarButtonItem?.target = self
|
||||
if !self.isVerifyAgeBot {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode)
|
||||
self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed)
|
||||
self.navigationItem.rightBarButtonItem?.target = self
|
||||
}
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme)
|
||||
titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified)
|
||||
self.navigationItem.titleView = titleView
|
||||
self.titleView = titleView
|
||||
if !self.isVerifyAgeBot {
|
||||
let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme)
|
||||
titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified)
|
||||
self.navigationItem.titleView = titleView
|
||||
self.titleView = titleView
|
||||
}
|
||||
|
||||
self.moreButtonNode.action = { [weak self] _, gesture in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -3387,6 +3403,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
private var isVerifyAgeBot: Bool {
|
||||
if let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String {
|
||||
return self.botAddress == ageBotUsername
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func beforeMaximize(navigationController: NavigationController, completion: @escaping () -> Void) {
|
||||
switch self.source {
|
||||
case .generic, .settings:
|
||||
|
|
@ -3857,7 +3880,8 @@ public func standaloneWebAppController(
|
|||
willDismiss: @escaping () -> Void = {},
|
||||
didDismiss: @escaping () -> Void = {},
|
||||
getNavigationController: @escaping () -> NavigationController? = { return nil },
|
||||
getSourceRect: (() -> CGRect?)? = nil
|
||||
getSourceRect: (() -> CGRect?)? = nil,
|
||||
verifyAgeCompletion: ((Int) -> Void)? = nil
|
||||
) -> ViewController {
|
||||
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.source == .menu, hasTextInput: false, isFullSize: params.fullSize, makeEntityInputView: {
|
||||
return nil
|
||||
|
|
@ -3868,6 +3892,7 @@ public func standaloneWebAppController(
|
|||
webAppController.completion = completion
|
||||
webAppController.getNavigationController = getNavigationController
|
||||
webAppController.requestSwitchInline = requestSwitchInline
|
||||
webAppController.verifyAgeCompletion = verifyAgeCompletion
|
||||
present(webAppController, webAppController.mediaPickerContext)
|
||||
}
|
||||
controller.willDismiss = willDismiss
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"app": "11.14",
|
||||
"app": "11.13.3",
|
||||
"xcode": "16.2",
|
||||
"bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217",
|
||||
"macos": "15"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue