Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

# Conflicts:
#	submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift
This commit is contained in:
Mikhail Filimonov 2025-04-24 11:59:03 +01:00
commit 7482abfd3c
11 changed files with 97 additions and 31 deletions

View File

@ -131,6 +131,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
if (flags & Int32(1 << 30)) != 0 { if (flags & Int32(1 << 30)) != 0 {
channelFlags.insert(.isForum) channelFlags.insert(.isForum)
} }
if (flags2 & Int32(1 << 15)) != 0 {
channelFlags.insert(.autoTranslateEnabled)
}
var storiesHidden: Bool? var storiesHidden: Bool?
if flags2 & (1 << 2) == 0 { // stories_hidden_min if flags2 & (1 << 2) == 0 { // stories_hidden_min

View File

@ -181,6 +181,7 @@ public struct TelegramChannelFlags: OptionSet {
public static let joinToSend = TelegramChannelFlags(rawValue: 1 << 9) public static let joinToSend = TelegramChannelFlags(rawValue: 1 << 9)
public static let requestToJoin = TelegramChannelFlags(rawValue: 1 << 10) public static let requestToJoin = TelegramChannelFlags(rawValue: 1 << 10)
public static let isForum = TelegramChannelFlags(rawValue: 1 << 11) public static let isForum = TelegramChannelFlags(rawValue: 1 << 11)
public static let autoTranslateEnabled = TelegramChannelFlags(rawValue: 1 << 12)
} }
public final class TelegramChannel: Peer, Equatable { public final class TelegramChannel: Peer, Equatable {

View File

@ -2467,5 +2467,32 @@ public extension TelegramEngine.EngineData.Item {
} }
} }
} }
public struct AutoTranslateEnabled: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .peer(peerId: self.id, components: [])
}
func extract(view: PostboxView) -> Result {
guard let view = view as? PeerView else {
preconditionFailure()
}
if let channel = peerViewMainPeer(view) as? TelegramChannel {
return channel.flags.contains(.autoTranslateEnabled)
}
return false
}
}
} }
} }

View File

@ -179,7 +179,6 @@ public enum BotPaymentFormRequestError {
case alreadyActive case alreadyActive
case noPaymentNeeded case noPaymentNeeded
case disallowedStarGift case disallowedStarGift
case starGiftResellTooEarly(Int32)
} }
extension BotPaymentInvoice { extension BotPaymentInvoice {
@ -483,11 +482,6 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw
return .fail(.noPaymentNeeded) return .fail(.noPaymentNeeded)
} else if error.errorDescription == "USER_DISALLOWED_STARGIFTS" { } else if error.errorDescription == "USER_DISALLOWED_STARGIFTS" {
return .fail(.disallowedStarGift) return .fail(.disallowedStarGift)
} else if error.errorDescription.hasPrefix("STARGIFT_RESELL_TOO_EARLY_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "STARGIFT_RESELL_TOO_EARLY_".count)...])
if let value = Int32(timeout) {
return .fail(.starGiftResellTooEarly(value))
}
} }
return .fail(.generic) return .fail(.generic)
} }

View File

@ -847,7 +847,6 @@ public enum TransferStarGiftError {
public enum BuyStarGiftError { public enum BuyStarGiftError {
case generic case generic
case starGiftResellTooEarly(Int32)
} }
public enum UpdateStarGiftPriceError { public enum UpdateStarGiftPriceError {
@ -855,7 +854,6 @@ public enum UpdateStarGiftPriceError {
case starGiftResellTooEarly(Int32) case starGiftResellTooEarly(Int32)
} }
public enum UpgradeStarGiftError { public enum UpgradeStarGiftError {
case generic case generic
} }
@ -865,12 +863,7 @@ func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil) return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { error -> Signal<BotPaymentForm?, BuyStarGiftError> in |> `catch` { error -> Signal<BotPaymentForm?, BuyStarGiftError> in
switch error { return .fail(.generic)
case let .starGiftResellTooEarly(value):
return .fail(.starGiftResellTooEarly(value))
default:
return .fail(.generic)
}
} }
|> mapToSignal { paymentForm in |> mapToSignal { paymentForm in
if let paymentForm { if let paymentForm {
@ -2360,10 +2353,10 @@ func _internal_updateStarGiftResalePrice(account: Account, reference: StarGiftRe
return account.network.request(Api.functions.payments.updateStarGiftPrice(stargift: starGift, resellStars: price ?? 0)) return account.network.request(Api.functions.payments.updateStarGiftPrice(stargift: starGift, resellStars: price ?? 0))
|> mapError { error -> UpdateStarGiftPriceError in |> mapError { error -> UpdateStarGiftPriceError in
if error.errorDescription.hasPrefix("STARGIFT_RESELL_TOO_EARLY_") { if error.errorDescription.hasPrefix("STARGIFT_RESELL_TOO_EARLY_") {
let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "STARGIFT_RESELL_TOO_EARLY_".count)...]) let timeout = String(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "STARGIFT_RESELL_TOO_EARLY_".count)...])
if let value = Int32(timeout) { if let value = Int32(timeout) {
return .starGiftResellTooEarly(value) return .starGiftResellTooEarly(value)
} }
} }
return .generic return .generic
} }

View File

@ -0,0 +1,19 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func _internal_toggleAutoTranslation(account: Account, peerId: PeerId, enabled: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.toggleAutotranslation(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse)) |> `catch` { _ in .complete() } |> map { updates -> Void in
account.stateManager.addUpdates(updates)
}
} else {
return .complete()
}
}
|> switchToLatest
|> ignoreValues
}

View File

@ -1693,6 +1693,10 @@ public extension TelegramEngine {
let _ = _internal_removeChatManagingBot(account: self.account, chatId: chatId).startStandalone() let _ = _internal_removeChatManagingBot(account: self.account, chatId: chatId).startStandalone()
} }
public func toggleAutoTranslation(peerId: EnginePeer.Id, enabled: Bool) -> Signal<Never, NoError> {
return _internal_toggleAutoTranslation(account: self.account, peerId: peerId, enabled: enabled)
}
public func resolveMessageLink(slug: String) -> Signal<TelegramResolvedMessageLink?, NoError> { public func resolveMessageLink(slug: String) -> Signal<TelegramResolvedMessageLink?, NoError> {
return self.account.network.request(Api.functions.account.resolveBusinessChatLink(slug: slug)) return self.account.network.request(Api.functions.account.resolveBusinessChatLink(slug: slug))
|> map(Optional.init) |> map(Optional.init)

View File

@ -8155,7 +8155,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
let trimmedValues = values.withUpdatedVideoTrimRange(start ..< min(start + storyMaxVideoDuration, originalDuration)) let trimmedValues = values.withUpdatedVideoTrimRange(start ..< min(start + storyMaxVideoDuration, originalDuration))
var editingItem = EditingItem(asset: asset) var editingItem = EditingItem(asset: asset)
editingItem.caption = self.node.getCaption() if i == 0 {
editingItem.caption = self.node.getCaption()
}
editingItem.values = trimmedValues editingItem.values = trimmedValues
multipleItems.append(editingItem) multipleItems.append(editingItem)

View File

@ -2007,7 +2007,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
return result return result
} }
private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatLocation: ChatLocation, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] { private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, state: PeerInfoState, chatLocation: ChatLocation, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] {
enum Section: Int, CaseIterable { enum Section: Int, CaseIterable {
case notifications case notifications
case groupLocation case groupLocation
@ -2276,11 +2276,12 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
interaction.editingOpenNameColorSetup() interaction.editingOpenNameColorSetup()
})) }))
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
var isLocked = true var isLocked = true
if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel >= 3 { if let boostLevel = boostStatus?.level, boostLevel >= BoostSubject.autoTranslate.requiredLevel(group: false, context: context, configuration: premiumConfiguration) {
isLocked = false isLocked = false
} }
items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemPeerAutoTranslate, text: presentationData.strings.Channel_Info_AutoTranslate, value: false, icon: UIImage(bundleImageName: "Settings/Menu/AutoTranslate"), isLocked: isLocked, toggled: { value in items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemPeerAutoTranslate, text: presentationData.strings.Channel_Info_AutoTranslate, value: channel.flags.contains(.autoTranslateEnabled), icon: UIImage(bundleImageName: "Settings/Menu/AutoTranslate"), isLocked: isLocked, toggled: { value in
if isLocked { if isLocked {
interaction.displayAutoTranslateLocked() interaction.displayAutoTranslateLocked()
} else { } else {
@ -9157,7 +9158,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
private func toggleAutoTranslate(isEnabled: Bool) { private func toggleAutoTranslate(isEnabled: Bool) {
self.activeActionDisposable.set(self.context.engine.peers.toggleAutoTranslation(peerId: self.peerId, enabled: isEnabled).start())
} }
private func displayAutoTranslateLocked() { private func displayAutoTranslateLocked() {
@ -11918,7 +11919,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
var validEditingSections: [AnyHashable] = [] var validEditingSections: [AnyHashable] = []
let editItems = (self.isSettings || self.isMyProfile) ? settingsEditingItems(data: self.data, state: self.state, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isMyProfile: self.isMyProfile) : editingItems(data: self.data, state: self.state, chatLocation: self.chatLocation, context: self.context, presentationData: self.presentationData, interaction: self.interaction) let editItems = (self.isSettings || self.isMyProfile) ? settingsEditingItems(data: self.data, state: self.state, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isMyProfile: self.isMyProfile) : editingItems(data: self.data, boostStatus: self.boostStatus, state: self.state, chatLocation: self.chatLocation, context: self.context, presentationData: self.presentationData, interaction: self.interaction)
for (sectionId, sectionItems) in editItems { for (sectionId, sectionItems) in editItems {
var insets = UIEdgeInsets() var insets = UIEdgeInsets()

View File

@ -675,17 +675,22 @@ extension ChatControllerImpl {
let isHidden = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: peerId)) let isHidden = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: peerId))
|> distinctUntilChanged |> distinctUntilChanged
let hasAutoTranslate = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AutoTranslateEnabled(id: peerId))
|> distinctUntilChanged
self.translationStateDisposable = (combineLatest( self.translationStateDisposable = (combineLatest(
queue: .concurrentDefaultQueue(), queue: .concurrentDefaultQueue(),
isPremium, isPremium,
isHidden, isHidden,
hasAutoTranslate,
ApplicationSpecificNotice.translationSuggestion(accountManager: self.context.sharedContext.accountManager) ApplicationSpecificNotice.translationSuggestion(accountManager: self.context.sharedContext.accountManager)
) |> mapToSignal { isPremium, isHidden, counterAndTimestamp -> Signal<ChatPresentationTranslationState?, NoError> in ) |> mapToSignal { isPremium, isHidden, hasAutoTranslate, counterAndTimestamp -> Signal<ChatPresentationTranslationState?, NoError> in
var maybeSuggestPremium = false var maybeSuggestPremium = false
if counterAndTimestamp.0 >= 3 { if counterAndTimestamp.0 >= 3 {
maybeSuggestPremium = true maybeSuggestPremium = true
} }
if (isPremium || maybeSuggestPremium) && !isHidden { if (isPremium || maybeSuggestPremium || hasAutoTranslate) && !isHidden {
return chatTranslationState(context: context, peerId: peerId) return chatTranslationState(context: context, peerId: peerId)
|> map { translationState -> ChatPresentationTranslationState? in |> map { translationState -> ChatPresentationTranslationState? in
if let translationState, !translationState.fromLang.isEmpty && (translationState.fromLang != baseLanguageCode || translationState.isEnabled) { if let translationState, !translationState.fromLang.isEmpty && (translationState.fromLang != baseLanguageCode || translationState.isEnabled) {

View File

@ -180,10 +180,17 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
baseLang = String(baseLang.dropLast(rawSuffix.count)) baseLang = String(baseLang.dropLast(rawSuffix.count))
} }
return context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
|> mapToSignal { sharedData in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) ?? TranslationSettings.defaultSettings return combineLatest(
if !settings.translateChats { context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
|> map { sharedData -> TranslationSettings in
return sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) ?? TranslationSettings.defaultSettings
},
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AutoTranslateEnabled(id: peerId))
)
|> mapToSignal { settings, autoTranslateEnabled in
if !settings.translateChats && !autoTranslateEnabled {
return .single(nil) return .single(nil)
} }
@ -286,12 +293,22 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
if loggingEnabled { if loggingEnabled {
Logger.shared.log("ChatTranslation", "Ended with: \(fromLang)") Logger.shared.log("ChatTranslation", "Ended with: \(fromLang)")
} }
let isEnabled: Bool
if let currentIsEnabled = cached?.isEnabled {
isEnabled = currentIsEnabled
} else if autoTranslateEnabled {
isEnabled = true
} else {
isEnabled = false
}
let state = ChatTranslationState( let state = ChatTranslationState(
baseLang: baseLang, baseLang: baseLang,
fromLang: fromLang, fromLang: fromLang,
timestamp: currentTime, timestamp: currentTime,
toLang: cached?.toLang, toLang: cached?.toLang,
isEnabled: cached?.isEnabled ?? false isEnabled: isEnabled
) )
let _ = updateChatTranslationState(engine: context.engine, peerId: peerId, state: state).start() let _ = updateChatTranslationState(engine: context.engine, peerId: peerId, state: state).start()
if !dontTranslateLanguages.contains(fromLang) { if !dontTranslateLanguages.contains(fromLang) {