Various improvements

This commit is contained in:
Ilya Laktyushin 2025-03-11 18:48:35 +04:00
parent 80cd8f7b32
commit bafbe20063
38 changed files with 434 additions and 156 deletions

View File

@ -13996,3 +13996,13 @@ Sorry for the inconvenience.";
"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 >]()";
"Notification.PaidMessageRefund.Stars_1" = "%@ Star";
"Notification.PaidMessageRefund.Stars_any" = "%@ Stars";
"Notification.PaidMessageRefund" = "%1$@ refunded you %2$@";
"Notification.PaidMessageRefundYou" = "You refunded %1$@ to %2$@";
"Notification.PaidMessagePriceChanged.Stars_1" = "%@ Star";
"Notification.PaidMessagePriceChanged.Stars_any" = "%@ Stars";
"Notification.PaidMessagePriceChanged" = "%1$@ changed price to %2$@ per message";
"Notification.PaidMessagePriceChangedYou" = "You changed price to %1$@ per message";

View File

@ -1053,7 +1053,7 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, dismissed: (() -> Void)?) -> ViewController
func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, proceed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
@ -1232,6 +1232,7 @@ public protocol AccountContext: AnyObject {
var availableMessageEffects: Signal<AvailableMessageEffects?, NoError> { get }
var isPremium: Bool { get }
var isFrozen: Bool { get }
var userLimits: EngineConfiguration.UserLimits { get }
var peerNameColors: PeerNameColors { get }

View File

@ -43,6 +43,7 @@ public enum PremiumIntroSource {
case animatedEmoji
case messageEffects
case paidMessages
case auth(String)
}
public enum PremiumGiftSource: Equatable {

View File

@ -251,15 +251,17 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
iconName: "Premium/Authorization/Support",
iconColor: linkColor,
action: { [weak self] in
guard let self, let controller = self.environment?.controller() else {
guard let self, let controller = self.environment?.controller(), let product = self.products.first(where: { $0.id == component.storeProduct }) else {
return
}
let introController = component.sharedContext.makePremiumIntroController(
sharedContext: component.sharedContext,
engine: component.engine,
inAppPurchaseManager: component.inAppPurchaseManager,
source: .about,
dismissed: nil
source: .auth(product.price),
proceed: { [weak self] in
self?.proceed()
}
)
controller.push(introController)
}
@ -274,11 +276,13 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
let buttonHeight: CGFloat = 50.0
let bottomPanelPadding: CGFloat = 12.0
let titleSpacing: CGFloat = -24.0
let listSpacing: CGFloat = 12.0
let totalHeight = animationSize.height + titleSpacing + titleSize.height + listSpacing + listSize.height
var originY = floor((availableSize.height - totalHeight) / 2.0)
var originY = floor((availableSize.height - buttonHeight - bottomPanelPadding * 2.0 - totalHeight) / 2.0)
if let animationView = self.animation.view {
if animationView.superview == nil {
@ -302,8 +306,6 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize)
}
let buttonHeight: CGFloat = 50.0
let bottomPanelPadding: CGFloat = 12.0
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
@ -329,7 +331,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
component: AnyComponent(
VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.regular(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
], spacing: 1.0)
)
),
@ -410,6 +412,10 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
fatalError("init(coder:) has not been implemented")
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
@objc private func cancelPressed() {
self.dismiss()
}

View File

@ -2794,6 +2794,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private weak var storyCameraTooltip: TooltipScreen?
fileprivate func openStoryCamera(fromList: Bool) {
guard !self.context.isFrozen else {
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
return
}
var reachedCountLimit = false
var premiumNeeded = false
var hasActiveCall = false
@ -4641,6 +4647,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
@objc fileprivate func composePressed() {
guard !self.context.isFrozen else {
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
return
}
guard let navigationController = self.navigationController as? NavigationController else {
return
}

View File

@ -9,6 +9,7 @@ public final class SolidRoundedButtonComponent: Component {
public typealias Theme = SolidRoundedButtonTheme
public let title: String?
public let subtitle: String?
public let label: String?
public let badge: String?
public let icon: UIImage?
@ -28,6 +29,7 @@ public final class SolidRoundedButtonComponent: Component {
public init(
title: String? = nil,
subtitle: String? = nil,
label: String? = nil,
badge: String? = nil,
icon: UIImage? = nil,
@ -46,6 +48,7 @@ public final class SolidRoundedButtonComponent: Component {
action: @escaping () -> Void
) {
self.title = title
self.subtitle = subtitle
self.label = label
self.badge = badge
self.icon = icon
@ -68,6 +71,9 @@ public final class SolidRoundedButtonComponent: Component {
if lhs.title != rhs.title {
return false
}
if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.label != rhs.label {
return false
}
@ -147,6 +153,7 @@ public final class SolidRoundedButtonComponent: Component {
if let button = self.button {
button.title = component.title
button.subtitle = component.subtitle
button.label = component.label
button.badge = component.badge
button.iconPosition = component.iconPosition

View File

@ -242,6 +242,7 @@ open class ViewControllerComponentContainer: ViewController {
public private(set) var validLayout: ContainerViewLayout?
public var wasDismissed: (() -> Void)?
public var customProceed: (() -> Void)?
public init<C: Component>(
context: AccountContext,

View File

@ -67,9 +67,7 @@
- (SSignal *)coverImageSignalForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item;
- (NSNumber *)coverPositionForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
- (void)setTemporaryRep:(id)rep forItem:(id<TGMediaEditableItem>)item;

View File

@ -309,6 +309,12 @@ public enum PremiumSource: Equatable {
} else {
return false
}
case let .auth(lhsPrice):
if case let .auth(rhsPrice) = rhs, lhsPrice == rhsPrice {
return true
} else {
return false
}
}
}
@ -357,6 +363,7 @@ public enum PremiumSource: Equatable {
case folderTags
case messageEffects
case paidMessages
case auth(String)
var identifier: String? {
switch self {
@ -452,6 +459,8 @@ public enum PremiumSource: Equatable {
return "effects"
case .paidMessages:
return "paid_messages"
case .auth:
return "auth"
}
}
}
@ -1214,6 +1223,7 @@ final class PerkComponent: CombinedComponent {
let subtitleColor: UIColor
let arrowColor: UIColor
let accentColor: UIColor
let displayArrow: Bool
let badge: String?
init(
@ -1225,6 +1235,7 @@ final class PerkComponent: CombinedComponent {
subtitleColor: UIColor,
arrowColor: UIColor,
accentColor: UIColor,
displayArrow: Bool = true,
badge: String? = nil
) {
self.iconName = iconName
@ -1235,6 +1246,7 @@ final class PerkComponent: CombinedComponent {
self.subtitleColor = subtitleColor
self.arrowColor = arrowColor
self.accentColor = accentColor
self.displayArrow = displayArrow
self.badge = badge
}
@ -1263,6 +1275,9 @@ final class PerkComponent: CombinedComponent {
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.displayArrow != rhs.displayArrow {
return false
}
if lhs.badge != rhs.badge {
return false
}
@ -1306,15 +1321,6 @@ final class PerkComponent: CombinedComponent {
transition: context.transition
)
let arrow = arrow.update(
component: BundleIconComponent(
name: "Item List/DisclosureArrow",
tintColor: component.arrowColor
),
availableSize: context.availableSize,
transition: context.transition
)
let title = title.update(
component: MultilineTextComponent(
text: .plain(
@ -1391,9 +1397,20 @@ final class PerkComponent: CombinedComponent {
)
let size = CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + spacing + subtitle.size.height + textBottomInset)
if component.displayArrow {
let arrow = arrow.update(
component: BundleIconComponent(
name: "Item List/DisclosureArrow",
tintColor: component.arrowColor
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(arrow
.position(CGPoint(x: context.availableSize.width - 7.0 - arrow.size.width / 2.0, y: size.height / 2.0))
)
}
return size
}
@ -2086,6 +2103,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
foregroundColor: .white,
iconName: perk.iconName
))), false),
accessory: accountContext != nil ? .arrow : nil,
action: { [weak state] _ in
guard let accountContext else {
return
@ -2162,7 +2180,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
updateIsFocused(true)
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
}
},
highlighting: accountContext != nil ? .default : .disabled
))))
i += 1
}
@ -3660,7 +3679,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
if !buttonIsHidden {
let buttonTitle: String
if isUnusedGift {
var buttonSubtitle: String?
if case let .auth(price) = context.component.source {
buttonTitle = "Sign up for \(price)"
buttonSubtitle = "Get Telegram Premium for 1 week"
} else if isUnusedGift {
buttonTitle = environment.strings.Premium_Gift_ApplyLink
} else if state.isPremium == true && state.canUpgrade {
buttonTitle = state.isAnnual ? environment.strings.Premium_UpgradeForAnnual(state.price ?? "").string : environment.strings.Premium_UpgradeFor(state.price ?? "").string
@ -3668,10 +3691,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
buttonTitle = state.isAnnual ? environment.strings.Premium_SubscribeForAnnual(state.price ?? "").string : environment.strings.Premium_SubscribeFor(state.price ?? "").string
}
let controller = environment.controller
let sideInset: CGFloat = 16.0
let button = button.update(
component: SolidRoundedButtonComponent(
title: buttonTitle,
subtitle: buttonSubtitle,
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: UIColor(rgb: 0x8878ff),
backgroundColors: [
@ -3687,8 +3712,13 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
gloss: true,
isLoading: state.inProgress,
action: {
if let controller = controller() as? PremiumIntroScreen, let customProceed = controller.customProceed {
controller.dismiss()
customProceed()
} else {
state.buy()
}
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 50.0),
transition: context.transition)

View File

@ -315,7 +315,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati
entries.append(.footerItem(footer))
}
if !peers.isEmpty {
if !peers.isEmpty || state.enableForPremium || state.enableForBots {
entries.append(.deleteItem(presentationData.strings.Privacy_Exceptions_DeleteAllExceptions))
}

View File

@ -1412,7 +1412,7 @@ public final class SolidRoundedButtonView: UIView {
}
self.titleNode.attributedText = titleText
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.medium(11.0), textColor: theme.foregroundColor.withAlphaComponent(0.7))
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor)
@ -1472,7 +1472,7 @@ public final class SolidRoundedButtonView: UIView {
}
let titleSize = self.titleNode.updateLayout(buttonSize)
let spacingOffset: CGFloat = 9.0
let spacingOffset: CGFloat = 7.0
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
let iconSpacing: CGFloat = self.iconSpacing
let badgeSpacing: CGFloat = 6.0
@ -1533,11 +1533,11 @@ public final class SolidRoundedButtonView: UIView {
}
if self.subtitle != self.subtitleNode.attributedText?.string {
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.medium(11.0), textColor: self.theme.foregroundColor.withAlphaComponent(0.7))
}
let subtitleSize = self.subtitleNode.updateLayout(buttonSize)
let subtitleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - subtitleSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - titleSize.height) / 2.0) + spacingOffset + 2.0), size: subtitleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - subtitleSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - titleSize.height) / 2.0) + spacingOffset + 7.0), size: subtitleSize)
transition.updateFrame(view: self.subtitleNode, frame: subtitleFrame)
if previousSubtitle == nil && self.subtitle != nil {

View File

@ -577,6 +577,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) }
dict[1345295095] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) }
dict[-1126755303] = { return Api.MessageAction.parse_messageActionPaidMessagesPrice($0) }
dict[-1407246387] = { return Api.MessageAction.parse_messageActionPaidMessagesRefunded($0) }
dict[1102307842] = { return Api.MessageAction.parse_messageActionPaymentRefunded($0) }
dict[-970673810] = { return Api.MessageAction.parse_messageActionPaymentSent($0) }
dict[-6288180] = { return Api.MessageAction.parse_messageActionPaymentSentMe($0) }

View File

@ -363,6 +363,8 @@ public extension Api {
case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32)
case messageActionHistoryClear
case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int64])
case messageActionPaidMessagesPrice(stars: Int64)
case messageActionPaidMessagesRefunded(count: Int32, stars: Int64)
case messageActionPaymentRefunded(flags: Int32, peer: Api.Peer, currency: String, totalAmount: Int64, payload: Buffer?, charge: Api.PaymentCharge)
case messageActionPaymentSent(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?, subscriptionUntilDate: Int32?)
case messageActionPaymentSentMe(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge, subscriptionUntilDate: Int32?)
@ -595,6 +597,19 @@ public extension Api {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .messageActionPaidMessagesPrice(let stars):
if boxed {
buffer.appendInt32(-1126755303)
}
serializeInt64(stars, buffer: buffer, boxed: false)
break
case .messageActionPaidMessagesRefunded(let count, let stars):
if boxed {
buffer.appendInt32(-1407246387)
}
serializeInt32(count, buffer: buffer, boxed: false)
serializeInt64(stars, buffer: buffer, boxed: false)
break
case .messageActionPaymentRefunded(let flags, let peer, let currency, let totalAmount, let payload, let charge):
if boxed {
buffer.appendInt32(1102307842)
@ -847,6 +862,10 @@ public extension Api {
return ("messageActionHistoryClear", [])
case .messageActionInviteToGroupCall(let call, let users):
return ("messageActionInviteToGroupCall", [("call", call as Any), ("users", users as Any)])
case .messageActionPaidMessagesPrice(let stars):
return ("messageActionPaidMessagesPrice", [("stars", stars as Any)])
case .messageActionPaidMessagesRefunded(let count, let stars):
return ("messageActionPaidMessagesRefunded", [("count", count as Any), ("stars", stars as Any)])
case .messageActionPaymentRefunded(let flags, let peer, let currency, let totalAmount, let payload, let charge):
return ("messageActionPaymentRefunded", [("flags", flags as Any), ("peer", peer as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("payload", payload as Any), ("charge", charge as Any)])
case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug, let subscriptionUntilDate):
@ -1277,6 +1296,31 @@ public extension Api {
return nil
}
}
public static func parse_messageActionPaidMessagesPrice(_ reader: BufferReader) -> MessageAction? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.MessageAction.messageActionPaidMessagesPrice(stars: _1!)
}
else {
return nil
}
}
public static func parse_messageActionPaidMessagesRefunded(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageAction.messageActionPaidMessagesRefunded(count: _1!, stars: _2!)
}
else {
return nil
}
}
public static func parse_messageActionPaymentRefunded(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()

View File

@ -227,7 +227,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
}
switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique:
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice:
break
case let .messageActionChannelMigrateFrom(_, chatId):
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))

View File

@ -191,6 +191,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return nil
}
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId))
case let .messageActionPaidMessagesRefunded(count, stars):
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
case let .messageActionPaidMessagesPrice(stars):
return TelegramMediaAction(action: .paidMessagesPriceEdited(stars: stars))
}
}

View File

@ -132,6 +132,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
case paidMessagesRefunded(count: Int32, stars: Int64)
case paidMessagesPriceEdited(stars: Int64)
public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -256,6 +258,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
case 45:
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
case 46:
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
case 47:
self = .paidMessagesPriceEdited(stars: decoder.decodeInt64ForKey("stars", orElse: 0))
default:
self = .unknown
}
@ -626,6 +632,13 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "savedId")
}
case let .paidMessagesRefunded(count, stars):
encoder.encodeInt32(46, forKey: "_rawValue")
encoder.encodeInt32(count, forKey: "count")
encoder.encodeInt64(stars, forKey: "stars")
case let .paidMessagesPriceEdited(stars):
encoder.encodeInt32(47, forKey: "_rawValue")
encoder.encodeInt64(stars, forKey: "stars")
}
}

View File

@ -109,7 +109,14 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
return .single([])
}
if let channel = peer as? TelegramChannel, case .group = channel.info {
if let channel = peer as? TelegramChannel {
if case .group = channel.info {
} else if channel.adminRights != nil || channel.flags.contains(.isCreator) {
} else {
return .single([])
}
} else {
return .single([])
}

View File

@ -170,6 +170,12 @@ public final class PrincipalThemeEssentialGraphics {
public let outgoingDateAndStatusRepliesIcon: UIImage
public let mediaRepliesIcon: UIImage
public let freeRepliesIcon: UIImage
public let incomingDateAndStatusStarsIcon: UIImage
public let outgoingDateAndStatusStarsIcon: UIImage
public let mediaStarsIcon: UIImage
public let freeStarsIcon: UIImage
public let incomingDateAndStatusPinnedIcon: UIImage
public let outgoingDateAndStatusPinnedIcon: UIImage
public let mediaPinnedIcon: UIImage
@ -358,6 +364,12 @@ public final class PrincipalThemeEssentialGraphics {
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
@ -479,6 +491,12 @@ public final class PrincipalThemeEssentialGraphics {
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!

View File

@ -1196,6 +1196,33 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
}
}
case let .paidMessagesRefunded(_, stars):
let starsString = strings.Notification_PaidMessageRefund_Stars(Int32(stars))
if message.author?.id == accountPeerId, let messagePeer = message.peers[message.id.peerId] {
let peerName = EnginePeer(messagePeer).compactDisplayTitle
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(1, messagePeer.id)])
attributes[0] = boldAttributes
let resultString = strings.Notification_PaidMessageRefundYou(starsString, peerName)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
let resultString = strings.Notification_PaidMessageRefund(peerName, starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
case let .paidMessagesPriceEdited(stars):
let starsString = strings.Notification_PaidMessagePriceChanged_Stars(Int32(stars))
if message.author?.id == accountPeerId {
let resultString = strings.Notification_PaidMessagePriceChangedYou(starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
let resultString = strings.Notification_PaidMessagePriceChanged(peerName, starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
case .unknown:
attributedString = nil
}

View File

@ -413,7 +413,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.incomingDateAndStatusPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.incomingDateAndStatusRepliesIcon
starsImage = graphics.incomingDateAndStatusStarsIcon
}
case let .BubbleOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
@ -432,7 +432,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.outgoingDateAndStatusRepliesIcon
starsImage = graphics.outgoingDateAndStatusStarsIcon
}
case .ImageIncoming:
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
@ -451,7 +451,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.mediaPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaRepliesIcon
starsImage = graphics.mediaStarsIcon
}
case let .ImageOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
@ -471,7 +471,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.mediaPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaRepliesIcon
starsImage = graphics.mediaStarsIcon
}
case .FreeIncoming:
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
@ -492,7 +492,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.freePinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeRepliesIcon
starsImage = graphics.freeStarsIcon
}
case let .FreeOutgoing(status):
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
@ -513,7 +513,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.freePinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeRepliesIcon
starsImage = graphics.freeStarsIcon
}
}

View File

@ -516,7 +516,7 @@ public final class PeerInfoCoverComponent: Component {
let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance
let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0)))
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: itemDistanceFraction, reverse: false)
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.56), t: itemDistanceFraction, reverse: false)
let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction
var itemAngle: CGFloat

View File

@ -19,6 +19,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
public let giftsContext: ProfileGiftsContext
public let hasBackground: Bool
public let avatarCenter: CGPoint
public let avatarSize: CGSize
public let defaultHeight: CGFloat
public let avatarTransitionFraction: CGFloat
public let statusBarHeight: CGFloat
@ -34,6 +35,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
giftsContext: ProfileGiftsContext,
hasBackground: Bool,
avatarCenter: CGPoint,
avatarSize: CGSize,
defaultHeight: CGFloat,
avatarTransitionFraction: CGFloat,
statusBarHeight: CGFloat,
@ -48,6 +50,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
self.giftsContext = giftsContext
self.hasBackground = hasBackground
self.avatarCenter = avatarCenter
self.avatarSize = avatarSize
self.defaultHeight = defaultHeight
self.avatarTransitionFraction = avatarTransitionFraction
self.statusBarHeight = statusBarHeight
@ -71,6 +74,9 @@ public final class PeerInfoGiftsCoverComponent: Component {
if lhs.avatarCenter != rhs.avatarCenter {
return false
}
if lhs.avatarSize != rhs.avatarSize {
return false
}
if lhs.defaultHeight != rhs.defaultHeight {
return false
}
@ -298,26 +304,25 @@ public final class PeerInfoGiftsCoverComponent: Component {
}
iconLayer.glowing = component.hasBackground
let centerPosition = component.avatarCenter
let finalPosition = iconPosition.center.offsetBy(dx: component.avatarCenter.x, dy: component.avatarCenter.y)
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: 0.0, reverse: false)
let itemDistanceFraction = max(0.0, min(1.0, iconPosition.distance / 100.0))
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.56), t: itemDistanceFraction, reverse: false)
func interpolateRect(from: CGPoint, to: CGPoint, t: CGFloat) -> CGPoint {
func interpolatePosition(from: PositionGenerator.Position, to: PositionGenerator.Position, t: CGFloat) -> PositionGenerator.Position {
let clampedT = max(0, min(1, t))
let interpolatedX = from.x + (to.x - from.x) * clampedT
let interpolatedY = from.y + (to.y - from.y) * clampedT
let interpolatedDistance = from.distance + (to.distance - from.distance) * clampedT
let interpolatedAngle = from.angle + (to.angle - from.angle) * clampedT
return CGPoint(
x: interpolatedX,
y: interpolatedY
)
return PositionGenerator.Position(distance: interpolatedDistance, angle: interpolatedAngle, scale: from.scale)
}
let effectivePosition = interpolateRect(from: finalPosition, to: centerPosition, t: itemScaleFraction)
let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + .pi * 0.18, scale: iconPosition.scale)
let effectivePosition = interpolatePosition(from: iconPosition, to: centerPosition, t: itemScaleFraction)
let position = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter)
iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize))
iconTransition.setPosition(layer: iconLayer, position: effectivePosition)
iconTransition.setPosition(layer: iconLayer, position: position)
iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction))
iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction)
@ -638,8 +643,16 @@ private class GiftIconLayer: SimpleLayer {
private struct PositionGenerator {
struct Position {
let center: CGPoint
let distance: CGFloat
let angle: CGFloat
let scale: CGFloat
var relativeCartesian: CGPoint {
return CGPoint(
x: self.distance * cos(self.angle),
y: self.distance * sin(self.angle)
)
}
}
let containerSize: CGSize
@ -700,15 +713,14 @@ private struct PositionGenerator {
let orbitRangeSize = self.innerOrbitRange.max - self.innerOrbitRange.min
let orbitDistanceFactor = self.innerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
let orbitDistance = orbitDistanceFactor * centerRadius
let distance = orbitDistanceFactor * centerRadius
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
let absoluteX = centerPoint.x + orbitDistance * cos(angle)
let absoluteY = centerPoint.y + orbitDistance * sin(angle)
let absolutePosition = CGPoint(x: absoluteX, y: absoluteY)
// Get the absolute position to check boundaries and collisions
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
@ -717,11 +729,6 @@ private struct PositionGenerator {
continue
}
let relativePosition = CGPoint(
x: absolutePosition.x - centerPoint.x,
y: absolutePosition.y - centerPoint.y
)
let itemRect = CGRect(
x: absolutePosition.x - itemSize.width/2,
y: absolutePosition.y - itemSize.height/2,
@ -729,10 +736,12 @@ private struct PositionGenerator {
height: itemSize.height
)
if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) {
if self.isValidPosition(itemRect, existingPositions: positions.map {
getAbsolutePosition(distance: $0.distance, angle: $0.angle, centerPoint: centerPoint)
}, itemSize: itemSize) {
let scaleRangeSize = max(self.scaleRange.min + 0.1, 0.75) - self.scaleRange.max
let scale = self.scaleRange.max + scaleRangeSize * CGFloat(self.lokiRng.next())
positions.append(Position(center: relativePosition, scale: scale))
positions.append(Position(distance: distance, angle: angle, scale: scale))
if absolutePosition.x < centerPoint.x {
leftPositions += 1
@ -751,15 +760,14 @@ private struct PositionGenerator {
let orbitRangeSize = self.outerOrbitRange.max - self.outerOrbitRange.min
let orbitDistanceFactor = self.outerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
let orbitDistance = orbitDistanceFactor * centerRadius
let distance = orbitDistanceFactor * centerRadius
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
let absoluteX = centerPoint.x + orbitDistance * cos(angle)
let absoluteY = centerPoint.y + orbitDistance * sin(angle)
let absolutePosition = CGPoint(x: absoluteX, y: absoluteY)
// Get the absolute position to check boundaries and collisions
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
@ -768,11 +776,6 @@ private struct PositionGenerator {
continue
}
let relativePosition = CGPoint(
x: absolutePosition.x - centerPoint.x,
y: absolutePosition.y - centerPoint.y
)
let itemRect = CGRect(
x: absolutePosition.x - itemSize.width/2,
y: absolutePosition.y - itemSize.height/2,
@ -780,12 +783,12 @@ private struct PositionGenerator {
height: itemSize.height
)
if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) {
let distance = hypot(absolutePosition.x - centerPoint.x, absolutePosition.y - centerPoint.y)
if self.isValidPosition(itemRect, existingPositions: positions.map {
getAbsolutePosition(distance: $0.distance, angle: $0.angle, centerPoint: centerPoint)
}, itemSize: itemSize) {
let normalizedDistance = min(distance / maxPossibleDistance, 1.0)
let scale = self.scaleRange.max - normalizedDistance * (self.scaleRange.max - self.scaleRange.min)
positions.append(Position(center: relativePosition, scale: scale))
positions.append(Position(distance: distance, angle: angle, scale: scale))
if absolutePosition.x < centerPoint.x {
leftPositions += 1
@ -798,8 +801,11 @@ private struct PositionGenerator {
return positions
}
private func posToAbsolute(_ relativePos: CGPoint, centerPoint: CGPoint) -> CGPoint {
return CGPoint(x: relativePos.x + centerPoint.x, y: relativePos.y + centerPoint.y)
func getAbsolutePosition(distance: CGFloat, angle: CGFloat, centerPoint: CGPoint) -> CGPoint {
return CGPoint(
x: centerPoint.x + distance * cos(angle),
y: centerPoint.y + distance * sin(angle)
)
}
private func isValidPosition(_ rect: CGRect, existingPositions: [CGPoint], itemSize: CGSize) -> Bool {
@ -827,6 +833,20 @@ private struct PositionGenerator {
}
}
private func getAbsolutePosition(position: PositionGenerator.Position, centerPoint: CGPoint) -> CGPoint {
return CGPoint(
x: centerPoint.x + position.distance * cos(position.angle),
y: centerPoint.y + position.distance * sin(position.angle)
)
}
private func getAbsolutePosition(distance: CGFloat, angle: CGFloat, centerPoint: CGPoint) -> CGPoint {
return CGPoint(
x: centerPoint.x + distance * cos(angle),
y: centerPoint.y + distance * sin(angle)
)
}
private func windowFunction(t: CGFloat) -> CGFloat {
return bezierPoint(0.6, 0.0, 0.4, 1.0, t)
}

View File

@ -2352,6 +2352,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
giftsContext: profileGiftsContext,
hasBackground: hasBackground,
avatarCenter: apparentAvatarFrame.center,
avatarSize: apparentAvatarFrame.size,
defaultHeight: backgroundDefaultHeight,
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
statusBarHeight: statusBarHeight,

View File

@ -165,9 +165,9 @@ private final class SheetContent: CombinedComponent {
iconName: "Account Freeze/Appeal",
iconColor: linkColor,
action: {
component.submitAppeal()
Queue.mainQueue().after(1.0) {
component.dismiss()
Queue.mainQueue().after(0.5) {
component.submitAppeal()
}
}
))
@ -201,9 +201,9 @@ private final class SheetContent: CombinedComponent {
isEnabled: true,
displaysProgress: false,
action: {
component.submitAppeal()
Queue.mainQueue().after(1.0) {
component.dismiss()
Queue.mainQueue().after(0.5) {
component.submitAppeal()
}
}
),

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "hand_30.pdf",
"filename" : "sandtimer_30.pdf",
"idiom" : "universal"
}
],

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "sandtimer_30.pdf",
"filename" : "hand_30.pdf",
"idiom" : "universal"
}
],

View File

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

View File

@ -266,6 +266,9 @@ public final class AccountContextImpl: AccountContext {
public private(set) var isPremium: Bool
private var isFrozenDisposable: Disposable?
public private(set) var isFrozen: Bool
public let imageCache: AnyObject?
public init(sharedContext: SharedAccountContextImpl, account: Account, limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, availableReplyColors: EngineAvailableColorOptions, availableProfileColors: EngineAvailableColorOptions, temp: Bool = false)
@ -280,6 +283,7 @@ public final class AccountContextImpl: AccountContext {
self.peerNameColors = PeerNameColors.with(availableReplyColors: availableReplyColors, availableProfileColors: availableProfileColors)
self.audioTranscriptionTrial = AudioTranscription.TrialState.defaultValue
self.isPremium = false
self.isFrozen = false
self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager)
@ -452,6 +456,18 @@ public final class AccountContextImpl: AccountContext {
}
self.audioTranscriptionTrial = audioTranscriptionTrial
})
self.isFrozenDisposable = (self.appConfiguration
|> map { appConfiguration in
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration).freezeUntilDate != nil
}
|> distinctUntilChanged
|> deliverOnMainQueue).startStrict(next: { [weak self] isFrozen in
guard let self = self else {
return
}
self.isFrozen = isFrozen
})
}
deinit {
@ -464,6 +480,7 @@ public final class AccountContextImpl: AccountContext {
self.animatedEmojiStickersDisposable?.dispose()
self.userLimitsConfigurationDisposable?.dispose()
self.peerNameColorsConfigurationDisposable?.dispose()
self.isFrozenDisposable?.dispose()
}
public func storeSecureIdPassword(password: String) {

View File

@ -0,0 +1,32 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AsyncDisplayKit
import Display
import ContextUI
import UndoUI
import AccountContext
import ChatControllerInteraction
import AnimatedTextComponent
import ChatMessagePaymentAlertController
import TelegramPresentationData
import TelegramNotices
extension ChatControllerImpl {
func presentAccountFrozenInfoIfNeeded() -> Bool {
if self.context.isFrozen {
let accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
if let freezeAppealUrl = accountFreezeConfiguration.freezeAppealUrl {
let components = freezeAppealUrl.components(separatedBy: "/")
if let username = components.last, let peer = self.presentationInterfaceState.renderedPeer?.peer, peer.addressName == username {
return false
}
}
self.push(self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context))
return true
}
return false
}
}

View File

@ -1865,6 +1865,12 @@ extension ChatControllerImpl {
guard let strongSelf = self, strongSelf.isNodeLoaded else {
return
}
guard !strongSelf.presentAccountFrozenInfoIfNeeded() else {
completion(.immediate, {})
return
}
if let messageId = messageId {
let intrinsicCanSendMessagesHere = canSendMessagesToChat(strongSelf.presentationInterfaceState)
var canSendMessagesHere = intrinsicCanSendMessagesHere
@ -2114,6 +2120,11 @@ extension ChatControllerImpl {
})
}, deleteMessages: { [weak self] messages, contextController, completion in
if let strongSelf = self, !messages.isEmpty {
guard !strongSelf.presentAccountFrozenInfoIfNeeded() else {
completion(.default)
return
}
let messageIds = Set(messages.map { $0.id })
strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, keepUpdated: false)
|> deliverOnMainQueue).startStrict(next: { actions in
@ -4416,10 +4427,7 @@ extension ChatControllerImpl {
return
}
let accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
if let _ = accountFreezeConfiguration.freezeUntilDate {
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
guard !self.presentAccountFrozenInfoIfNeeded() else {
return
}

View File

@ -333,6 +333,9 @@ extension ChatControllerImpl {
}
controller?.dismissWithoutContent()
guard !self.presentAccountFrozenInfoIfNeeded() else {
return
}
self.presentTagPremiumPaywall()
}
@ -341,6 +344,11 @@ extension ChatControllerImpl {
return
}
guard !self.presentAccountFrozenInfoIfNeeded() else {
controller?.dismiss(completion: {})
return
}
guard let message = messages.first else {
return
}

View File

@ -3399,8 +3399,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}, scheduleCurrentMessage: { [weak self] params in
if let strongSelf = self {
strongSelf.presentScheduleTimePicker(completion: { [weak self] time in
guard let self else {
return
}
guard !self.presentAccountFrozenInfoIfNeeded() else {
return
}
self.presentScheduleTimePicker(completion: { [weak self] time in
if let strongSelf = self {
if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState {
strongSelf.sendMediaRecording(scheduleTime: time, messageEffect: (params?.effect).flatMap {
@ -3424,17 +3429,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
})
}
}, sendScheduledMessagesNow: { [weak self] messageIds in
if let strongSelf = self {
if let _ = strongSelf.presentationInterfaceState.slowmodeState {
if let rect = strongSelf.chatDisplayNode.frameForInputActionButton() {
strongSelf.interfaceInteraction?.displaySlowmodeTooltip(strongSelf.chatDisplayNode.view, rect)
guard let self else {
return
}
guard !self.presentAccountFrozenInfoIfNeeded() else {
return
}
if let _ = self.presentationInterfaceState.slowmodeState {
if let rect = self.chatDisplayNode.frameForInputActionButton() {
self.interfaceInteraction?.displaySlowmodeTooltip(self.chatDisplayNode.view, rect)
}
return
} else {
let _ = strongSelf.context.engine.messages.sendScheduledMessageNowInteractively(messageId: messageIds.first!).startStandalone()
}
let _ = self.context.engine.messages.sendScheduledMessageNowInteractively(messageId: messageIds.first!).startStandalone()
}
}, editScheduledMessagesTime: { [weak self] messageIds in
if let strongSelf = self, let messageId = messageIds.first {

View File

@ -21,8 +21,16 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
return (nil, nil)
}
if context.isFrozen {
var isActuallyFrozen = true
let accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if let _ = accountFreezeConfiguration.freezeUntilDate {
if let freezeAppealUrl = accountFreezeConfiguration.freezeAppealUrl {
let components = freezeAppealUrl.components(separatedBy: "/")
if let username = components.last, let peer = chatPresentationInterfaceState.renderedPeer?.peer, peer.addressName == username {
isActuallyFrozen = false
}
}
if isActuallyFrozen {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {
@ -32,6 +40,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
return (panel, nil)
}
}
}
if let _ = chatPresentationInterfaceState.search {
var selectionPanel: ChatMessageSelectionInputPanelNode?

View File

@ -644,7 +644,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return self.actionButtons.micButton
}
private let startingBotDisposable = MetaDisposable()
private let statusDisposable = MetaDisposable()
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
didSet {
@ -655,25 +654,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
self?.updateIsProcessingInlineRequest(value)
}).strict())
}
if let startingBot = self.interfaceInteraction?.statuses?.startingBot {
self.startingBotDisposable.set((startingBot |> deliverOnMainQueue).startStrict(next: { [weak self] value in
if let strongSelf = self {
strongSelf.startingBotProgress = value
}
}).strict())
}
}
}
private var startingBotProgress = false {
didSet {
// if self.startingBotProgress != oldValue {
// if self.startingBotProgress {
// self.startButton.transitionToProgress()
// } else {
// self.startButton.transitionFromProgress()
// }
// }
}
}
@ -1130,7 +1110,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
deinit {
self.statusDisposable.dispose()
self.startingBotDisposable.dispose()
self.tooltipController?.dismiss()
self.currentEmojiSuggestion?.disposable.dispose()
}

View File

@ -2451,6 +2451,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .animatedEmoji
case .paidMessages:
mappedSource = .paidMessages
case let .auth(price):
mappedSource = .auth(price)
}
return mappedSource
}
@ -2465,13 +2467,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return controller
}
public func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, dismissed: (() -> Void)?) -> ViewController {
public func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, proceed: (() -> Void)?) -> ViewController {
var modal = true
if case .settings = source {
modal = false
}
let controller = PremiumIntroScreen(screenContext: .sharedContext(sharedContext, engine, inAppPurchaseManager), source: self.mapIntroSource(source: source), modal: modal)
controller.wasDismissed = dismissed
controller.customProceed = proceed
return controller
}