Various fixes

This commit is contained in:
Ilya Laktyushin 2025-05-13 13:41:41 +04:00
parent 0ae2f9b53a
commit 0b448bf68f
10 changed files with 243 additions and 67 deletions

View File

@ -14320,3 +14320,16 @@ Sorry for the inconvenience.";
"Story.Editor.TooltipSelection_1" = "Tap here to view your %@ story";
"Story.Editor.TooltipSelection_any" = "Tap here to view your %@ stories";
"Gift.Buy.ErrorUnknown" = "An error occurred. Please try again.";
"Gift.Buy.ErrorPriceChanged.Title" = "Warning";
"Gift.Buy.ErrorPriceChanged.Text" = "The price has increased from **%1$@** to **%2$@**. Buy the gift anyway?";
"Gift.Buy.ErrorPriceChanged.Text.Stars_1" = "%@ Star";
"Gift.Buy.ErrorPriceChanged.Text.Stars_any" = "%@ Stars";
"ChatbotSetup.Gift.Warning.Title" = "Warning";
"ChatbotSetup.Gift.Warning.CombinedText" = "The bot **%@** will be able to **manage your gifts and stars**, including giving them away to other users.";
"ChatbotSetup.Gift.Warning.GiftsText" = "The bot **%@** will be able to **manage your gifts**, including giving them away to other users.";
"ChatbotSetup.Gift.Warning.StarsText" = "The bot **%@** will be able to **transfer your stars**.";
"ChatbotSetup.Gift.Warning.UsernameText" = "The bot **%@** will be able to **edit your username**, which will make your current username available for others to claim.";
"ChatbotSetup.Gift.Warning.Proceed" = "Proceed";

View File

@ -1841,7 +1841,7 @@ public final class ContactListNode: ASDisplayNode {
}
var isEmpty = false
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty && topPeers.isEmpty {
isEmpty = true
}

View File

@ -854,6 +854,7 @@ public enum TransferStarGiftError {
public enum BuyStarGiftError {
case generic
case priceChanged(Int64)
}
public enum UpdateStarGiftPriceError {
@ -865,7 +866,7 @@ public enum UpgradeStarGiftError {
case generic
}
func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id, price: Int64?) -> Signal<Never, BuyStarGiftError> {
let source: BotPaymentInvoiceSource = .starGiftResale(slug: slug, toPeerId: peerId)
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|> map(Optional.init)
@ -874,6 +875,9 @@ func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id
}
|> mapToSignal { paymentForm in
if let paymentForm {
if let paymentPrice = paymentForm.invoice.prices.first?.amount, let price, paymentPrice > price {
return .fail(.priceChanged(paymentPrice))
}
return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source)
|> mapError { _ -> BuyStarGiftError in
return .generic
@ -1473,26 +1477,53 @@ private final class ProfileGiftsContextImpl {
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
}
func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
if let count = self.count {
self.count = max(0, count - 1)
func buyStarGift(slug: String, peerId: EnginePeer.Id, price: Int64?) -> Signal<Never, BuyStarGiftError> {
var listingPrice: Int64?
if let gift = self.gifts.first(where: { gift in
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
return false
}), case let .unique(uniqueGift) = gift.gift {
listingPrice = uniqueGift.resellStars
}
self.gifts.removeAll(where: { gift in
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
return false
})
self.filteredGifts.removeAll(where: { gift in
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
return false
})
self.pushState()
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
if listingPrice == nil {
if let gift = self.filteredGifts.first(where: { gift in
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
return false
}), case let .unique(uniqueGift) = gift.gift {
listingPrice = uniqueGift.resellStars
}
}
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId, price: price ?? listingPrice)
|> afterCompleted { [weak self] in
guard let self else {
return
}
self.queue.async {
if let count = self.count {
self.count = max(0, count - 1)
}
self.gifts.removeAll(where: { gift in
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
return false
})
self.filteredGifts.removeAll(where: { gift in
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
return true
}
return false
})
self.pushState()
}
}
}
func removeStarGift(gift: TelegramCore.StarGift) {
@ -1975,11 +2006,11 @@ public final class ProfileGiftsContext {
}
}
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
public func buyStarGift(slug: String, peerId: EnginePeer.Id, price: Int64? = nil) -> Signal<Never, BuyStarGiftError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId).start(error: { error in
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId, price: price).start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
@ -2606,8 +2637,18 @@ private final class ResaleGiftsContextImpl {
self.loadMore()
}
func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
func buyStarGift(slug: String, peerId: EnginePeer.Id, price: Int64?) -> Signal<Never, BuyStarGiftError> {
var listingPrice: Int64?
if let gift = self.gifts.first(where: { gift in
if case let .unique(uniqueGift) = gift, uniqueGift.slug == slug {
return true
}
return false
}), case let .unique(uniqueGift) = gift {
listingPrice = uniqueGift.resellStars
}
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId, price: price ?? listingPrice)
|> afterCompleted { [weak self] in
guard let self else {
return
@ -2754,11 +2795,11 @@ public final class ResaleGiftsContext {
}
}
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
public func buyStarGift(slug: String, peerId: EnginePeer.Id, price: Int64? = nil) -> Signal<Never, BuyStarGiftError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId).start(error: { error in
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId, price: price).start(error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()

View File

@ -125,8 +125,8 @@ public extension TelegramEngine {
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
}
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
public func buyStarGift(slug: String, peerId: EnginePeer.Id, price: Int64?) -> Signal<Never, BuyStarGiftError> {
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId, price: price)
}
public func upgradeStarGift(formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {

View File

@ -270,8 +270,8 @@ final class GiftStoreScreenComponent: Component {
subject: .uniqueGift(uniqueGift, state.peerId),
allSubjects: allSubjects,
index: index,
buyGift: { slug, peerId in
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId) ?? .complete()
buyGift: { slug, peerId, price in
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId, price: price) ?? .complete()
},
updateResellStars: { price in
return self.state?.starGiftsContext.updateStarGiftResellPrice(slug: uniqueGift.slug, price: price) ?? .complete()

View File

@ -1145,7 +1145,7 @@ private final class GiftViewSheetContent: CombinedComponent {
}
}
func commitBuy(skipConfirmation: Bool = false) {
func commitBuy(acceptedPrice: Int64? = nil, skipConfirmation: Bool = false) {
guard let resellStars = self.subject.arguments?.resellStars, let starsContext = self.context.starsContext, let starsState = starsContext.currentState, case let .unique(uniqueGift) = self.subject.arguments?.gift else {
return
}
@ -1157,7 +1157,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let recipientPeerId = self.recipientPeerId ?? self.context.account.peerId
let action = {
let proceed: (Int64) -> Void = { formId in
let proceed: () -> Void = {
guard let controller = self.getController() as? GiftViewScreen else {
return
}
@ -1165,38 +1165,66 @@ private final class GiftViewSheetContent: CombinedComponent {
self.inProgress = true
self.updated()
let buyGiftImpl: ((String, EnginePeer.Id) -> Signal<Never, BuyStarGiftError>)
let buyGiftImpl: ((String, EnginePeer.Id, Int64?) -> Signal<Never, BuyStarGiftError>)
if let buyGift = controller.buyGift {
buyGiftImpl = { slug, peerId in
return buyGift(slug, peerId)
buyGiftImpl = { slug, peerId, price in
return buyGift(slug, peerId, price)
|> afterCompleted {
context.starsContext?.load(force: true)
}
}
} else {
buyGiftImpl = { slug, peerId in
return self.context.engine.payments.buyStarGift(slug: slug, peerId: peerId)
buyGiftImpl = { slug, peerId, price in
return self.context.engine.payments.buyStarGift(slug: slug, peerId: peerId, price: price)
|> afterCompleted {
context.starsContext?.load(force: true)
}
}
}
self.buyDisposable = (buyGiftImpl(uniqueGift.slug, recipientPeerId)
|> deliverOnMainQueue).start(error: { [weak self] error in
guard let self, let controller = self.getController() else {
return
}
self.inProgress = false
self.updated()
let errorText = presentationData.strings.Gift_Send_ErrorUnknown
let alertController = textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true)
controller.present(alertController, in: .window(.root))
}, completed: { [weak self, weak starsContext] in
guard let self, let controller = self.getController() as? GiftViewScreen else {
self.buyDisposable = (buyGiftImpl(uniqueGift.slug, recipientPeerId, acceptedPrice ?? resellStars)
|> deliverOnMainQueue).start(
error: { [weak self] error in
guard let self, let controller = self.getController() else {
return
}
self.inProgress = false
self.updated()
switch error {
case let .priceChanged(newPrice):
let errorTitle = presentationData.strings.Gift_Buy_ErrorPriceChanged_Title
let originalPriceString = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text_Stars(Int32(resellStars))
let newPriceString = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text_Stars(Int32(newPrice))
let errorText = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text(originalPriceString, newPriceString).string
let alertController = textAlertController(
context: context,
title: errorTitle,
text: errorText,
actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Gift_Buy_Confirm_BuyFor(Int32(newPrice)), action: { [weak self] in
guard let self else {
return
}
self.commitBuy(acceptedPrice: newPrice, skipConfirmation: true)
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})
],
actionLayout: .vertical,
parseMarkdown: true
)
controller.present(alertController, in: .window(.root))
default:
let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Gift_Buy_ErrorUnknown, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true)
controller.present(alertController, in: .window(.root))
}
},
completed: { [weak self, weak starsContext] in
guard let self,
let controller = self.getController() as? GiftViewScreen else {
return
}
self.inProgress = false
@ -1303,7 +1331,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.commitBuy(skipConfirmation: true)
} else {
proceed(buyForm.id)
proceed()
}
});
})
@ -1312,7 +1340,7 @@ private final class GiftViewSheetContent: CombinedComponent {
controller.push(purchaseController)
})
} else {
proceed(buyForm.id)
proceed()
}
}
}
@ -1321,7 +1349,7 @@ private final class GiftViewSheetContent: CombinedComponent {
action()
} else {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: recipientPeerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
@ -3565,7 +3593,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
fileprivate let convertToStars: (() -> Void)?
fileprivate let transferGift: ((Bool, EnginePeer.Id) -> Signal<Never, TransferStarGiftError>)?
fileprivate let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
fileprivate let buyGift: ((String, EnginePeer.Id) -> Signal<Never, BuyStarGiftError>)?
fileprivate let buyGift: ((String, EnginePeer.Id, Int64?) -> Signal<Never, BuyStarGiftError>)?
fileprivate let updateResellStars: ((Int64?) -> Signal<Never, UpdateStarGiftPriceError>)?
fileprivate let togglePinnedToTop: ((Bool) -> Bool)?
fileprivate let shareStory: ((StarGift.UniqueGift) -> Void)?
@ -3582,7 +3610,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
convertToStars: (() -> Void)? = nil,
transferGift: ((Bool, EnginePeer.Id) -> Signal<Never, TransferStarGiftError>)? = nil,
upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil,
buyGift: ((String, EnginePeer.Id) -> Signal<Never, BuyStarGiftError>)? = nil,
buyGift: ((String, EnginePeer.Id, Int64?) -> Signal<Never, BuyStarGiftError>)? = nil,
updateResellStars: ((Int64?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil,
togglePinnedToTop: ((Bool) -> Bool)? = nil,
shareStory: ((StarGift.UniqueGift) -> Void)? = nil

View File

@ -2851,6 +2851,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
private var hiddenMediaDisposable: Disposable?
private let hiddenAvatarRepresentationDisposable = MetaDisposable()
private var autoTranslateDisposable: Disposable?
private var resolvePeerByNameDisposable: MetaDisposable?
private let navigationActionDisposable = MetaDisposable()
private let enqueueMediaMessageDisposable = MetaDisposable()
@ -5113,6 +5115,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.joinChannelDisposable.dispose()
self.boostStatusDisposable?.dispose()
self.personalChannelsDisposable?.dispose()
self.autoTranslateDisposable?.dispose()
}
override func didLoad() {
@ -9183,7 +9186,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
private func displayAutoTranslateLocked() {
let _ = combineLatest(
guard self.autoTranslateDisposable == nil else {
return
}
self.autoTranslateDisposable = combineLatest(
queue: Queue.mainQueue(),
context.engine.peers.getChannelBoostStatus(peerId: self.peerId),
context.engine.peers.getMyBoostStatus()
@ -9197,6 +9203,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
})
controller.push(boostController)
self.autoTranslateDisposable?.dispose()
self.autoTranslateDisposable = nil
})
}

View File

@ -604,11 +604,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}
return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
},
buyGift: { [weak self] slug, peerId in
buyGift: { [weak self] slug, peerId, price in
guard let self else {
return .never()
}
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId)
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId, price: price)
},
updateResellStars: { [weak self] price in
guard let self, let reference = product.reference else {

View File

@ -174,6 +174,8 @@ final class ChatbotSetupScreenComponent: Component {
private var permissions: [Permission] = []
private var botRights: TelegramBusinessBotRights = []
private var temporaryEnabledPermissions = Set<String>()
override init(frame: CGRect) {
self.scrollView = ScrollView()
self.scrollView.showsVerticalScrollIndicator = true
@ -505,6 +507,44 @@ final class ChatbotSetupScreenComponent: Component {
}
}
private func presentStarGiftsWarningIfNeeded(_ key: TelegramBusinessBotRights, completion: @escaping (Bool) -> Void) -> Bool {
guard let component = self.component, let environment = self.environment, let botResolutionState = self.botResolutionState, case let .found(peer, _) = botResolutionState.state, let controller = environment.controller() else {
return false
}
if !key.contains(.transferAndUpgradeGifts) && !key.contains(.transferStars) && !key.contains(.editUsername) {
completion(true)
return false
} else {
let botUsername = "@\(peer.addressName ?? "")"
let text: String
if key.contains(.editUsername) {
text = environment.strings.ChatbotSetup_Gift_Warning_UsernameText(botUsername).string
} else if key == .transferAndUpgradeGifts {
text = environment.strings.ChatbotSetup_Gift_Warning_GiftsText(botUsername).string
} else if key == .transferStars {
text = environment.strings.ChatbotSetup_Gift_Warning_StarsText(botUsername).string
} else {
text = environment.strings.ChatbotSetup_Gift_Warning_CombinedText(botUsername).string
}
let alertController = textAlertController(context: component.context, title: environment.strings.ChatbotSetup_Gift_Warning_Title, text: text, actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
completion(false)
}),
TextAlertAction(type: .defaultAction, title: environment.strings.ChatbotSetup_Gift_Warning_Proceed, action: {
completion(true)
})
], parseMarkdown: true)
alertController.dismissed = { byOutsideTap in
if byOutsideTap {
completion(false)
}
}
controller.present(alertController, in: .window(.root))
return true
}
}
func update(component: ChatbotSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
@ -741,7 +781,7 @@ final class ChatbotSetupScreenComponent: Component {
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.isBusiness) {
botResolutionState.state = .found(peer: peer, isInstalled: true)
self.botResolutionState = botResolutionState
self.botRights = .All
self.botRights = [.reply, .readMessages, .deleteSentMessages, .deleteReceivedMessages]
self.state?.updated(transition: .spring(duration: 0.3))
} else {
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [
@ -1074,6 +1114,10 @@ final class ChatbotSetupScreenComponent: Component {
selectedCount += 1
}
}
if self.temporaryEnabledPermissions.contains(permission.id) {
value = true
}
titleItems.append(
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
@ -1101,11 +1145,33 @@ final class ChatbotSetupScreenComponent: Component {
return
}
if let subpermissions = permission.subpermissions {
for subpermission in subpermissions {
if subpermission.enabled {
if let key = subpermission.key {
if value {
var combinedKey: TelegramBusinessBotRights = []
for subpermission in subpermissions {
if subpermission.enabled, let key = subpermission.key {
combinedKey.insert(key)
}
}
self.temporaryEnabledPermissions.insert(permission.id)
let presentedWarning = self.presentStarGiftsWarningIfNeeded(combinedKey, completion: { [weak self] value in
guard let self else {
return
}
if value {
self.botRights.insert(combinedKey)
}
self.temporaryEnabledPermissions.remove(permission.id)
self.state?.updated(transition: .spring(duration: 0.4))
})
if !presentedWarning {
self.state?.updated(transition: .spring(duration: 0.4))
}
} else {
for subpermission in subpermissions {
if subpermission.enabled, let key = subpermission.key {
if value {
self.botRights.insert(key)
} else {
self.botRights.remove(key)
}
@ -1170,7 +1236,15 @@ final class ChatbotSetupScreenComponent: Component {
}
if let key = subpermission.key {
if !value {
self.botRights.insert(key)
let _ = self.presentStarGiftsWarningIfNeeded(key, completion: { [weak self] value in
guard let self else {
return
}
if value {
self.botRights.insert(key)
}
self.state?.updated(transition: .spring(duration: 0.4))
})
} else {
self.botRights.remove(key)
}

View File

@ -178,7 +178,18 @@ final class ChatTranslationPanelNode: ASDisplayNode {
}
@objc private func closePressed() {
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, count: -100, timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 7).startStandalone()
let isPremium = self.chatInterfaceState?.isPremium ?? false
var translationAvailable = isPremium
if let channel = self.chatInterfaceState?.renderedPeer?.chatMainPeer as? TelegramChannel, channel.flags.contains(.autoTranslateEnabled) {
translationAvailable = true
}
if translationAvailable {
self.interfaceInteraction?.hideTranslationPanel()
} else if !isPremium {
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, count: -100, timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 7).startStandalone()
}
}
@objc private func buttonPressed() {