This commit is contained in:
Isaac 2025-12-13 00:04:46 +08:00
commit 6d42458fe7
12 changed files with 282 additions and 158 deletions

View file

@ -5,8 +5,8 @@
"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing.";
"NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch.";
"NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls.";
"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library.";
"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library.";
"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library and save the ones you capture.";
"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library and save the ones you capture.";
"NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound.";
"NSSiriUsageDescription" = "You can use Siri to send messages.";
"NSFaceIDUsageDescription" = "You can use Face ID to unlock the app.";

View file

@ -1376,7 +1376,7 @@ public protocol SharedAccountContext: AnyObject {
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?)
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
@ -1678,7 +1678,7 @@ public struct StarsSubscriptionConfiguration {
return StarsSubscriptionConfiguration(
maxFee: 2500,
usdWithdrawRate: 1200,
tonUsdRate: 0,
tonUsdRate: 1.0,
paidMessageMaxAmount: 10000,
paidMessageCommissionPermille: 850,
paidMessagesAvailable: false,
@ -1698,7 +1698,7 @@ public struct StarsSubscriptionConfiguration {
public let maxFee: Int64
public let usdWithdrawRate: Int64
public let tonUsdRate: Int64
public let tonUsdRate: Double
public let paidMessageMaxAmount: Int64
public let paidMessageCommissionPermille: Int32
public let paidMessagesAvailable: Bool
@ -1717,7 +1717,7 @@ public struct StarsSubscriptionConfiguration {
fileprivate init(
maxFee: Int64,
usdWithdrawRate: Int64,
tonUsdRate: Int64,
tonUsdRate: Double,
paidMessageMaxAmount: Int64,
paidMessageCommissionPermille: Int32,
paidMessagesAvailable: Bool,
@ -1756,7 +1756,7 @@ public struct StarsSubscriptionConfiguration {
if let data = appConfiguration.data {
let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee
let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate
let tonUsdRate = (data["ton_usd_rate"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate
let tonUsdRate = (data["ton_usd_rate"] as? Double) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate
let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount
let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille
let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable

View file

@ -3777,126 +3777,188 @@ public func avatarMediaPickerController(
performDelete: @escaping () -> Void,
completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
dismissed: @escaping () -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
chatLocation: nil,
buttons: [.standalone],
initialButton: .standalone,
fromMenu: false,
hasTextInput: false,
makeEntityInputView: {
return nil
})
controller.forceSourceRect = true
controller.getSourceRect = getSourceRect
controller.requestController = { [weak controller] _, present in
var mainButtonState: AttachmentMainButtonState?
if canDelete {
mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false)
) -> (controller: ViewController?, holder: Any?) {
if #available(iOS 14.0, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized {
final class PickerDelegate: NSObject, PHPickerViewControllerDelegate {
var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)?
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
for item in results {
if item.itemProvider.canLoadObject(ofClass: UIImage.self) {
item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in
if let uiImage = image as? UIImage {
Queue.mainQueue().async {
self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {})
}
}
}
}
}
}
}
let mediaPickerController = MediaPickerScreenImpl(
let holder = PickerDelegate()
holder.completion = completion
let openMediaPicker = {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = holder
(context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController?.present(picker, animated: true, completion: nil)
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
var items: [ActionSheetButtonItem] = [
ActionSheetButtonItem(title: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: {
dismissAction()
openMediaPicker()
}),
ActionSheetButtonItem(title: presentationData.strings.ProfilePhoto_SetEmoji, color: .accent, action: {
dismissAction()
completion(nil, nil, CGRect(), nil, false, { _ in return nil }, {})
})
]
if canDelete {
items.append(ActionSheetButtonItem(title: presentationData.strings.MediaPicker_RemovePhoto, color: .destructive, action: {
dismissAction()
performDelete()
}))
}
controller.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
return (controller, holder)
} else {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(
context: context,
updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil,
threadTitle: nil,
chatLocation: nil,
bannedSendPhotos: nil,
bannedSendVideos: nil,
subject: .assets(nil, .createAvatar),
mainButtonState: mainButtonState,
mainButtonAction: { [weak controller] in
controller?.dismiss(animated: true)
performDelete()
}
)
mediaPickerController.customSelection = { controller, result in
if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier)
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in
if let isNew {
if isNew {
controller.updateHiddenMediaId(nil)
if let transitionView = controller.defaultTransitionView() {
return (transitionView, transitionView.bounds)
}
} else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
return (transitionView, transitionView.bounds)
}
}
return nil
}
completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in
controller?.updateHiddenMediaId(nil)
})
}
}
}
mediaPickerController.openAvatarEditor = { [weak controller] in
completion(nil, nil, .zero, nil, false, { _ in return nil }, {
buttons: [.standalone],
initialButton: .standalone,
fromMenu: false,
hasTextInput: false,
makeEntityInputView: {
return nil
})
controller?.dismiss(animated: true)
}
mediaPickerController.openCamera = { [weak controller] cameraHolder in
let _ = controller
guard let cameraHolder = cameraHolder as? CameraHolder else {
return
controller.forceSourceRect = true
controller.getSourceRect = getSourceRect
controller.requestController = { [weak controller] _, present in
var mainButtonState: AttachmentMainButtonState?
if canDelete {
mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false)
}
var returnToCameraImpl: (() -> Void)?
let cameraScreen = context.sharedContext.makeCameraScreen(
let mediaPickerController = MediaPickerScreenImpl(
context: context,
mode: .avatar,
cameraHolder: cameraHolder,
transitionIn: CameraScreenTransitionIn(
sourceView: cameraHolder.parentView,
sourceRect: cameraHolder.parentView.bounds,
sourceCornerRadius: 0.0,
useFillAnimation: false
),
transitionOut: { _ in
return CameraScreenTransitionOut(
destinationView: cameraHolder.parentView,
destinationRect: cameraHolder.parentView.bounds,
destinationCornerRadius: 0.0
)
},
completion: { result, commit in
completion(result, nil, .zero, nil, true, { _ in return nil }, {
returnToCameraImpl?()
})
},
transitionedOut: { [weak cameraHolder] in
if let cameraHolder {
cameraHolder.restore()
}
updatedPresentationData: updatedPresentationData,
style: .glass,
peer: nil,
threadTitle: nil,
chatLocation: nil,
bannedSendPhotos: nil,
bannedSendVideos: nil,
subject: .assets(nil, .createAvatar),
mainButtonState: mainButtonState,
mainButtonAction: { [weak controller] in
controller?.dismiss(animated: true)
performDelete()
}
)
controller?.push(cameraScreen)
returnToCameraImpl = { [weak cameraScreen] in
if let cameraScreen = cameraScreen as? CameraScreen {
cameraScreen.returnFromEditor()
mediaPickerController.customSelection = { controller, result in
if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier)
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in
if let isNew {
if isNew {
controller.updateHiddenMediaId(nil)
if let transitionView = controller.defaultTransitionView() {
return (transitionView, transitionView.bounds)
}
} else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
return (transitionView, transitionView.bounds)
}
}
return nil
}
completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in
controller?.updateHiddenMediaId(nil)
})
}
}
}
mediaPickerController.openAvatarEditor = { [weak controller] in
completion(nil, nil, .zero, nil, false, { _ in return nil }, {
})
controller?.dismiss(animated: true)
}
mediaPickerController.openCamera = { [weak controller] cameraHolder in
let _ = controller
guard let cameraHolder = cameraHolder as? CameraHolder else {
return
}
var returnToCameraImpl: (() -> Void)?
let cameraScreen = context.sharedContext.makeCameraScreen(
context: context,
mode: .avatar,
cameraHolder: cameraHolder,
transitionIn: CameraScreenTransitionIn(
sourceView: cameraHolder.parentView,
sourceRect: cameraHolder.parentView.bounds,
sourceCornerRadius: 0.0,
useFillAnimation: false
),
transitionOut: { _ in
return CameraScreenTransitionOut(
destinationView: cameraHolder.parentView,
destinationRect: cameraHolder.parentView.bounds,
destinationCornerRadius: 0.0
)
},
completion: { result, commit in
completion(result, nil, .zero, nil, true, { _ in return nil }, {
returnToCameraImpl?()
})
},
transitionedOut: { [weak cameraHolder] in
if let cameraHolder {
cameraHolder.restore()
}
}
)
controller?.push(cameraScreen)
returnToCameraImpl = { [weak cameraScreen] in
if let cameraScreen = cameraScreen as? CameraScreen {
cameraScreen.returnFromEditor()
}
}
}
present(mediaPickerController, mediaPickerController.mediaPickerContext)
}
present(mediaPickerController, mediaPickerController.mediaPickerContext)
controller.willDismiss = {
dismissed()
}
controller.navigationPresentation = .flatModal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
return (controller, nil)
}
controller.willDismiss = {
dismissed()
}
controller.navigationPresentation = .flatModal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
return controller
}

View file

@ -1578,7 +1578,8 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
prepaidUpgradeHash: nil,
upgradeSeparate: false,
dropOriginalDetailsStars: dropOriginalDetailsStars,
number: nil
number: nil,
isRefunded: false
))
}
}
@ -2533,6 +2534,7 @@ public final class ProfileGiftsContext {
case upgradeSeparate
case dropOriginalDetailsStars
case number
case isRefunded
}
public let gift: TelegramCore.StarGift
@ -2556,7 +2558,8 @@ public final class ProfileGiftsContext {
public let upgradeSeparate: Bool
public let dropOriginalDetailsStars: Int64?
public let number: Int32?
public let isRefunded: Bool
fileprivate let _fromPeerId: EnginePeer.Id?
public enum DecodingError: Error {
@ -2584,7 +2587,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: String?,
upgradeSeparate: Bool,
dropOriginalDetailsStars: Int64?,
number: Int32?
number: Int32?,
isRefunded: Bool
) {
self.gift = gift
self.reference = reference
@ -2608,6 +2612,7 @@ public final class ProfileGiftsContext {
self.upgradeSeparate = upgradeSeparate
self.dropOriginalDetailsStars = dropOriginalDetailsStars
self.number = number
self.isRefunded = isRefunded
}
public init(from decoder: Decoder) throws {
@ -2641,6 +2646,7 @@ public final class ProfileGiftsContext {
self.upgradeSeparate = try container.decodeIfPresent(Bool.self, forKey: .upgradeSeparate) ?? false
self.dropOriginalDetailsStars = try container.decodeIfPresent(Int64.self, forKey: .dropOriginalDetailsStars)
self.number = try container.decodeIfPresent(Int32.self, forKey: .number)
self.isRefunded = try container.decodeIfPresent(Bool.self, forKey: .isRefunded) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -2667,6 +2673,8 @@ public final class ProfileGiftsContext {
try container.encode(self.upgradeSeparate, forKey: .upgradeSeparate)
try container.encodeIfPresent(self.dropOriginalDetailsStars, forKey: .dropOriginalDetailsStars)
try container.encodeIfPresent(self.number, forKey: .number)
try container.encode(self.isRefunded, forKey: .isRefunded)
}
public func withGift(_ gift: TelegramCore.StarGift) -> StarGift {
@ -2691,7 +2699,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
@ -2717,7 +2726,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
@ -2743,7 +2753,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift {
@ -2768,7 +2779,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
@ -2794,7 +2806,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
}
@ -3063,6 +3076,7 @@ extension ProfileGiftsContext.State.StarGift {
self.upgradeSeparate = (flags & (1 << 17)) != 0
self.dropOriginalDetailsStars = dropOriginalDetailsStars
self.number = number
self.isRefunded = (flags & (1 << 9)) != 0
}
}
}

View file

@ -1644,7 +1644,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid:
receiptMessageId = nil
}
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id {
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id {
let reference: StarGiftReference
if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)
@ -1672,7 +1672,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
prepaidUpgradeHash: nil,
upgradeSeparate: false,
dropOriginalDetailsStars: dropOriginalDetailsStars,
number: nil
number: nil,
isRefunded: isRefunded
)
}
}

View file

@ -1663,7 +1663,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case .stars:
priceString = strings.Notification_StarsGiftOffer_OfferYou_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGiftOffer_OfferYou(peerName, priceString, giftTitle)._tuple, body: bodyAttributes, argumentAttributes: attributes)
@ -1673,7 +1673,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case .stars:
priceString = strings.Notification_StarsGiftOffer_Offer_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGiftOffer_Offer(peerName, priceString, giftTitle)._tuple, body: bodyAttributes, argumentAttributes: attributes)
@ -1696,7 +1696,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case .stars:
priceString = strings.Notification_StarsGiftOffer_ExpiredYou_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
@ -1709,7 +1709,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case .stars:
priceString = strings.Notification_StarsGiftOffer_Expired_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
let timeString = "[TODO]"
@ -1730,7 +1730,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case .stars:
priceString = strings.Notification_StarsGiftOffer_Rejected_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)

View file

@ -131,7 +131,7 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
case .stars:
priceString = item.presentationData.strings.Notification_StarGiftOffer_Offer_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: item.presentationData.dateTimeFormat) + " TON"
}
let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""

View file

@ -485,7 +485,7 @@ public func giftOfferAlertController(
case .stars:
priceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: presentationData.dateTimeFormat) + " TON"
}
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
@ -507,6 +507,7 @@ public func giftOfferAlertController(
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in
contentNode?.inProgress = true
commit()
dismissImpl?(true)
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
})]

View file

@ -40,6 +40,7 @@ import ChatThemeScreen
import ProfileLevelRatingBarComponent
import AnimatedTextComponent
import InfoParagraphComponent
import ChatMessagePaymentAlertController
private final class GiftViewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -2337,6 +2338,7 @@ private final class GiftViewSheetContent: CombinedComponent {
guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, case let .peerId(ownerPeerId) = uniqueGift.owner, let controller = self.getController() else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: ownerPeerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
@ -2346,13 +2348,41 @@ private final class GiftViewSheetContent: CombinedComponent {
guard let self else {
return
}
self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration)
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: peer.id))
|> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars in
guard let self else {
return
}
let action: (Int64?) -> Void = { allowPaidStars in
self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration, allowPaidStars: allowPaidStars)
}
if let sendPaidMessageStars, sendPaidMessageStars.value > 0 {
let alertController = chatMessagePaymentAlertController(
context: nil,
presentationData: presentationData,
updatedPresentationData: nil,
peers: [EngineRenderedPeer(peer: peer)],
count: 1,
amount: sendPaidMessageStars,
totalAmount: nil,
hasCheck: false,
navigationController: controller?.navigationController as? NavigationController,
completion: { _ in
action(sendPaidMessageStars.value)
}
)
controller?.present(alertController, in: .window(.root))
} else {
action(nil)
}
})
}))
controller.push(buyController)
})
}
func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32) {
func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) {
guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
return
}
@ -2362,7 +2392,7 @@ private final class GiftViewSheetContent: CombinedComponent {
guard let self else {
return
}
self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: nil)
self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: allowPaidStars)
|> deliverOnMainQueue).start(error: { _ in
}, completed: { [weak self, weak starsContext] in
guard let self else {
@ -2383,11 +2413,15 @@ private final class GiftViewSheetContent: CombinedComponent {
guard let self, let controller = self.getController() else {
return
}
var finalStars = price.amount.value
if let allowPaidStars {
finalStars += allowPaidStars
}
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options ?? [],
purpose: .starGiftOffer(requiredStars: price.amount.value),
purpose: .starGiftOffer(requiredStars: finalStars),
targetPeerId: nil,
customTheme: nil,
completion: { [weak self, weak starsContext] stars in
@ -2411,7 +2445,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.inProgress = false
self.updated()
self.commitGiftBuyOffer(peer: peer, price: price, duration: duration)
self.commitGiftBuyOffer(peer: peer, price: price, duration: duration, allowPaidStars: allowPaidStars)
} else {
proceed()
}

View file

@ -13498,6 +13498,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
private var tabBarItemDisposable: Disposable?
var avatarPickerHolder: Any?
var controllerNode: PeerInfoScreenNode {
return self.displayNode as! PeerInfoScreenNode
}

View file

@ -103,9 +103,14 @@ extension PeerInfoScreenImpl {
let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController
var dismissImpl: (() -> Void)?
let mainController = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in
let (mainController, pickerHolder) = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in
self?.openAvatarRemoval(mode: mode, peer: peer, item: item)
}, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in
}, completion: { [weak self] result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in
guard let self else {
return
}
self.avatarPickerHolder = nil
var resultImage: UIImage?
let uploadStatusPromise = Promise<PeerInfoAvatarUploadStatus>(.progress(0.0))
@ -155,7 +160,6 @@ extension PeerInfoScreenImpl {
commit()
}
parentController?.push(controller)
//isFromEditor = true
return
}
@ -232,31 +236,37 @@ extension PeerInfoScreenImpl {
self.parentController?.pushViewController(editorController)
}
}, dismissed: {
})
dismissImpl = { [weak self, weak mainController] in
if let mainController, let navigationController = mainController.navigationController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in
return !(c is CameraScreen) && c !== mainController
self.avatarPickerHolder = pickerHolder
if let mainController {
dismissImpl = { [weak self, weak mainController] in
if let mainController, let navigationController = mainController.navigationController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in
return !(c is CameraScreen) && c !== mainController
}
navigationController.setViewControllers(viewControllers, animated: false)
}
navigationController.setViewControllers(viewControllers, animated: false)
}
if let self, let navigationController = self.parentController, let mainController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in
return !(c is CameraScreen) && c !== mainController
if let self, let navigationController = self.parentController, let mainController {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in
return !(c is CameraScreen) && c !== mainController
}
navigationController.setViewControllers(viewControllers, animated: false)
}
}
if mainController is ActionSheetController {
self.present(mainController, in: .window(.root))
} else {
mainController.navigationPresentation = .flatModal
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
if self.navigationController != nil {
self.push(mainController)
} else {
self.parentController?.pushViewController(mainController)
}
navigationController.setViewControllers(viewControllers, animated: false)
}
}
mainController.navigationPresentation = .flatModal
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
if self.navigationController != nil {
self.push(mainController)
} else {
self.parentController?.pushViewController(mainController)
}
})
}

View file

@ -3713,7 +3713,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed)
}
public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) {
return avatarMediaPickerController(context: context, getSourceRect: getSourceRect, canDelete: canDelete, performDelete: performDelete, completion: completion, dismissed: dismissed)
}