Various fixes

This commit is contained in:
Ilya Laktyushin 2025-07-22 19:20:26 +02:00
parent 2af0b2a8b7
commit 9fbd857506
7 changed files with 157 additions and 132 deletions

View File

@ -14749,6 +14749,9 @@ Sorry for the inconvenience.";
"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.Unavailable.Title" = "18+";
"AgeVerification.Unavailable.Text" = "This media may contain sensitive content suitable only for adults.";
"AgeVerification.Success.Title" = "Age check passed!";
"AgeVerification.Success.Text" = "You can now view this content.";

View File

@ -912,12 +912,6 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
}
})
updateSensitiveContentDisposable.set(updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: value).start())
if !value {
let _ = updateAgeVerificationState(engine: context.engine, { _ in
return AgeVerificationState(verificationPassed: false)
}).start()
}
}
if value {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -2143,6 +2143,28 @@ public final class ProfileGiftsContext {
collectionIds: self.collectionIds
)
}
public func withCollectionIds(_ collectionIds: [Int32]?) -> StarGift {
return StarGift(
gift: self.gift,
reference: self.reference,
fromPeer: self.fromPeer,
date: self.date,
text: self.text,
entities: self.entities,
nameHidden: self.nameHidden,
savedToProfile: self.savedToProfile,
pinnedToTop: self.pinnedToTop,
convertStars: self.convertStars,
canUpgrade: self.canUpgrade,
canExportDate: self.canExportDate,
upgradeStars: self.upgradeStars,
transferStars: self.transferStars,
canTransferDate: self.canTransferDate,
canResaleDate: self.canResaleDate,
collectionIds: collectionIds
)
}
}
public enum DataState: Equatable {

View File

@ -190,10 +190,15 @@ private func _internal_reorderStarGiftCollections(account: Account, peerId: Engi
}
}
private func _internal_updateStarGiftCollection(account: Account, peerId: EnginePeer.Id, collectionId: Int32, giftsContext: ProfileGiftsContext?, actions: [ProfileGiftsCollectionsContext.UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
private func _internal_updateStarGiftCollection(account: Account, peerId: EnginePeer.Id, collectionId: Int32, giftsContext: ProfileGiftsContext?, allGiftsContext: ProfileGiftsContext?, actions: [ProfileGiftsCollectionsContext.UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
for action in actions {
switch action {
case let .addGifts(gifts):
let gifts = gifts.map { gift in
var collectionIds = gift.collectionIds ?? []
collectionIds.append(collectionId)
return gift.withCollectionIds(collectionIds)
}
giftsContext?.insertStarGifts(gifts: gifts)
case let .removeGifts(gifts):
giftsContext?.removeStarGifts(references: gifts)
@ -294,6 +299,7 @@ public final class ProfileGiftsCollectionsContext {
private let queue: Queue = .mainQueue()
private let account: Account
private let peerId: EnginePeer.Id
private weak var allGiftsContext: ProfileGiftsContext?
private let disposable = MetaDisposable()
@ -306,9 +312,10 @@ public final class ProfileGiftsCollectionsContext {
return self.stateValue.get()
}
public init(account: Account, peerId: EnginePeer.Id) {
public init(account: Account, peerId: EnginePeer.Id, allGiftsContext: ProfileGiftsContext?) {
self.account = account
self.peerId = peerId
self.allGiftsContext = allGiftsContext
self.reload()
}
@ -362,7 +369,7 @@ public final class ProfileGiftsCollectionsContext {
public func updateCollection(id: Int32, actions: [UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
let giftsContext = self.giftsContextForCollection(id: id)
return _internal_updateStarGiftCollection(account: self.account, peerId: self.peerId, collectionId: id, giftsContext: giftsContext, actions: actions)
return _internal_updateStarGiftCollection(account: self.account, peerId: self.peerId, collectionId: id, giftsContext: giftsContext, allGiftsContext: self.allGiftsContext, actions: actions)
|> deliverOn(self.queue)
|> afterNext { [weak self] collection in
guard let self else {

View File

@ -398,127 +398,92 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor
public func presentAgeVerification(context: AccountContext, parentController: ViewController, completion: @escaping () -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.ageVerificationState)
) |> deliverOnMainQueue).start(next: { [weak parentController] ageVerificationStatePreference in
let state = ageVerificationStatePreference?.get(AgeVerificationState.self) ?? AgeVerificationState.default
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)
let _ = (contentSettingsConfiguration(network: context.account.network)
|> deliverOnMainQueue).start(next: { [weak parentController] settings in
if !settings.canAdjustSensitiveContent {
let alertController = textAlertController(
context: context,
title: presentationData.strings.AgeVerification_Unavailable_Title,
text: presentationData.strings.AgeVerification_Unavailable_Text,
actions: []
)
parentController?.present(alertController, in: .window(.root))
return
}
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 {
var requiredAge = 18
if let value = context.currentAppConfiguration.with({ $0 }).data?["verify_age_min"] as? Double {
requiredAge = Int(value)
}
let success = { [weak parentController] in
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)
}
return .complete()
})
}
let infoScreen = AgeVerificationScreen(context: context, completion: { [weak parentController] check, availability in
if check {
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 scanScreen = FaceScanScreen(context: context, availability: availability, completion: { age in
}
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()
}
})
parentController?.push(scanScreen)
}
})
}
})
parentController?.push(infoScreen)
}
}
)
} else {
let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { age in
if age >= requiredAge {
success()
} else {
failure()
}
})
parentController?.push(scanScreen)
}
})
}
})
parentController?.push(infoScreen)
})
}
public func updateAgeVerificationState(engine: TelegramEngine, _ f: @escaping (AgeVerificationState) -> AgeVerificationState) -> Signal<Never, NoError> {
return engine.preferences.update(id: ApplicationSpecificPreferencesKeys.ageVerificationState, { entry in
let currentSettings: AgeVerificationState
if let entry = entry?.get(AgeVerificationState.self) {
currentSettings = entry
} else {
currentSettings = .default
}
return SharedPreferencesEntry(f(currentSettings))
})
}
public struct AgeVerificationState: Equatable, Codable {
public var verificationPassed: Bool
public static var `default`: AgeVerificationState {
return AgeVerificationState(verificationPassed: false)
}
public init(verificationPassed: Bool) {
self.verificationPassed = verificationPassed
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.verificationPassed = (try container.decode(Int32.self, forKey: "verificationPassed")) != 0
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode((self.verificationPassed ? 1 : 0) as Int32, forKey: "verificationPassed")
}
}

View File

@ -1082,7 +1082,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
if case .user = kind {
if isMyProfile || userPeerId != context.account.peerId {
profileGiftsContext = existingProfileGiftsContext ?? ProfileGiftsContext(account: context.account, peerId: userPeerId)
profileGiftsCollectionsContext = existingProfileGiftsCollectionsContext ?? ProfileGiftsCollectionsContext(account: context.account, peerId: userPeerId)
profileGiftsCollectionsContext = existingProfileGiftsCollectionsContext ?? ProfileGiftsCollectionsContext(account: context.account, peerId: userPeerId, allGiftsContext: profileGiftsContext)
} else {
profileGiftsContext = nil
profileGiftsCollectionsContext = nil
@ -1629,7 +1629,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId)
let profileGiftsCollectionsContext = ProfileGiftsCollectionsContext(account: context.account, peerId: peerId)
let profileGiftsCollectionsContext = ProfileGiftsCollectionsContext(account: context.account, peerId: peerId, allGiftsContext: profileGiftsContext)
let personalChannel = peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: false)

View File

@ -538,8 +538,6 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
)
self.parentController?.presentInGlobalOverlay(contextController)
}
func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
if let params = self.currentParams {
@ -590,12 +588,12 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
)
)),
isReorderable: collections.count > 1,
contextAction: { [weak self] sourceNode, gesture in
contextAction: canEditCollections ? { [weak self] sourceNode, gesture in
guard let self else {
return
}
self.openCollectionContextMenu(id: collection.id, sourceNode: sourceNode, gesture: gesture)
}
} : nil
))
}
@ -1024,6 +1022,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let undoController = UndoOverlayController(
presentationData: currentParams.presentationData,
content: .sticker(context: self.context, file: giftFile, loop: false, title: nil, text: text, undoText: nil, customAction: nil),
elevatedLayout: true,
action: { _ in return true }
)
self.parentController?.present(undoController, in: .current)
@ -1302,8 +1301,43 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_RemoveFromCollection, textColor: .destructive, textLayout: .twoLinesMax, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/RemoveFromCollection"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in
f(.default)
guard let self else {
return
}
if let reference = gift.reference {
let _ = self?.profileGiftsCollections.removeGifts(id: id, gifts: [reference]).start()
let _ = self.profileGiftsCollections.removeGifts(id: id, gifts: [reference]).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 collection = self.collections?.first(where: { $0.id == id }) {
let text: String
if let giftTitle {
text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollectionUnique(giftTitle, collection.title).string
} else {
text = currentParams.presentationData.strings.PeerInfo_Gifts_RemovedFromCollection(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),
elevatedLayout: true,
action: { _ in return true }
)
self.parentController?.present(undoController, in: .current)
}
})))
}