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.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.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.Title" = "Age check passed!";
"AgeVerification.Success.Text" = "You can now view this content."; "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()) 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 { if value {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@@ -2143,6 +2143,28 @@ public final class ProfileGiftsContext {
collectionIds: self.collectionIds 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 { 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 { for action in actions {
switch action { switch action {
case let .addGifts(gifts): 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) giftsContext?.insertStarGifts(gifts: gifts)
case let .removeGifts(gifts): case let .removeGifts(gifts):
giftsContext?.removeStarGifts(references: gifts) giftsContext?.removeStarGifts(references: gifts)
@@ -294,6 +299,7 @@ public final class ProfileGiftsCollectionsContext {
private let queue: Queue = .mainQueue() private let queue: Queue = .mainQueue()
private let account: Account private let account: Account
private let peerId: EnginePeer.Id private let peerId: EnginePeer.Id
private weak var allGiftsContext: ProfileGiftsContext?
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
@@ -306,9 +312,10 @@ public final class ProfileGiftsCollectionsContext {
return self.stateValue.get() return self.stateValue.get()
} }
public init(account: Account, peerId: EnginePeer.Id) { public init(account: Account, peerId: EnginePeer.Id, allGiftsContext: ProfileGiftsContext?) {
self.account = account self.account = account
self.peerId = peerId self.peerId = peerId
self.allGiftsContext = allGiftsContext
self.reload() self.reload()
} }
@@ -362,7 +369,7 @@ public final class ProfileGiftsCollectionsContext {
public func updateCollection(id: Int32, actions: [UpdateAction]) -> Signal<StarGiftCollection?, NoError> { public func updateCollection(id: Int32, actions: [UpdateAction]) -> Signal<StarGiftCollection?, NoError> {
let giftsContext = self.giftsContextForCollection(id: id) 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) |> deliverOn(self.queue)
|> afterNext { [weak self] collection in |> afterNext { [weak self] collection in
guard let self else { 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) { public func presentAgeVerification(context: AccountContext, parentController: ViewController, completion: @escaping () -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.engine.data.get( let _ = (contentSettingsConfiguration(network: context.account.network)
TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.ageVerificationState) |> deliverOnMainQueue).start(next: { [weak parentController] settings in
) |> deliverOnMainQueue).start(next: { [weak parentController] ageVerificationStatePreference in if !settings.canAdjustSensitiveContent {
let state = ageVerificationStatePreference?.get(AgeVerificationState.self) ?? AgeVerificationState.default let alertController = textAlertController(
if state.verificationPassed { context: context,
completion() title: presentationData.strings.AgeVerification_Unavailable_Title,
} else { text: presentationData.strings.AgeVerification_Unavailable_Text,
let miniappPromise = Promise<EnginePeer?>(nil) actions: []
var useVerifyAgeBot = false )
if let value = context.currentAppConfiguration.with({ $0 }).data?["force_verify_age_bot"] as? Bool, value { parentController?.present(alertController, in: .window(.root))
useVerifyAgeBot = value return
} }
if useVerifyAgeBot, let verifyAgeBotUsername = context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { let miniappPromise = Promise<EnginePeer?>(nil)
miniappPromise.set(context.engine.peers.resolvePeerByName(name: verifyAgeBotUsername, referrer: nil) var useVerifyAgeBot = false
|> mapToSignal { result in if let value = context.currentAppConfiguration.with({ $0 }).data?["force_verify_age_bot"] as? Bool, value {
if case let .result(peer) = result { useVerifyAgeBot = value
return .single(peer) }
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 failure = { [weak parentController] in
let infoScreen = AgeVerificationScreen(context: context, completion: { [weak parentController] check, availability 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 })
if check { parentController?.present(controller, in: .current)
var requiredAge = 18 }
if let value = context.currentAppConfiguration.with({ $0 }).data?["verify_age_min"] as? Double {
requiredAge = Int(value) let _ = (miniappPromise.get()
} |> take(1)
|> deliverOnMainQueue).start(next: { peer in
let success = { [weak parentController] in if let peer, let parentController {
let _ = updateAgeVerificationState(engine: context.engine, { _ in context.sharedContext.openWebApp(
return AgeVerificationState(verificationPassed: true) context: context,
}).start() parentController: parentController,
completion() updatedPresentationData: nil,
botPeer: peer,
let navigationController = parentController?.navigationController chatPeer: nil,
Queue.mainQueue().after(2.0) { threadId: nil,
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 }) buttonText: "",
(navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .current) url: "",
} simple: true,
} source: .generic,
skipTermsOfService: true,
let failure = { [weak parentController] in payload: nil,
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 }) verifyAgeCompletion: { age in
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
if age >= requiredAge { if age >= requiredAge {
success() success()
} else { } else {
failure() failure()
} }
}) }
parentController?.push(scanScreen) )
} } else {
}) let scanScreen = FaceScanScreen(context: context, availability: availability, completion: { age in
} if age >= requiredAge {
}) success()
parentController?.push(infoScreen) } 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 case .user = kind {
if isMyProfile || userPeerId != context.account.peerId { if isMyProfile || userPeerId != context.account.peerId {
profileGiftsContext = existingProfileGiftsContext ?? ProfileGiftsContext(account: context.account, peerId: userPeerId) 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 { } else {
profileGiftsContext = nil profileGiftsContext = nil
profileGiftsCollectionsContext = nil profileGiftsCollectionsContext = nil
@@ -1629,7 +1629,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} }
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId) 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) 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) self.parentController?.presentInGlobalOverlay(contextController)
} }
func updateScrolling(interactive: Bool = false, transition: ComponentTransition) { func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
if let params = self.currentParams { if let params = self.currentParams {
@@ -590,12 +588,12 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
) )
)), )),
isReorderable: collections.count > 1, isReorderable: collections.count > 1,
contextAction: { [weak self] sourceNode, gesture in contextAction: canEditCollections ? { [weak self] sourceNode, gesture in
guard let self else { guard let self else {
return return
} }
self.openCollectionContextMenu(id: collection.id, sourceNode: sourceNode, gesture: gesture) self.openCollectionContextMenu(id: collection.id, sourceNode: sourceNode, gesture: gesture)
} } : nil
)) ))
} }
@@ -1024,6 +1022,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let undoController = UndoOverlayController( let undoController = UndoOverlayController(
presentationData: currentParams.presentationData, presentationData: currentParams.presentationData,
content: .sticker(context: self.context, file: giftFile, loop: false, title: nil, text: text, undoText: nil, customAction: nil), content: .sticker(context: self.context, file: giftFile, loop: false, title: nil, text: text, undoText: nil, customAction: nil),
elevatedLayout: true,
action: { _ in return true } action: { _ in return true }
) )
self.parentController?.present(undoController, in: .current) 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 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) f(.default)
guard let self else {
return
}
if let reference = gift.reference { 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)
} }
}))) })))
} }