Various fixes

This commit is contained in:
Ilya Laktyushin 2025-03-08 00:01:18 +04:00
parent c39ba69250
commit a9ce88255d
9 changed files with 224 additions and 106 deletions

View File

@ -13941,6 +13941,7 @@ Sorry for the inconvenience.";
"Stars.Intro.BuyShort" = "Top Up";
"Stars.Intro.Withdraw" = "Withdraw";
"Stars.Intro.Stats" = "Stats";
"Group.Info.Settings" = "Group Settings";
@ -13992,3 +13993,6 @@ Sorry for the inconvenience.";
"Conversation.VideoTimeLinkCopied" = "Link with start time at %@ copied to clipboard.";
"Share.VideoStartAt" = "Start at %@";
"SendStarReactions.SubtitleFrom" = "from %@";
"Stars.AccountRevenue.Proceeds.Info" = "Stars from your total balance can be withdrawn as rewards 21 days after they are earned.";
"Stars.AccountRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()";

View File

@ -48,6 +48,7 @@ public enum ContactMultiselectionControllerMode {
public var onlyUsers: Bool
public var disableChannels: Bool
public var disableBots: Bool
public var disableContacts: Bool
public init(
title: String,
@ -59,7 +60,8 @@ public enum ContactMultiselectionControllerMode {
displayPresence: Bool = false,
onlyUsers: Bool = false,
disableChannels: Bool = false,
disableBots: Bool = false
disableBots: Bool = false,
disableContacts: Bool = false
) {
self.title = title
self.searchPlaceholder = searchPlaceholder
@ -71,6 +73,7 @@ public enum ContactMultiselectionControllerMode {
self.onlyUsers = onlyUsers
self.disableChannels = disableChannels
self.disableBots = disableBots
self.disableContacts = disableContacts
}
}

View File

@ -281,8 +281,9 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
chatListFilters: nil,
onlyUsers: false,
disableChannels: true,
disableBots: false
)), filters: [.excludeSelf]))
disableBots: true,
disableContacts: true
))))
addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] result in
@ -342,7 +343,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
controller.navigationPresentation = .modal
pushControllerImpl?(controller)
} else {
let controller = selectivePrivacyPeersController(context: context, title: presentationData.strings.Privacy_Messages_Exceptions_Title, footer: presentationData.strings.Privacy_Messages_RemoveFeeInfo, initialPeers: peerIds, initialEnableForPremium: false, displayPremiumCategory: false, initialEnableForBots: false, displayBotsCategory: false, updated: { updatedPeerIds, _, _ in
let controller = selectivePrivacyPeersController(context: context, title: presentationData.strings.Privacy_Messages_Exceptions_Title, footer: presentationData.strings.Privacy_Messages_RemoveFeeInfo, hideContacts: true, initialPeers: peerIds, initialEnableForPremium: false, displayPremiumCategory: false, initialEnableForBots: false, displayBotsCategory: false, updated: { updatedPeerIds, _, _ in
updateState { state in
var updatedState = state
updatedState.disableFor = updatedPeerIds

View File

@ -322,7 +322,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati
return entries
}
public func selectivePrivacyPeersController(context: AccountContext, title: String, footer: String? = nil, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], initialEnableForPremium: Bool, displayPremiumCategory: Bool, initialEnableForBots: Bool, displayBotsCategory: Bool, updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer], Bool, Bool) -> Void) -> ViewController {
public func selectivePrivacyPeersController(context: AccountContext, title: String, footer: String? = nil, hideContacts: Bool = false, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], initialEnableForPremium: Bool, displayPremiumCategory: Bool, initialEnableForBots: Bool, displayBotsCategory: Bool, updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer], Bool, Bool) -> Void) -> ViewController {
let initialState = SelectivePrivacyPeersControllerState(enableForPremium: initialEnableForPremium, enableForBots: initialEnableForBots, editing: false, peerIdWithRevealedOptions: nil)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
@ -428,7 +428,8 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
chatListFilters: nil,
onlyUsers: false,
disableChannels: true,
disableBots: false
disableBots: hideContacts,
disableContacts: hideContacts
)), alwaysEnabled: true))
addPeerDisposable.set((controller.result
|> take(1)

View File

@ -29,6 +29,7 @@ final class StarsStatisticsScreenComponent: Component {
let peerId: EnginePeer.Id
let revenueContext: StarsRevenueStatsContext
let openTransaction: (StarsContext.State.Transaction) -> Void
let buy: () -> Void
let withdraw: () -> Void
let showTimeoutTooltip: (Int32) -> Void
let buyAds: () -> Void
@ -38,6 +39,7 @@ final class StarsStatisticsScreenComponent: Component {
peerId: EnginePeer.Id,
revenueContext: StarsRevenueStatsContext,
openTransaction: @escaping (StarsContext.State.Transaction) -> Void,
buy: @escaping () -> Void,
withdraw: @escaping () -> Void,
showTimeoutTooltip: @escaping (Int32) -> Void,
buyAds: @escaping () -> Void
@ -46,6 +48,7 @@ final class StarsStatisticsScreenComponent: Component {
self.peerId = peerId
self.revenueContext = revenueContext
self.openTransaction = openTransaction
self.buy = buy
self.withdraw = withdraw
self.showTimeoutTooltip = showTimeoutTooltip
self.buyAds = buyAds
@ -508,7 +511,7 @@ final class StarsStatisticsScreenComponent: Component {
)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.Stars_BotRevenue_Proceeds_Info,
string: component.peerId == component.context.account.peerId ? strings.Stars_AccountRevenue_Proceeds_Info : strings.Stars_BotRevenue_Proceeds_Info,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
@ -558,8 +561,8 @@ final class StarsStatisticsScreenComponent: Component {
return (TelegramTextAttributes.URL, contents)
})
let balanceInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(strings.Stars_BotRevenue_Withdraw_Info, attributes: termsMarkdownAttributes, textAlignment: .natural
))
let balanceRawString = component.peerId == component.context.account.peerId ? strings.Stars_AccountRevenue_Withdraw_Info : strings.Stars_BotRevenue_Withdraw_Info
let balanceInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(balanceRawString, attributes: termsMarkdownAttributes, textAlignment: .natural))
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
}
@ -567,6 +570,96 @@ final class StarsStatisticsScreenComponent: Component {
balanceInfoString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceInfoString.string))
}
var balanceItems: [AnyComponentWithIdentity<Empty>] = []
if component.peerId == component.context.account.peerId {
let withdrawEnabled = self.starsState?.balances.withdrawEnabled ?? false
balanceItems = [
AnyComponentWithIdentity(id: 0, component: AnyComponent(
StarsBalanceComponent(
theme: environment.theme,
strings: strings,
dateTimeFormat: environment.dateTimeFormat,
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
rate: self.starsState?.usdRate ?? 0,
actionTitle: strings.Stars_Intro_BuyShort,
actionAvailable: true,
actionIsEnabled: true,
actionIcon: PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.buy()
},
secondaryActionTitle: withdrawEnabled ? strings.Stars_Intro_Withdraw : nil,
secondaryActionIcon: PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme),
secondaryActionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp,
secondaryAction: { [weak self] in
guard let self, let component = self.component else {
return
}
var remainingCooldownSeconds: Int32 = 0
if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp {
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
if remainingCooldownSeconds > 0 {
component.showTimeoutTooltip(cooldownUntilTimestamp)
} else {
component.withdraw()
}
} else {
component.withdraw()
}
}
)
))
]
} else {
balanceItems = [
AnyComponentWithIdentity(id: 0, component: AnyComponent(
StarsBalanceComponent(
theme: environment.theme,
strings: strings,
dateTimeFormat: environment.dateTimeFormat,
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
rate: self.starsState?.usdRate ?? 0,
actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort,
actionAvailable: true,
actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true,
actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp,
actionIcon: PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
var remainingCooldownSeconds: Int32 = 0
if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp {
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
if remainingCooldownSeconds > 0 {
component.showTimeoutTooltip(cooldownUntilTimestamp)
} else {
component.withdraw()
}
} else {
component.withdraw()
}
},
secondaryActionTitle: strings.Stars_BotRevenue_Withdraw_BuyAds,
secondaryActionIcon: PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme),
secondaryAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.buyAds()
}
)
))
]
}
let balanceSize = self.balanceView.update(
transition: .immediate,
component: AnyComponent(ListSectionComponent(
@ -597,44 +690,7 @@ final class StarsStatisticsScreenComponent: Component {
}
}
)),
items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(
StarsBalanceComponent(
theme: environment.theme,
strings: strings,
dateTimeFormat: environment.dateTimeFormat,
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
rate: self.starsState?.usdRate ?? 0,
actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort,
actionAvailable: true,
actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true,
actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
var remainingCooldownSeconds: Int32 = 0
if let cooldownUntilTimestamp = self.starsState?.balances.nextWithdrawalTimestamp {
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
if remainingCooldownSeconds > 0 {
component.showTimeoutTooltip(cooldownUntilTimestamp)
} else {
component.withdraw()
}
} else {
component.withdraw()
}
},
secondaryActionTitle: strings.Stars_BotRevenue_Withdraw_BuyAds,
secondaryAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.buyAds()
}
)
))]
items: balanceItems
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height)
@ -799,11 +855,14 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
private weak var tooltipScreen: UndoOverlayController?
private var timer: Foundation.Timer?
private let options = Promise<[StarsTopUpOption]>()
public init(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) {
self.context = context
self.peerId = peerId
self.revenueContext = revenueContext
var buyImpl: (() -> Void)?
var withdrawImpl: (() -> Void)?
var buyAdsImpl: (() -> Void)?
var showTimeoutTooltipImpl: ((Int32) -> Void)?
@ -815,6 +874,9 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
openTransaction: { transaction in
openTransactionImpl?(transaction)
},
buy: {
buyImpl?()
},
withdraw: {
withdrawImpl?()
},
@ -842,6 +904,46 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
})
}
if peerId == context.account.peerId {
self.options.set(.single([]) |> then(context.engine.payments.starsTopUpOptions()))
}
buyImpl = { [weak self] in
guard let self else {
return
}
let _ = (self.options.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] options in
guard let self, let starsContext = context.starsContext else {
return
}
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, completion: { [weak self] stars in
guard let self else {
return
}
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let resultController = UndoOverlayController(
presentationData: presentationData,
content: .universal(
animation: "StarsBuy",
scale: 0.066,
colors: [:],
title: presentationData.strings.Stars_Intro_PurchasedTitle,
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string,
customUndoText: nil,
timeout: nil
),
elevatedLayout: false,
action: { _ in return true})
self.present(resultController, in: .window(.root))
})
self.push(controller)
})
}
withdrawImpl = { [weak self] in
guard let self else {
return

View File

@ -630,7 +630,7 @@ final class StarsTransactionsScreenComponent: Component {
contentHeight += descriptionSize.height
contentHeight += 29.0
let withdrawAvailable = self.revenueState?.balances.withdrawEnabled ?? false
let withdrawAvailable = (self.revenueState?.balances.overallRevenue.value ?? 0) > 0
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
let balanceSize = self.balanceView.update(
@ -656,26 +656,14 @@ final class StarsTransactionsScreenComponent: Component {
}
component.buy()
},
secondaryActionTitle: withdrawAvailable ? environment.strings.Stars_Intro_Withdraw : nil,
secondaryActionIcon: withdrawAvailable ? PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme) : nil,
secondaryActionTitle: withdrawAvailable ? environment.strings.Stars_Intro_Stats : nil,
secondaryActionIcon: withdrawAvailable ? PresentationResourcesItemList.itemListStatsIcon(environment.theme) : nil,
secondaryActionCooldownUntilTimestamp: self.revenueState?.balances.nextWithdrawalTimestamp,
secondaryAction: withdrawAvailable ? { [weak self] in
guard let self, let component = self.component else {
return
}
var remainingCooldownSeconds: Int32 = 0
if let cooldownUntilTimestamp = self.revenueState?.balances.nextWithdrawalTimestamp {
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
if remainingCooldownSeconds > 0 {
component.showTimeoutTooltip(cooldownUntilTimestamp)
} else {
component.withdraw()
}
} else {
component.withdraw()
}
component.withdraw()
} : nil,
additionalAction: (premiumConfiguration.starsGiftsPurchaseAvailable && !premiumConfiguration.isPremiumDisabled) ? AnyComponent(
Button(
@ -739,7 +727,7 @@ final class StarsTransactionsScreenComponent: Component {
return
}
let _ = (component.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: component.context, peerId: component.context.account.peerId, mode: .connectedPrograms)
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
guard let self, let component = self.component else {
return
}
@ -1236,48 +1224,52 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
guard let self else {
return
}
let _ = (context.engine.peers.checkStarsRevenueWithdrawalAvailability()
|> deliverOnMainQueue).start(error: { [weak self] error in
guard let self else {
return
}
switch error {
case .serverProvided:
return
case .requestPassword:
let _ = (self.starsRevenueStatsContext.state
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let self else {
return
}
let controller = self.context.sharedContext.makeStarsWithdrawalScreen(context: context, completion: { [weak self] amount in
guard let self else {
return
}
let controller = confirmStarsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: amount, present: { [weak self] c, a in
self?.present(c, in: .window(.root))
}, completion: { [weak self] url in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
Queue.mainQueue().after(2.0) {
self?.starsRevenueStatsContext.reload()
}
})
self.present(controller, in: .window(.root))
})
self.push(controller)
})
default:
let controller = starsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: 0, initialError: error, present: { [weak self] c, a in
self?.present(c, in: .window(.root))
}, completion: { _ in
})
self.present(controller, in: .window(.root))
}
})
let controller = self.context.sharedContext.makeStarsStatisticsScreen(context: context, peerId: context.account.peerId, revenueContext: self.starsRevenueStatsContext)
self.push(controller)
// let _ = (context.engine.peers.checkStarsRevenueWithdrawalAvailability()
// |> deliverOnMainQueue).start(error: { [weak self] error in
// guard let self else {
// return
// }
// switch error {
// case .serverProvided:
// return
// case .requestPassword:
// let _ = (self.starsRevenueStatsContext.state
// |> take(1)
// |> deliverOnMainQueue).start(next: { [weak self] state in
// guard let self else {
// return
// }
// let controller = self.context.sharedContext.makeStarsWithdrawalScreen(context: context, completion: { [weak self] amount in
// guard let self else {
// return
// }
// let controller = confirmStarsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: amount, present: { [weak self] c, a in
// self?.present(c, in: .window(.root))
// }, completion: { [weak self] url in
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
// context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
//
// Queue.mainQueue().after(2.0) {
// self?.starsRevenueStatsContext.reload()
// }
// })
// self.present(controller, in: .window(.root))
// })
// self.push(controller)
// })
// default:
// let controller = starsRevenueWithdrawalController(context: context, peerId: context.account.peerId, amount: 0, initialError: error, present: { [weak self] c, a in
// self?.present(c, in: .window(.root))
// }, completion: { _ in
//
// })
// self.present(controller, in: .window(.root))
// }
// })
}
showTimeoutTooltipImpl = { [weak self] cooldownUntilTimestamp in

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "stats_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -146,6 +146,9 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
))
} else if chatSelection.disableChannels || chatSelection.disableBots {
var categories: ChatListFilterPeerCategories = [.contacts, .nonContacts, .groups, .bots, .channels]
if chatSelection.disableContacts {
categories.remove(.contacts)
}
if chatSelection.disableChannels {
categories.remove(.channels)
}