mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit 'c026f5af0b'
This commit is contained in:
commit
e5908c0bc8
72 changed files with 2763 additions and 679 deletions
|
|
@ -13875,6 +13875,26 @@ Sorry for the inconvenience.";
|
|||
|
||||
"Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()";
|
||||
|
||||
"Chat.NonContactUser.Subtitle" = "Not a contact";
|
||||
"Chat.NonContactUser.PhoneNumber" = "Phone Number";
|
||||
"Chat.NonContactUser.Registration" = "Registration";
|
||||
"Chat.NonContactUser.Groups" = "Shared Groups";
|
||||
"Chat.NonContactUser.GroupsCount_1" = "%@ group";
|
||||
"Chat.NonContactUser.GroupsCount_any" = "%@ groups";
|
||||
"Chat.NonContactUser.GroupsNone" = "None";
|
||||
"Chat.NonContactUser.Disclaimer" = "Not an official account";
|
||||
"Chat.NonContactUser.AnonymousNumber" = "Anonymous";
|
||||
|
||||
"Chat.NonContactUser.UpdatedName" = "User updated name %1$@";
|
||||
"Chat.NonContactUser.UpdatedPhoto" = "User updated photo %1$@";
|
||||
|
||||
"Chat.NonContactUser.UpdatedMinutes_1" = "%@ minute ago";
|
||||
"Chat.NonContactUser.UpdatedMinutes_any" = "%@ minutes ago";
|
||||
"Chat.NonContactUser.UpdatedHours_1" = "%@ hour ago";
|
||||
"Chat.NonContactUser.UpdatedHours_any" = "%@ hours ago";
|
||||
"Chat.NonContactUser.UpdatedDays_1" = "%@ day ago";
|
||||
"Chat.NonContactUser.UpdatedDays_any" = "%@ days ago";
|
||||
|
||||
"Chat.InputTextPaidMessagePlaceholder" = "Message for %@";
|
||||
|
||||
"Privacy.Messages.Stars_1" = "%@ Star";
|
||||
|
|
@ -13884,3 +13904,72 @@ Sorry for the inconvenience.";
|
|||
"Premium.PaidMessages" = "Paid Messages";
|
||||
"Premium.PaidMessagesInfo" = "Charge a fee for messages from non-contacts or new senders.";
|
||||
"Premium.PaidMessages.Proceed" = "About Telegram Premium";
|
||||
|
||||
"PeerInfo.Gifts.Reorder" = "Reorder";
|
||||
"PeerInfo.Gifts.Context.Pin" = "Pin";
|
||||
"PeerInfo.Gifts.Context.Unpin" = "Unpin";
|
||||
"PeerInfo.Gifts.Context.Reorder" = "Reorder";
|
||||
"PeerInfo.Gifts.Context.Wear" = "Wear";
|
||||
"PeerInfo.Gifts.Context.CopyLink" = "Copy Link";
|
||||
"PeerInfo.Gifts.Context.Share" = "Share";
|
||||
"PeerInfo.Gifts.Context.Hide" = "Hide";
|
||||
"PeerInfo.Gifts.Context.Show" = "Show";
|
||||
"PeerInfo.Gifts.Context.Transfer" = "Transfer";
|
||||
|
||||
"Gift.Send.Premium.Confirmation.Title" = "Send a Gift";
|
||||
"Gift.Send.Premium.Confirmation.Text" = "Are you sure you want to gift **Telegram Premium** to **%1$@** for **%2$@**?";
|
||||
"Gift.Send.Premium.Confirmation.Text.Stars_1" = "%@ Star";
|
||||
"Gift.Send.Premium.Confirmation.Text.Stars_any" = "%@ Stars";
|
||||
"Gift.Send.Premium.Confirmation.Confirm" = "Confirm";
|
||||
|
||||
"StarsBalance.YourBalance" = "Your balance is %@";
|
||||
"StarsBalance.GetMoreStars" = "Get More Stars >";
|
||||
|
||||
"Stars.Intro.BuyShort" = "Top Up";
|
||||
"Stars.Intro.Withdraw" = "Withdraw";
|
||||
|
||||
"Group.Info.Settings" = "Group Settings";
|
||||
|
||||
"Monetization.Balance.StarsInfoGroup" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()";
|
||||
|
||||
"PeerInfo.Gifts.ToastPinLimit.Text_1" = "You can't pin more than %@ gift.";
|
||||
"PeerInfo.Gifts.ToastPinLimit.Text_any" = "You can't pin more than %@ gifts.";
|
||||
"PeerInfo.Gifts.ToastUnpinned.Text" = "Gift Unpinned.";
|
||||
"PeerInfo.Gifts.ToastPinned.Title" = "Gift Pinned";
|
||||
"PeerInfo.Gifts.ToastPinned.Text" = "Now it will always be shown on the top.";
|
||||
|
||||
"Chat.PaidMessage.Confirm.Title" = "Confirm Payment";
|
||||
"Chat.PaidMessage.Confirm.DontAskAgain" = "Don't ask again";
|
||||
"Chat.PaidMessage.Confirm.PayForMessage_1" = "Pay for %@ Message";
|
||||
"Chat.PaidMessage.Confirm.PayForMessage_any" = "Pay for %@ Messages";
|
||||
"Chat.PaidMessage.Confirm.Single.Text" = "**%1$@** charges **%2$@** per incoming message. Would you like to pay **%3$@** to send %4$@?";
|
||||
"Chat.PaidMessage.Confirm.Multiple.Text" = "You selected %1$@ who charge Stars for messages. Would you like to pay **%2$@** Stars to send %3$@?";
|
||||
"Chat.PaidMessage.Confirm.Text.Users_1" = "**%@** user";
|
||||
"Chat.PaidMessage.Confirm.Text.Users_any" = "**%@** users";
|
||||
"Chat.PaidMessage.Confirm.Text.Messages_1" = "**%@** message";
|
||||
"Chat.PaidMessage.Confirm.Text.Messages_any" = "**%@** messages";
|
||||
"Chat.PaidMessage.Confirm.Text.Stars_1" = "%@ Star";
|
||||
"Chat.PaidMessage.Confirm.Text.Stars_any" = "%@ Stars";
|
||||
|
||||
"Chat.PaidMessage.RemoveFee.Title" = "Remove Fee";
|
||||
"Chat.PaidMessage.RemoveFee.Text" = "Are you sure you want to allow **%@** to message you for free?";
|
||||
"Chat.PaidMessage.RemoveFee.Yes" = "Yes";
|
||||
"Chat.PaidMessage.RemoveFee.Refund" = "Refund already paid **%@**";
|
||||
"Chat.PaidMessage.RemoveFee.Refund.Stars_1" = "%@ Star";
|
||||
"Chat.PaidMessage.RemoveFee.Refund.Stars_any" = "%@ Stars";
|
||||
|
||||
"Chat.PaidMessage.Sent.Title_1" = "Message Sent";
|
||||
"Chat.PaidMessage.Sent.Title_any" = "%@ Messages Sent";
|
||||
"Chat.PaidMessage.Sent.Text" = "You paid %@";
|
||||
"Chat.PaidMessage.Sent.Text.Stars_1" = "%@ Star";
|
||||
"Chat.PaidMessage.Sent.Text.Stars_any" = "%@ Stars";
|
||||
|
||||
"Chat.PaidMessageFee.Text" = "%1$@ must pay %2$@ for each message to you.";
|
||||
"Chat.PaidMessageFee.RemoveFee" = "Remove Fee";
|
||||
|
||||
"Privacy.Review.Review" = "Review";
|
||||
"Privacy.Review.Calls.Title" = "Call Settings";
|
||||
"Privacy.Review.Calls.Text" = "You've restricted who can message you, but anyone can still call you. Would you like to review these settings?";
|
||||
|
||||
"Privacy.Review.Invite.Title" = "Invitation Settings";
|
||||
"Privacy.Review.Invite.Text" = "You've restricted who can message you, but anyone can still invite you to groups and channels. Would you like to review these settings?";
|
||||
|
|
|
|||
|
|
@ -593,6 +593,7 @@ public enum PeerInfoControllerMode {
|
|||
case myProfile
|
||||
case gifts
|
||||
case myProfileGifts
|
||||
case groupsInCommon
|
||||
}
|
||||
|
||||
public enum ContactListActionItemInlineIconPosition {
|
||||
|
|
@ -1100,6 +1101,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController
|
||||
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
|
||||
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
||||
func makeStarsIntroScreen(context: AccountContext) -> ViewController
|
||||
|
|
|
|||
|
|
@ -314,10 +314,12 @@ public struct ChatControllerInitialBotAppStart {
|
|||
public enum ChatControllerInteractionNavigateToPeer {
|
||||
public struct InfoParams {
|
||||
public let switchToRecommendedChannels: Bool
|
||||
public let switchToGroupsInCommon: Bool
|
||||
public let ignoreInSavedMessages: Bool
|
||||
|
||||
public init(switchToRecommendedChannels: Bool = false, ignoreInSavedMessages: Bool = false) {
|
||||
public init(switchToRecommendedChannels: Bool = false, switchToGroupsInCommon: Bool = false, ignoreInSavedMessages: Bool = false) {
|
||||
self.switchToRecommendedChannels = switchToRecommendedChannels
|
||||
self.switchToGroupsInCommon = switchToGroupsInCommon
|
||||
self.ignoreInSavedMessages = ignoreInSavedMessages
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -908,7 +908,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
}
|
||||
self.textPlaceholderNode.isHidden = inputHasText
|
||||
|
||||
let textInputFrame = CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)
|
||||
let additionalRightInset = self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelHeight, transition: transition)
|
||||
|
||||
let textInputFrame = CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right - additionalRightInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)
|
||||
transition.updateFrame(node: self.textInputContainer, frame: textInputFrame)
|
||||
|
||||
if let textInputNode = self.textInputNode {
|
||||
|
|
@ -923,16 +925,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
}
|
||||
}
|
||||
|
||||
self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelHeight, transition: transition)
|
||||
|
||||
self.actionButtons.updateAccessibility()
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
private func updateFieldAndButtonsLayout(inputHasText: Bool, panelHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
private func updateFieldAndButtonsLayout(inputHasText: Bool, panelHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
guard let (width, leftInset, rightInset, additionalSideInsets, _, metrics, _) = self.validLayout else {
|
||||
return
|
||||
return 0.0
|
||||
}
|
||||
var textFieldMinHeight: CGFloat = 33.0
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
|
|
@ -954,6 +954,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
let baseWidth = width - leftInset - rightInset
|
||||
let textInputFrame = self.textInputContainer.frame
|
||||
|
||||
var textFieldInsets = self.textFieldInsets(metrics: metrics)
|
||||
if additionalSideInsets.right > 0.0 {
|
||||
textFieldInsets.right += additionalSideInsets.right / 3.0
|
||||
}
|
||||
|
||||
var textBackgroundInset: CGFloat = 0.0
|
||||
let actionButtonsSize: CGSize
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
|
|
@ -968,24 +973,19 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
text = presentationInterfaceState.strings.MediaPicker_Send
|
||||
}
|
||||
actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: isMinimized, text: text, interfaceState: presentationInterfaceState)
|
||||
textBackgroundInset = 44.0 - actionButtonsSize.width
|
||||
textBackgroundInset = actionButtonsSize.width - 44.0
|
||||
} else {
|
||||
actionButtonsSize = CGSize(width: 44.0, height: minimalHeight)
|
||||
}
|
||||
|
||||
var textFieldInsets = self.textFieldInsets(metrics: metrics)
|
||||
if additionalSideInsets.right > 0.0 {
|
||||
textFieldInsets.right += additionalSideInsets.right / 3.0
|
||||
}
|
||||
|
||||
let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: actionButtonsSize)
|
||||
transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame)
|
||||
|
||||
let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: textInputFrame.size.width + composeButtonsOffset + textBackgroundInset, height: textInputFrame.size.height))
|
||||
let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: textInputFrame.size.width + composeButtonsOffset, height: textInputFrame.size.height))
|
||||
transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: textInputBackgroundFrame)
|
||||
|
||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset + textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
|
||||
transition.updateFrame(layer: self.textInputBackgroundImageNode.layer, frame: CGRect(x: 0.0, y: 0.0, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset + textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
|
||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
|
||||
transition.updateFrame(layer: self.textInputBackgroundImageNode.layer, frame: CGRect(x: 0.0, y: 0.0, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
|
||||
|
||||
var textInputViewRealInsets = UIEdgeInsets()
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
|
|
@ -1027,6 +1027,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
placeholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size)
|
||||
}
|
||||
transition.updateFrame(node: self.textPlaceholderNode, frame: placeholderFrame)
|
||||
|
||||
return textBackgroundInset
|
||||
}
|
||||
|
||||
private var skipUpdate = false
|
||||
|
|
@ -1387,7 +1389,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
|
||||
let panelHeight = self.updateTextHeight(animated: animated)
|
||||
if self.isAttachment, let panelHeight = panelHeight {
|
||||
self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
let _ = self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ public func emojiFlagForISOCountryCode(_ countryCode: String) -> String {
|
|||
return "🛰️"
|
||||
} else if countryCode == "XV" {
|
||||
return "🌍"
|
||||
} else if countryCode == "TS" {
|
||||
return "🏳️"
|
||||
}
|
||||
|
||||
if ["YL"].contains(countryCode) {
|
||||
|
|
|
|||
|
|
@ -2376,7 +2376,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
|
||||
|
||||
//if self. {
|
||||
//TODO:localize
|
||||
//self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Add", badge: "\(count)", font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: count > 0, progress: .none, isEnabled: true, hasShimmer: false)))
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1302,7 +1302,6 @@ private final class DemoSheetContent: CombinedComponent {
|
|||
buttonAnimationName = "premium_unlock"
|
||||
case .paidMessages:
|
||||
buttonText = strings.Premium_PaidMessages_Proceed
|
||||
buttonAnimationName = "premium_unlock"
|
||||
default:
|
||||
buttonText = strings.Common_OK
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1470,16 +1470,15 @@ public func privacyAndSecurityController(
|
|||
}
|
||||
|
||||
showPrivacySuggestionImpl = {
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if reviewCallPrivacySuggestion {
|
||||
reviewCallPrivacySuggestion = false
|
||||
let alertController = textAlertController(
|
||||
context: context,
|
||||
title: "Call Settings",
|
||||
text: "You've restricted who can message you, but anyone can still call you. Would you like to review these settings?",
|
||||
title: presentationData.strings.Privacy_Review_Calls_Title,
|
||||
text: presentationData.strings.Privacy_Review_Calls_Text,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: "Review", action: {
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.Privacy_Review_Review, action: {
|
||||
arguments.openVoiceCallPrivacy()
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
|
|
@ -1495,10 +1494,10 @@ public func privacyAndSecurityController(
|
|||
reviewInvitePrivacySuggestion = false
|
||||
let alertController = textAlertController(
|
||||
context: context,
|
||||
title: "Invitation Settings",
|
||||
text: "You've restricted who can message you, but anyone can still invite you to groups and channels. Would you like to review these settings?",
|
||||
title: presentationData.strings.Privacy_Review_Invite_Title,
|
||||
text: presentationData.strings.Privacy_Review_Invite_Text,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: "Review", action: {
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.Privacy_Review_Review, action: {
|
||||
arguments.openGroupsPrivacy()
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||
case adsTonBalanceInfo(PresentationTheme, String)
|
||||
|
||||
case adsStarsBalanceTitle(PresentationTheme, String)
|
||||
case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool, Int32?)
|
||||
case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool, Bool, Int32?)
|
||||
case adsStarsBalanceInfo(PresentationTheme, String)
|
||||
|
||||
case earnStarsInfo
|
||||
|
|
@ -827,8 +827,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .adsStarsBalance(lhsTheme, lhsStats, lhsCanWithdraw, lhsIsEnabled, lhsCooldownUntilTimestamp):
|
||||
if case let .adsStarsBalance(rhsTheme, rhsStats, rhsCanWithdraw, rhsIsEnabled, rhsCooldownUntilTimestamp) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsCanWithdraw == rhsCanWithdraw, lhsIsEnabled == rhsIsEnabled, lhsCooldownUntilTimestamp == rhsCooldownUntilTimestamp {
|
||||
case let .adsStarsBalance(lhsTheme, lhsStats, lhsCanWithdraw, lhsCanBuyAds, lhsIsEnabled, lhsCooldownUntilTimestamp):
|
||||
if case let .adsStarsBalance(rhsTheme, rhsStats, rhsCanWithdraw, rhsCanBuyAds, rhsIsEnabled, rhsCooldownUntilTimestamp) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats, lhsCanWithdraw == rhsCanWithdraw, lhsCanBuyAds == rhsCanBuyAds, lhsIsEnabled == rhsIsEnabled, lhsCooldownUntilTimestamp == rhsCooldownUntilTimestamp {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
@ -1108,7 +1108,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||
arguments.openMonetizationInfo()
|
||||
})
|
||||
case let .adsStarsBalance(_, stats, canWithdraw, isEnabled, cooldownUntilTimestamp):
|
||||
case let .adsStarsBalance(_, stats, canWithdraw, canBuyAds, isEnabled, cooldownUntilTimestamp):
|
||||
return MonetizationBalanceItem(
|
||||
context: arguments.context,
|
||||
presentationData: presentationData,
|
||||
|
|
@ -1131,7 +1131,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||
arguments.requestStarsWithdraw()
|
||||
}
|
||||
},
|
||||
buyAdsAction: canWithdraw ? {
|
||||
buyAdsAction: canWithdraw && canBuyAds ? {
|
||||
arguments.buyAds()
|
||||
} : nil,
|
||||
sectionId: self.section,
|
||||
|
|
@ -1565,7 +1565,7 @@ private func monetizationEntries(
|
|||
presentationData: PresentationData,
|
||||
state: ChannelStatsControllerState,
|
||||
peer: EnginePeer?,
|
||||
data: RevenueStats,
|
||||
data: RevenueStats?,
|
||||
boostData: ChannelBoostStatus?,
|
||||
transactionsInfo: RevenueStatsTransactionsContext.State,
|
||||
starsData: StarsRevenueStats?,
|
||||
|
|
@ -1584,7 +1584,7 @@ private func monetizationEntries(
|
|||
isBot = true
|
||||
}
|
||||
|
||||
if canViewRevenue {
|
||||
if canViewRevenue, let data {
|
||||
entries.append(.adsHeader(presentationData.theme, isBot ? presentationData.strings.Monetization_Bot_Header : presentationData.strings.Monetization_Header))
|
||||
|
||||
if !data.topHoursGraph.isEmpty {
|
||||
|
|
@ -1608,7 +1608,7 @@ private func monetizationEntries(
|
|||
entries.append(.adsProceedsTitle(presentationData.theme, presentationData.strings.Monetization_StarsProceeds_Title))
|
||||
entries.append(.adsProceedsOverview(presentationData.theme, canViewRevenue ? data : nil, canViewStarsRevenue ? starsData : nil))
|
||||
|
||||
let hasTonBalance = data.balances.overallRevenue > 0
|
||||
let hasTonBalance = (data?.balances.overallRevenue ?? 0) > 0
|
||||
let hasStarsBalance = (starsData?.balances.overallRevenue ?? StarsAmount.zero) > StarsAmount.zero
|
||||
|
||||
let proceedsInfo: String
|
||||
|
|
@ -1622,11 +1622,15 @@ private func monetizationEntries(
|
|||
entries.append(.adsProceedsInfo(presentationData.theme, proceedsInfo))
|
||||
|
||||
var isCreator = false
|
||||
var isGroup = false
|
||||
if let peer, case let .channel(channel) = peer, channel.flags.contains(.isCreator) {
|
||||
isCreator = true
|
||||
if case .group = channel.info {
|
||||
isGroup = true
|
||||
}
|
||||
}
|
||||
|
||||
if canViewRevenue {
|
||||
if canViewRevenue, let data {
|
||||
entries.append(.adsTonBalanceTitle(presentationData.theme, isBot ? presentationData.strings.Monetization_Bot_BalanceTitle : presentationData.strings.Monetization_TonBalanceTitle))
|
||||
entries.append(.adsTonBalance(presentationData.theme, data, (isCreator || isBot) && data.balances.availableBalance > 0, data.balances.withdrawEnabled))
|
||||
|
||||
|
|
@ -1643,12 +1647,14 @@ private func monetizationEntries(
|
|||
}
|
||||
}
|
||||
|
||||
if canViewStarsRevenue {
|
||||
if let starsData, starsData.balances.overallRevenue > StarsAmount.zero {
|
||||
entries.append(.adsStarsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_StarsBalanceTitle))
|
||||
entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > StarsAmount.zero, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp))
|
||||
entries.append(.adsStarsBalanceInfo(presentationData.theme, presentationData.strings.Monetization_Balance_StarsInfo))
|
||||
}
|
||||
if canViewStarsRevenue, let starsData, starsData.balances.overallRevenue > StarsAmount.zero {
|
||||
entries.append(.adsStarsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_StarsBalanceTitle))
|
||||
entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > StarsAmount.zero, !isGroup, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp))
|
||||
entries.append(.adsStarsBalanceInfo(presentationData.theme, isGroup ? presentationData.strings.Monetization_Balance_StarsInfoGroup : presentationData.strings.Monetization_Balance_StarsInfo))
|
||||
}
|
||||
|
||||
if canJoinRefPrograms && !isGroup {
|
||||
entries.append(.earnStarsInfo)
|
||||
}
|
||||
|
||||
var addedTransactionsTabs = false
|
||||
|
|
@ -1666,7 +1672,7 @@ private func monetizationEntries(
|
|||
if canViewStarsRevenue && !starsTransactionsInfo.transactions.isEmpty && (transactionsInfo.transactions.isEmpty || state.starsSelected) {
|
||||
displayStarsTransactions = true
|
||||
}
|
||||
|
||||
|
||||
if displayTonTransactions {
|
||||
if !addedTransactionsTabs {
|
||||
entries.append(.adsTransactionsTitle(presentationData.theme, isBot ? presentationData.strings.Monetization_TransactionsTitle.uppercased() : presentationData.strings.Monetization_TonTransactions.uppercased()))
|
||||
|
|
@ -1700,11 +1706,7 @@ private func monetizationEntries(
|
|||
|
||||
if displayStarsTransactions {
|
||||
if !addedTransactionsTabs {
|
||||
if canJoinRefPrograms {
|
||||
entries.append(.earnStarsInfo)
|
||||
}
|
||||
|
||||
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_StarsTransactions.uppercased()))
|
||||
entries.append(.adsTransactionsTitle(presentationData.theme, isGroup ? presentationData.strings.Monetization_TransactionsTitle.uppercased() : presentationData.strings.Monetization_StarsTransactions.uppercased()))
|
||||
}
|
||||
|
||||
var transactions = starsTransactionsInfo.transactions
|
||||
|
|
@ -1733,7 +1735,7 @@ private func monetizationEntries(
|
|||
}
|
||||
}
|
||||
|
||||
if isCreator && canViewRevenue {
|
||||
if isCreator && canViewRevenue && !isGroup {
|
||||
var switchOffAdds: Bool? = nil
|
||||
if let boostData, boostData.level >= premiumConfiguration.minChannelRestrictAdsLevel {
|
||||
switchOffAdds = adsRestricted
|
||||
|
|
@ -1797,7 +1799,7 @@ private func channelStatsControllerEntries(
|
|||
)
|
||||
}
|
||||
case .monetization:
|
||||
if let revenueState {
|
||||
if revenueState != nil || starsState != nil {
|
||||
return monetizationEntries(
|
||||
presentationData: presentationData,
|
||||
state: state,
|
||||
|
|
@ -1825,6 +1827,7 @@ public func channelStatsController(
|
|||
peerId: PeerId,
|
||||
section: ChannelStatsSection = .stats,
|
||||
existingRevenueContext: RevenueStatsContext? = nil,
|
||||
existingStarsRevenueContext: StarsRevenueStatsContext? = nil,
|
||||
boostStatus: ChannelBoostStatus? = nil,
|
||||
boostStatusUpdated: ((ChannelBoostStatus) -> Void)? = nil
|
||||
) -> ViewController {
|
||||
|
|
@ -1888,7 +1891,7 @@ public func channelStatsController(
|
|||
let revenueState = Promise<RevenueStatsContextState?>()
|
||||
revenueState.set(.single(nil) |> then(revenueContext.state |> map(Optional.init)))
|
||||
|
||||
let starsContext = context.engine.payments.peerStarsRevenueContext(peerId: peerId)
|
||||
let starsContext = existingStarsRevenueContext ?? context.engine.payments.peerStarsRevenueContext(peerId: peerId)
|
||||
let starsState = Promise<StarsRevenueStatsContextState?>()
|
||||
starsState.set(.single(nil) |> then(starsContext.state |> map(Optional.init)))
|
||||
|
||||
|
|
@ -2157,8 +2160,9 @@ public func channelStatsController(
|
|||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let (canViewStats, adsRestricted, _, canViewStarsRevenue) = peerData
|
||||
let (canViewStats, adsRestricted, _, _) = peerData
|
||||
var canViewRevenue = peerData.2
|
||||
var canViewStarsRevenue = peerData.3
|
||||
|
||||
var canJoinRefPrograms = false
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
||||
|
|
@ -2192,7 +2196,7 @@ public func channelStatsController(
|
|||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||
}
|
||||
case .monetization:
|
||||
if revenueState?.stats == nil {
|
||||
if revenueState?.stats == nil && starsState?.stats == nil {
|
||||
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
|
||||
}
|
||||
}
|
||||
|
|
@ -2222,7 +2226,10 @@ public func channelStatsController(
|
|||
var headerItem: BoostHeaderItem?
|
||||
var leftNavigationButton: ItemListNavigationButton?
|
||||
var boostsOnly = false
|
||||
if existingRevenueContext != nil {
|
||||
if existingStarsRevenueContext != nil {
|
||||
title = .text(presentationData.strings.Stats_Monetization)
|
||||
canViewStarsRevenue = true
|
||||
} else if existingRevenueContext != nil {
|
||||
title = .text(presentationData.strings.Stats_TonBotRevenue_Title)
|
||||
canViewRevenue = true
|
||||
} else if section == .boosts {
|
||||
|
|
|
|||
|
|
@ -231,7 +231,10 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
var itemDate: String
|
||||
switch item.transaction.peer {
|
||||
case let .peer(peer):
|
||||
if !item.transaction.media.isEmpty {
|
||||
if item.transaction.flags.contains(.isPaidMessage) {
|
||||
itemTitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast)
|
||||
itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_PaidMessage(item.transaction.paidMessageCount ?? 1)
|
||||
} else if !item.transaction.media.isEmpty {
|
||||
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_MediaPurchase
|
||||
itemSubtitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast)
|
||||
} else if let title = item.transaction.title {
|
||||
|
|
|
|||
|
|
@ -734,7 +734,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
|
||||
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
|
||||
dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
|
||||
dict[227293676] = { return Api.PeerSettings.parse_peerSettings($0) }
|
||||
dict[-193510921] = { return Api.PeerSettings.parse_peerSettings($0) }
|
||||
dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) }
|
||||
dict[1000707084] = { return Api.PhoneCall.parse_phoneCall($0) }
|
||||
dict[587035009] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
|
||||
|
|
@ -1390,7 +1390,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
|
||||
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
|
||||
dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) }
|
||||
dict[-418915641] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
|
||||
dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
|
||||
dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) }
|
||||
dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) }
|
||||
dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) }
|
||||
|
|
|
|||
|
|
@ -960,13 +960,13 @@ public extension Api {
|
|||
}
|
||||
public extension Api {
|
||||
enum PeerSettings: TypeConstructorDescription {
|
||||
case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?, chargePaidMessageStars: Int64?, registrationMonth: String?, phoneCountry: String?, locationCountry: String?)
|
||||
case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?, chargePaidMessageStars: Int64?, registrationMonth: String?, phoneCountry: String?, nameChangeDate: Int32?, photoChangeDate: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl, let chargePaidMessageStars, let registrationMonth, let phoneCountry, let locationCountry):
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl, let chargePaidMessageStars, let registrationMonth, let phoneCountry, let nameChangeDate, let photoChangeDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(227293676)
|
||||
buffer.appendInt32(-193510921)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)}
|
||||
|
|
@ -977,15 +977,16 @@ public extension Api {
|
|||
if Int(flags) & Int(1 << 14) != 0 {serializeInt64(chargePaidMessageStars!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 15) != 0 {serializeString(registrationMonth!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 16) != 0 {serializeString(phoneCountry!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 17) != 0 {serializeString(locationCountry!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 17) != 0 {serializeInt32(nameChangeDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 18) != 0 {serializeInt32(photoChangeDate!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl, let chargePaidMessageStars, let registrationMonth, let phoneCountry, let locationCountry):
|
||||
return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any), ("chargePaidMessageStars", chargePaidMessageStars as Any), ("registrationMonth", registrationMonth as Any), ("phoneCountry", phoneCountry as Any), ("locationCountry", locationCountry as Any)])
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl, let chargePaidMessageStars, let registrationMonth, let phoneCountry, let nameChangeDate, let photoChangeDate):
|
||||
return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any), ("chargePaidMessageStars", chargePaidMessageStars as Any), ("registrationMonth", registrationMonth as Any), ("phoneCountry", phoneCountry as Any), ("nameChangeDate", nameChangeDate as Any), ("photoChangeDate", photoChangeDate as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1008,8 +1009,10 @@ public extension Api {
|
|||
if Int(_1!) & Int(1 << 15) != 0 {_8 = parseString(reader) }
|
||||
var _9: String?
|
||||
if Int(_1!) & Int(1 << 16) != 0 {_9 = parseString(reader) }
|
||||
var _10: String?
|
||||
if Int(_1!) & Int(1 << 17) != 0 {_10 = parseString(reader) }
|
||||
var _10: Int32?
|
||||
if Int(_1!) & Int(1 << 17) != 0 {_10 = reader.readInt32() }
|
||||
var _11: Int32?
|
||||
if Int(_1!) & Int(1 << 18) != 0 {_11 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil
|
||||
|
|
@ -1020,8 +1023,9 @@ public extension Api {
|
|||
let _c8 = (Int(_1!) & Int(1 << 15) == 0) || _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 16) == 0) || _9 != nil
|
||||
let _c10 = (Int(_1!) & Int(1 << 17) == 0) || _10 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6, chargePaidMessageStars: _7, registrationMonth: _8, phoneCountry: _9, locationCountry: _10)
|
||||
let _c11 = (Int(_1!) & Int(1 << 18) == 0) || _11 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
||||
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6, chargePaidMessageStars: _7, registrationMonth: _8, phoneCountry: _9, nameChangeDate: _10, photoChangeDate: _11)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
public extension Api.payments {
|
||||
enum SavedStarGifts: TypeConstructorDescription {
|
||||
case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, pinnedToTop: [Int64]?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
|
||||
case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users):
|
||||
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-418915641)
|
||||
buffer.appendInt32(-1779201615)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {chatNotificationsEnabled!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(pinnedToTop!.count))
|
||||
for item in pinnedToTop! {
|
||||
serializeInt64(item, buffer: buffer, boxed: false)
|
||||
}}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(gifts.count))
|
||||
for item in gifts {
|
||||
|
|
@ -38,8 +33,8 @@ public extension Api.payments {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users):
|
||||
return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("pinnedToTop", pinnedToTop as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users):
|
||||
return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,34 +47,29 @@ public extension Api.payments {
|
|||
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
} }
|
||||
var _4: [Int64]?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
|
||||
} }
|
||||
var _5: [Api.SavedStarGift]?
|
||||
var _4: [Api.SavedStarGift]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
|
||||
}
|
||||
var _6: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
|
||||
var _7: [Api.Chat]?
|
||||
var _5: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) }
|
||||
var _6: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _8: [Api.User]?
|
||||
var _7: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, pinnedToTop: _4, gifts: _5!, nextOffset: _6, chats: _7!, users: _8!)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
text = nil
|
||||
entities = nil
|
||||
}
|
||||
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer?.peerId, months: months, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
|
||||
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 5)) != 0, boostPeerId: boostPeer?.peerId, months: months, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
|
||||
case let .messageActionGiveawayLaunch(_, stars):
|
||||
return TelegramMediaAction(action: .giveawayLaunched(stars: stars))
|
||||
case let .messageActionGiveawayResults(flags, winners, unclaimed):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import SwiftSignalKit
|
|||
extension PeerStatusSettings {
|
||||
init(apiSettings: Api.PeerSettings) {
|
||||
switch apiSettings {
|
||||
case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl, chargePaidMessageStars, registrationMonth, phoneCountry, locationCountry):
|
||||
case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl, chargePaidMessageStars, registrationMonth, phoneCountry, nameChangeDate, photoChangeDate):
|
||||
var result = PeerStatusSettings.Flags()
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
result.insert(.canAddContact)
|
||||
|
|
@ -54,7 +54,8 @@ extension PeerStatusSettings {
|
|||
paidMessageStars: chargePaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) },
|
||||
registrationDate: registrationMonth,
|
||||
phoneCountry: phoneCountry,
|
||||
locationCountry: locationCountry
|
||||
nameChangeDate: nameChangeDate,
|
||||
photoChangeDate: photoChangeDate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ private func requestChannelStats(postbox: Postbox, network: Network, peerId: Pee
|
|||
}
|
||||
return nil
|
||||
} |> mapToSignal { data -> Signal<ChannelStats?, NoError> in
|
||||
guard let (statsDatacenterId, peer) = data, let inputChannel = apiInputChannel(peer) else {
|
||||
guard let (statsDatacenterId, peer) = data, let channel = peer as? TelegramChannel, case .broadcast = channel.info, let inputChannel = apiInputChannel(peer) else {
|
||||
return .never()
|
||||
}
|
||||
|
||||
|
|
@ -813,7 +813,7 @@ private func requestGroupStats(accountPeerId: PeerId, postbox: Postbox, network:
|
|||
}
|
||||
return nil
|
||||
} |> mapToSignal { data -> Signal<GroupStats?, NoError> in
|
||||
guard let (statsDatacenterId, peer) = data, let inputChannel = apiInputChannel(peer) else {
|
||||
guard let (statsDatacenterId, peer) = data, let channel = peer as? TelegramChannel, case .group = channel.info, let inputChannel = apiInputChannel(peer) else {
|
||||
return .never()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ private func requestRevenueStats(postbox: Postbox, network: Network, peerId: Pee
|
|||
}
|
||||
return nil
|
||||
} |> mapToSignal { peer -> Signal<RevenueStats?, NoError> in
|
||||
guard let peer, let inputPeer = apiInputPeer(peer) else {
|
||||
guard let peer, let channel = peer as? TelegramChannel, case .broadcast = channel.info, let inputPeer = apiInputPeer(peer) else {
|
||||
return .never()
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +354,7 @@ private final class RevenueStatsTransactionsContextImpl {
|
|||
}
|
||||
|> mapToSignal { peer -> Signal<([RevenueStatsTransactionsContext.State.Transaction], Int32, Int32?), NoError> in
|
||||
if let peer {
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
guard let channel = peer as? TelegramChannel, case .broadcast = channel.info, let inputPeer = apiInputPeer(peer) else {
|
||||
return .complete()
|
||||
}
|
||||
let offset = lastOffset ?? 0
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ public struct Namespaces {
|
|||
public static let cachedProfileGifts: Int8 = 43
|
||||
public static let recommendedBots: Int8 = 44
|
||||
public static let channelsForPublicReaction: Int8 = 45
|
||||
public static let cachedGroupsInCommon: Int8 = 46
|
||||
}
|
||||
|
||||
public struct UnorderedItemList {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
public var paidMessageStars: StarsAmount?
|
||||
public var registrationDate: String?
|
||||
public var phoneCountry: String?
|
||||
public var locationCountry: String?
|
||||
public var nameChangeDate: Int32?
|
||||
public var photoChangeDate: Int32?
|
||||
|
||||
public init() {
|
||||
self.flags = PeerStatusSettings.Flags()
|
||||
|
|
@ -53,7 +54,8 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
self.paidMessageStars = nil
|
||||
self.registrationDate = nil
|
||||
self.phoneCountry = nil
|
||||
self.locationCountry = nil
|
||||
self.nameChangeDate = nil
|
||||
self.photoChangeDate = nil
|
||||
}
|
||||
|
||||
public init(
|
||||
|
|
@ -66,7 +68,8 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
paidMessageStars: StarsAmount? = nil,
|
||||
registrationDate: String? = nil,
|
||||
phoneCountry: String? = nil,
|
||||
locationCountry: String? = nil
|
||||
nameChangeDate: Int32? = nil,
|
||||
photoChangeDate: Int32? = nil
|
||||
) {
|
||||
self.flags = flags
|
||||
self.geoDistance = geoDistance
|
||||
|
|
@ -77,7 +80,8 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
self.paidMessageStars = paidMessageStars
|
||||
self.registrationDate = registrationDate
|
||||
self.phoneCountry = phoneCountry
|
||||
self.locationCountry = locationCountry
|
||||
self.nameChangeDate = nameChangeDate
|
||||
self.photoChangeDate = photoChangeDate
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
|
|
@ -90,7 +94,8 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
self.paidMessageStars = decoder.decodeCodable(StarsAmount.self, forKey: "paidMessageStars")
|
||||
self.registrationDate = decoder.decodeOptionalStringForKey("registrationDate")
|
||||
self.phoneCountry = decoder.decodeOptionalStringForKey("phoneCountry")
|
||||
self.locationCountry = decoder.decodeOptionalStringForKey("locationCountry")
|
||||
self.nameChangeDate = decoder.decodeOptionalInt32ForKey("nameChangeDate")
|
||||
self.photoChangeDate = decoder.decodeOptionalInt32ForKey("photoChangeDate")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
|
|
@ -135,10 +140,15 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "phoneCountry")
|
||||
}
|
||||
if let locationCountry = self.locationCountry {
|
||||
encoder.encodeString(locationCountry, forKey: "locationCountry")
|
||||
if let nameChangeDate = self.nameChangeDate {
|
||||
encoder.encodeInt32(nameChangeDate, forKey: "nameChangeDate")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "locationCountry")
|
||||
encoder.encodeNil(forKey: "nameChangeDate")
|
||||
}
|
||||
if let photoChangeDate = self.photoChangeDate {
|
||||
encoder.encodeInt32(photoChangeDate, forKey: "photoChangeDate")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "photoChangeDate")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
|
|||
return .single([])
|
||||
}
|
||||
|
||||
if let _ = peer as? TelegramChannel {
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,6 +745,25 @@ func _internal_updateStarGiftAddedToProfile(account: Account, reference: StarGif
|
|||
}
|
||||
}
|
||||
|
||||
func _internal_updateStarGiftsPinnedToTop(account: Account, peerId: EnginePeer.Id, references: [StarGiftReference]) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction in
|
||||
let peer = transaction.getPeer(peerId)
|
||||
let starGifts = references.compactMap { $0.apiStarGiftReference(transaction: transaction) }
|
||||
return (peer, starGifts)
|
||||
}
|
||||
|> mapToSignal { peer, starGifts in
|
||||
guard let inputPeer = peer.flatMap(apiInputPeer) else {
|
||||
return .complete()
|
||||
}
|
||||
return account.network.request(Api.functions.payments.toggleStarGiftsPinnedToTop(peer: inputPeer, stargift: starGifts))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
||||
public enum TransferStarGiftError {
|
||||
case generic
|
||||
}
|
||||
|
|
@ -854,6 +873,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
|
|||
entities: nil,
|
||||
nameHidden: false,
|
||||
savedToProfile: savedToProfile,
|
||||
pinnedToTop: false,
|
||||
convertStars: nil,
|
||||
canUpgrade: false,
|
||||
canExportDate: canExportDate,
|
||||
|
|
@ -1000,7 +1020,7 @@ private final class ProfileGiftsContextImpl {
|
|||
let dataState = isFiltered ? self.filteredDataState : self.dataState
|
||||
|
||||
if case let .ready(true, initialNextOffset) = dataState {
|
||||
if !isFiltered, self.gifts.isEmpty, initialNextOffset == nil {
|
||||
if !isFiltered, self.gifts.isEmpty, initialNextOffset == nil, !reload {
|
||||
self.cacheDisposable.set((self.account.postbox.transaction { transaction -> CachedProfileGifts? in
|
||||
let cachedGifts = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileGifts.self)
|
||||
cachedGifts?.render(transaction: transaction)
|
||||
|
|
@ -1064,8 +1084,7 @@ private final class ProfileGiftsContextImpl {
|
|||
}
|
||||
return postbox.transaction { transaction -> ([ProfileGiftsContext.State.StarGift], Int32, String?, Bool?) in
|
||||
switch result {
|
||||
case let .savedStarGifts(_, count, apiNotificationsEnabled, pinnedToTop, apiGifts, nextOffset, chats, users):
|
||||
let _ = pinnedToTop
|
||||
case let .savedStarGifts(_, count, apiNotificationsEnabled, apiGifts, nextOffset, chats, users):
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||
|
||||
|
|
@ -1131,9 +1150,33 @@ private final class ProfileGiftsContextImpl {
|
|||
self.actionDisposable.set(
|
||||
_internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: added).startStrict()
|
||||
)
|
||||
|
||||
if let index = self.gifts.firstIndex(where: { $0.reference == reference }) {
|
||||
self.gifts[index] = self.gifts[index].withSavedToProfile(added)
|
||||
if !added && self.gifts[index].pinnedToTop {
|
||||
let pinnedGifts = self.gifts.filter { $0.pinnedToTop && $0.reference != reference }
|
||||
let existingGifts = Set(pinnedGifts.compactMap { $0.reference })
|
||||
|
||||
var updatedGifts: [ProfileGiftsContext.State.StarGift] = []
|
||||
for gift in self.gifts {
|
||||
if let reference = gift.reference, existingGifts.contains(reference) {
|
||||
continue
|
||||
}
|
||||
var gift = gift
|
||||
if gift.reference == reference {
|
||||
gift = gift.withPinnedToTop(false).withSavedToProfile(false)
|
||||
}
|
||||
updatedGifts.append(gift)
|
||||
}
|
||||
updatedGifts.sort { lhs, rhs in
|
||||
lhs.date > rhs.date
|
||||
}
|
||||
updatedGifts.insert(contentsOf: pinnedGifts, at: 0)
|
||||
self.gifts = updatedGifts
|
||||
} else {
|
||||
self.gifts[index] = self.gifts[index].withSavedToProfile(added)
|
||||
}
|
||||
}
|
||||
|
||||
if let index = self.filteredGifts.firstIndex(where: { $0.reference == reference }) {
|
||||
self.filteredGifts[index] = self.filteredGifts[index].withSavedToProfile(added)
|
||||
if !self.filter.contains(.hidden) && !added {
|
||||
|
|
@ -1143,6 +1186,107 @@ private final class ProfileGiftsContextImpl {
|
|||
self.pushState()
|
||||
}
|
||||
|
||||
func updateStarGiftPinnedToTop(reference: StarGiftReference, pinnedToTop: Bool) {
|
||||
var pinnedGifts = self.gifts.filter { $0.pinnedToTop }
|
||||
var saveToProfile = false
|
||||
if var gift = self.gifts.first(where: { $0.reference == reference }) {
|
||||
gift = gift.withPinnedToTop(pinnedToTop)
|
||||
if pinnedToTop {
|
||||
if !gift.savedToProfile {
|
||||
gift = gift.withSavedToProfile(true)
|
||||
saveToProfile = true
|
||||
}
|
||||
pinnedGifts.append(gift)
|
||||
} else {
|
||||
pinnedGifts.removeAll(where: { $0.reference == reference })
|
||||
}
|
||||
}
|
||||
let existingGifts = Set(pinnedGifts.compactMap { $0.reference })
|
||||
|
||||
var updatedGifts: [ProfileGiftsContext.State.StarGift] = []
|
||||
for gift in self.gifts {
|
||||
if let reference = gift.reference, existingGifts.contains(reference) {
|
||||
continue
|
||||
}
|
||||
updatedGifts.append(gift)
|
||||
}
|
||||
updatedGifts.sort { lhs, rhs in
|
||||
lhs.date > rhs.date
|
||||
}
|
||||
updatedGifts.insert(contentsOf: pinnedGifts, at: 0)
|
||||
self.gifts = updatedGifts
|
||||
|
||||
if let index = self.filteredGifts.firstIndex(where: { $0.reference == reference }) {
|
||||
self.filteredGifts[index] = self.filteredGifts[index].withPinnedToTop(pinnedToTop)
|
||||
}
|
||||
self.pushState()
|
||||
|
||||
var signal = _internal_updateStarGiftsPinnedToTop(account: self.account, peerId: self.peerId, references: pinnedGifts.compactMap { $0.reference })
|
||||
|
||||
if saveToProfile {
|
||||
signal = _internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: true)
|
||||
|> then(signal)
|
||||
}
|
||||
|
||||
self.actionDisposable.set(
|
||||
(signal |> deliverOn(self.queue)).startStrict(completed: { [weak self] in
|
||||
self?.reload()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
public func updatePinnedToTopStarGifts(references: [StarGiftReference]) {
|
||||
let existingGifts = Set(references)
|
||||
var saveSignals: [Signal<Never, NoError>] = []
|
||||
let currentPinnedGifts = self.gifts.filter { gift in
|
||||
if let reference = gift.reference {
|
||||
return existingGifts.contains(reference)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}.map { gift in
|
||||
if !gift.savedToProfile, let reference = gift.reference {
|
||||
saveSignals.append(_internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: true))
|
||||
}
|
||||
return gift.withPinnedToTop(true).withSavedToProfile(true)
|
||||
}
|
||||
|
||||
var updatedGifts: [ProfileGiftsContext.State.StarGift] = []
|
||||
for gift in self.gifts {
|
||||
if let reference = gift.reference, existingGifts.contains(reference) {
|
||||
continue
|
||||
}
|
||||
updatedGifts.append(gift.withPinnedToTop(false))
|
||||
}
|
||||
updatedGifts.sort { lhs, rhs in
|
||||
lhs.date > rhs.date
|
||||
}
|
||||
|
||||
var pinnedGifts: [ProfileGiftsContext.State.StarGift] = []
|
||||
for reference in references {
|
||||
if let gift = currentPinnedGifts.first(where: { $0.reference == reference }) {
|
||||
pinnedGifts.append(gift)
|
||||
}
|
||||
}
|
||||
updatedGifts.insert(contentsOf: pinnedGifts, at: 0)
|
||||
self.gifts = updatedGifts
|
||||
|
||||
self.pushState()
|
||||
|
||||
var signal = _internal_updateStarGiftsPinnedToTop(account: self.account, peerId: self.peerId, references: pinnedGifts.compactMap { $0.reference })
|
||||
if !saveSignals.isEmpty {
|
||||
signal = combineLatest(saveSignals)
|
||||
|> ignoreValues
|
||||
|> then(signal)
|
||||
}
|
||||
|
||||
self.actionDisposable.set(
|
||||
(signal |> deliverOn(self.queue)).startStrict(completed: { [weak self] in
|
||||
self?.reload()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func convertStarGift(reference: StarGiftReference) {
|
||||
self.actionDisposable.set(
|
||||
_internal_convertStarGift(account: self.account, reference: reference).startStrict()
|
||||
|
|
@ -1277,6 +1421,7 @@ public final class ProfileGiftsContext {
|
|||
case messageId
|
||||
case nameHidden
|
||||
case savedToProfile
|
||||
case pinnedToTop
|
||||
case convertStars
|
||||
case canUpgrade
|
||||
case canExportDate
|
||||
|
|
@ -1293,6 +1438,7 @@ public final class ProfileGiftsContext {
|
|||
public let entities: [MessageTextEntity]?
|
||||
public let nameHidden: Bool
|
||||
public let savedToProfile: Bool
|
||||
public let pinnedToTop: Bool
|
||||
public let convertStars: Int64?
|
||||
public let canUpgrade: Bool
|
||||
public let canExportDate: Int32?
|
||||
|
|
@ -1314,6 +1460,7 @@ public final class ProfileGiftsContext {
|
|||
entities: [MessageTextEntity]?,
|
||||
nameHidden: Bool,
|
||||
savedToProfile: Bool,
|
||||
pinnedToTop: Bool,
|
||||
convertStars: Int64?,
|
||||
canUpgrade: Bool,
|
||||
canExportDate: Int32?,
|
||||
|
|
@ -1329,6 +1476,7 @@ public final class ProfileGiftsContext {
|
|||
self.entities = entities
|
||||
self.nameHidden = nameHidden
|
||||
self.savedToProfile = savedToProfile
|
||||
self.pinnedToTop = pinnedToTop
|
||||
self.convertStars = convertStars
|
||||
self.canUpgrade = canUpgrade
|
||||
self.canExportDate = canExportDate
|
||||
|
|
@ -1354,6 +1502,7 @@ public final class ProfileGiftsContext {
|
|||
self.entities = try container.decodeIfPresent([MessageTextEntity].self, forKey: .entities)
|
||||
self.nameHidden = try container.decode(Bool.self, forKey: .nameHidden)
|
||||
self.savedToProfile = try container.decode(Bool.self, forKey: .savedToProfile)
|
||||
self.pinnedToTop = try container.decodeIfPresent(Bool.self, forKey: .pinnedToTop) ?? false
|
||||
self.convertStars = try container.decodeIfPresent(Int64.self, forKey: .convertStars)
|
||||
self.canUpgrade = try container.decode(Bool.self, forKey: .canUpgrade)
|
||||
self.canExportDate = try container.decodeIfPresent(Int32.self, forKey: .canExportDate)
|
||||
|
|
@ -1372,6 +1521,7 @@ public final class ProfileGiftsContext {
|
|||
try container.encodeIfPresent(self.entities, forKey: .entities)
|
||||
try container.encode(self.nameHidden, forKey: .nameHidden)
|
||||
try container.encode(self.savedToProfile, forKey: .savedToProfile)
|
||||
try container.encode(self.pinnedToTop, forKey: .pinnedToTop)
|
||||
try container.encodeIfPresent(self.convertStars, forKey: .convertStars)
|
||||
try container.encode(self.canUpgrade, forKey: .canUpgrade)
|
||||
try container.encodeIfPresent(self.canExportDate, forKey: .canExportDate)
|
||||
|
|
@ -1389,6 +1539,7 @@ public final class ProfileGiftsContext {
|
|||
entities: self.entities,
|
||||
nameHidden: self.nameHidden,
|
||||
savedToProfile: savedToProfile,
|
||||
pinnedToTop: self.pinnedToTop,
|
||||
convertStars: self.convertStars,
|
||||
canUpgrade: self.canUpgrade,
|
||||
canExportDate: self.canExportDate,
|
||||
|
|
@ -1397,6 +1548,24 @@ public final class ProfileGiftsContext {
|
|||
)
|
||||
}
|
||||
|
||||
public func withPinnedToTop(_ pinnedToTop: Bool) -> StarGift {
|
||||
return StarGift(
|
||||
gift: self.gift,
|
||||
reference: self.reference,
|
||||
fromPeer: self.fromPeer,
|
||||
date: self.date,
|
||||
text: self.text,
|
||||
entities: self.entities,
|
||||
nameHidden: self.nameHidden,
|
||||
savedToProfile: self.savedToProfile,
|
||||
pinnedToTop: pinnedToTop,
|
||||
convertStars: self.convertStars,
|
||||
canUpgrade: self.canUpgrade,
|
||||
canExportDate: self.canExportDate,
|
||||
upgradeStars: self.upgradeStars,
|
||||
transferStars: self.transferStars
|
||||
)
|
||||
}
|
||||
fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift {
|
||||
return StarGift(
|
||||
gift: self.gift,
|
||||
|
|
@ -1406,7 +1575,8 @@ public final class ProfileGiftsContext {
|
|||
text: self.text,
|
||||
entities: self.entities,
|
||||
nameHidden: self.nameHidden,
|
||||
savedToProfile: savedToProfile,
|
||||
savedToProfile: self.savedToProfile,
|
||||
pinnedToTop: self.pinnedToTop,
|
||||
convertStars: self.convertStars,
|
||||
canUpgrade: self.canUpgrade,
|
||||
canExportDate: self.canExportDate,
|
||||
|
|
@ -1473,6 +1643,18 @@ public final class ProfileGiftsContext {
|
|||
}
|
||||
}
|
||||
|
||||
public func updateStarGiftPinnedToTop(reference: StarGiftReference, pinnedToTop: Bool) {
|
||||
self.impl.with { impl in
|
||||
impl.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: pinnedToTop)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatePinnedToTopStarGifts(references: [StarGiftReference]) {
|
||||
self.impl.with { impl in
|
||||
impl.updatePinnedToTopStarGifts(references: references)
|
||||
}
|
||||
}
|
||||
|
||||
public func convertStarGift(reference: StarGiftReference) {
|
||||
self.impl.with { impl in
|
||||
impl.convertStarGift(reference: reference)
|
||||
|
|
@ -1569,6 +1751,7 @@ extension ProfileGiftsContext.State.StarGift {
|
|||
}
|
||||
self.nameHidden = (flags & (1 << 0)) != 0
|
||||
self.savedToProfile = (flags & (1 << 5)) == 0
|
||||
self.pinnedToTop = (flags & (1 << 12)) != 0
|
||||
self.convertStars = convertStars
|
||||
self.canUpgrade = (flags & (1 << 10)) != 0
|
||||
self.canExportDate = canExportDate
|
||||
|
|
|
|||
|
|
@ -1504,6 +1504,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
|
|||
entities: nil,
|
||||
nameHidden: false,
|
||||
savedToProfile: savedToProfile,
|
||||
pinnedToTop: false,
|
||||
convertStars: nil,
|
||||
canUpgrade: false,
|
||||
canExportDate: canExportDate,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ private final class GroupsInCommonContextImpl {
|
|||
private let hintGroupInCommon: PeerId?
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private let cacheDisposable = MetaDisposable()
|
||||
|
||||
private var peers: [RenderedPeer] = []
|
||||
private var count: Int?
|
||||
|
|
@ -52,15 +53,41 @@ private final class GroupsInCommonContextImpl {
|
|||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.cacheDisposable.dispose()
|
||||
}
|
||||
|
||||
func loadMore(limit: Int32) {
|
||||
let peerId = self.peerId
|
||||
|
||||
if case .ready(true) = self.dataState {
|
||||
if self.peers.isEmpty {
|
||||
self.cacheDisposable.set((self.account.postbox.transaction { transaction -> ([RenderedPeer], Int32)? in
|
||||
if let cached = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedGroupsInCommon.self) {
|
||||
var peers: [RenderedPeer] = []
|
||||
for peerId in cached.peerIds {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
peers.append(RenderedPeer(peer: peer))
|
||||
}
|
||||
}
|
||||
return (peers, cached.count)
|
||||
}
|
||||
return nil
|
||||
} |> deliverOn(self.queue)).start(next: { [weak self] peersAndCount in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if case .loading = self.dataState, let (peers, count) = peersAndCount {
|
||||
self.peers = peers
|
||||
self.count = Int(count)
|
||||
self.pushState()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
self.dataState = .loading
|
||||
self.pushState()
|
||||
|
||||
let maxId = self.peers.last?.peerId.id
|
||||
let peerId = self.peerId
|
||||
let accountPeerId = self.account.peerId
|
||||
let network = self.account.network
|
||||
let postbox = self.account.postbox
|
||||
|
|
@ -93,7 +120,6 @@ private final class GroupsInCommonContextImpl {
|
|||
count = nil
|
||||
}
|
||||
|
||||
|
||||
return postbox.transaction { transaction -> ([Peer], Int) in
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||
|
|
@ -122,6 +148,14 @@ private final class GroupsInCommonContextImpl {
|
|||
}
|
||||
}
|
||||
|
||||
if maxId == nil {
|
||||
strongSelf.cacheDisposable.set(postbox.transaction { transaction in
|
||||
if let entry = CodableEntry(CachedGroupsInCommon(peerIds: peers.map { $0.id }, count: Int32(count))) {
|
||||
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
|
||||
}
|
||||
}.start())
|
||||
}
|
||||
|
||||
let updatedCount = max(strongSelf.peers.count, count)
|
||||
strongSelf.count = updatedCount
|
||||
strongSelf.dataState = .ready(canLoadMore: count != 0 && updatedCount > strongSelf.peers.count)
|
||||
|
|
@ -166,3 +200,38 @@ public final class GroupsInCommonContext {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CachedGroupsInCommon: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case peerIds
|
||||
case count
|
||||
}
|
||||
|
||||
var peerIds: [PeerId]
|
||||
let count: Int32
|
||||
|
||||
init(peerIds: [PeerId], count: Int32) {
|
||||
self.peerIds = peerIds
|
||||
self.count = count
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.peerIds = try container.decode([PeerId].self, forKey: .peerIds)
|
||||
self.count = try container.decode(Int32.self, forKey: .count)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(self.peerIds, forKey: .peerIds)
|
||||
try container.encode(self.count, forKey: .count)
|
||||
}
|
||||
}
|
||||
|
||||
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
|
||||
let cacheKey = ValueBoxKey(length: 8)
|
||||
cacheKey.setInt64(0, value: peerId.toInt64())
|
||||
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedGroupsInCommon, key: cacheKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ public enum PresentationResourceKey: Int32 {
|
|||
case itemListTopicArrowIcon
|
||||
case itemListAddBoostsIcon
|
||||
case itemListPremiumIcon
|
||||
case itemListRoundTopupIcon
|
||||
case itemListRoundWithdrawIcon
|
||||
|
||||
case statsReactionsIcon
|
||||
case statsForwardsIcon
|
||||
|
|
@ -314,6 +316,7 @@ public enum PresentationResourceKey: Int32 {
|
|||
|
||||
case chatEmptyStateStarIcon
|
||||
case chatPlaceholderStarIcon
|
||||
case chatUserInfoWarningIcon
|
||||
|
||||
case avatarPremiumLockBadgeBackground
|
||||
case avatarPremiumLockBadge
|
||||
|
|
|
|||
|
|
@ -1352,6 +1352,25 @@ public struct PresentationResourcesChat {
|
|||
})
|
||||
}
|
||||
|
||||
public static func chatUserInfoWarningIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatUserInfoWarningIcon.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 14.0, height: 14.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: .zero, size: size))
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
|
||||
let width: CGFloat = 2.0 - UIScreenPixel
|
||||
let height: CGFloat = 7.0 - UIScreenPixel
|
||||
context.addPath(CGPath(roundedRect: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - width) / 2.0), y: 2.0 + UIScreenPixel), size: CGSize(width: width, height: height)), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
|
||||
context.addPath(CGPath(ellipseIn: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - width) / 2.0), y: 2.0 + UIScreenPixel + height + 1.0 - UIScreenPixel), size: CGSize(width: width, height: width)), transform: nil))
|
||||
context.fillPath()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatPlaceholderStarIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatPlaceholderStarIcon.rawValue, { theme in
|
||||
if let image = UIImage(bundleImageName: "Premium/Stars/ButtonStar") {
|
||||
|
|
|
|||
|
|
@ -432,4 +432,37 @@ public struct PresentationResourcesItemList {
|
|||
return generateTintedImage(image: UIImage(bundleImageName: "Item List/ExpandingItemVerticalSmallArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
public static func itemListRoundTopupIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListRoundTopupIcon.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 16.0, height: 18.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.translateBy(x: 0.0, y: 2.0 - UIScreenPixel)
|
||||
context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.addPath(CGPath(roundedRect: CGRect(x: 7.0, y: 3.0, width: 2.0, height: 10.0), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
|
||||
context.addPath(CGPath(roundedRect: CGRect(x: 3.0, y: 7.0, width: 10.0, height: 2.0), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
|
||||
context.fillPath()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func itemListRoundWithdrawIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListRoundWithdrawIcon.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 16.0, height: 18.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.translateBy(x: 0.0, y: 2.0 - UIScreenPixel)
|
||||
context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.addPath(CGPath(roundedRect: CGRect(x: 3.0, y: 7.0, width: 10.0, height: 2.0), cornerWidth: 1.0, cornerHeight: 1.0, transform: nil))
|
||||
context.fillPath()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -659,3 +659,15 @@ public func stringForRemainingMuteInterval(strings: PresentationStrings, muteInt
|
|||
return strings.MuteExpires_Days(Int32(round(Float(value) / (24 * 60 * 60))))
|
||||
}
|
||||
}
|
||||
|
||||
public func stringForIntervalSinceUpdateAction(strings: PresentationStrings, value: Int32) -> String {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let value = max(1 * 60, timestamp - value)
|
||||
if value <= 1 * 60 * 60 {
|
||||
return strings.Chat_NonContactUser_UpdatedMinutes(Int32(round(Float(value) / 60)))
|
||||
} else if value <= 24 * 60 * 60 {
|
||||
return strings.Chat_NonContactUser_UpdatedHours(Int32(round(Float(value) / (60 * 60))))
|
||||
} else {
|
||||
return strings.Chat_NonContactUser_UpdatedDays(Int32(round(Float(value) / (24 * 60 * 60))))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1283,8 +1283,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||
case .suggest:
|
||||
buttonText = strings.AvatarEditor_SuggestProfilePhoto
|
||||
case .user:
|
||||
//TODO:localize
|
||||
buttonText = "Set My Photo" //strings.AvatarEditor_SetProfilePhoto
|
||||
buttonText = strings.AvatarEditor_SetProfilePhoto
|
||||
case .group, .forum:
|
||||
buttonText = strings.AvatarEditor_SetGroupPhoto
|
||||
case .channel:
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public struct ChatMessageEntryAttributes: Equatable {
|
|||
|
||||
public enum ChatInfoData: Equatable {
|
||||
case botInfo(title: String, text: String, photo: TelegramMediaImage?, video: TelegramMediaFile?)
|
||||
case userInfo(title: String, registrationDate: String?, phoneCountry: String?, locationCountry: String?, groupsInCommon: [EnginePeer])
|
||||
case userInfo(peer: EnginePeer, verification: PeerVerification?, registrationDate: String?, phoneCountry: String?, groupsInCommonCount: Int32)
|
||||
}
|
||||
|
||||
public enum ChatHistoryEntry: Identifiable, Comparable {
|
||||
|
|
|
|||
|
|
@ -4597,6 +4597,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
}
|
||||
}
|
||||
}
|
||||
if item.message.timestamp < 10 {
|
||||
hasMenuGesture = false
|
||||
}
|
||||
strongSelf.mainContainerNode.isGestureEnabled = hasMenuGesture
|
||||
for contentContainer in strongSelf.contentContainers {
|
||||
contentContainer.containerNode.isGestureEnabled = hasMenuGesture
|
||||
|
|
|
|||
|
|
@ -424,17 +424,9 @@ public func chatMessagePaymentAlertController(
|
|||
var completionImpl: (() -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
//TODO:localize
|
||||
let title = "Confirm Payment"
|
||||
let actionTitle: String
|
||||
let messagesString: String
|
||||
if count > 1 {
|
||||
messagesString = "**\(count)** messages"
|
||||
actionTitle = "Pay for \(count) Messages"
|
||||
} else {
|
||||
messagesString = "**\(count)** message"
|
||||
actionTitle = "Pay for 1 Message"
|
||||
}
|
||||
let title = presentationData.strings.Chat_PaidMessage_Confirm_Title
|
||||
let actionTitle = presentationData.strings.Chat_PaidMessage_Confirm_PayForMessage(count)
|
||||
let messagesString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Messages(count)
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: actionTitle, action: {
|
||||
completionImpl?()
|
||||
|
|
@ -442,17 +434,20 @@ public func chatMessagePaymentAlertController(
|
|||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
|
||||
|
||||
let text: String
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
text = "**\(peer.compactDisplayTitle)** charges **\(amount.value) Stars** per incoming message. Would you like to pay **\(amount.value * Int64(count)) Stars** to send \(messagesString)?"
|
||||
let amountString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value))
|
||||
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value * Int64(count)))
|
||||
text = presentationData.strings.Chat_PaidMessage_Confirm_Single_Text(peer.compactDisplayTitle, amountString, totalString, messagesString).string
|
||||
} else {
|
||||
let amount = totalAmount ?? amount
|
||||
text = "You selected **\(peers.count)** users who charge Stars for messages. Would you like to pay **\(amount.value)** Stars to send \(messagesString)?"
|
||||
let usersString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Users(Int32(peers.count))
|
||||
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value * Int64(count)))
|
||||
text = presentationData.strings.Chat_PaidMessage_Confirm_Multiple_Text(usersString, totalString, messagesString).string
|
||||
}
|
||||
|
||||
let optionText = hasCheck ? "Don't ask again" : nil
|
||||
let optionText = hasCheck ? presentationData.strings.Chat_PaidMessage_Confirm_DontAskAgain : nil
|
||||
|
||||
let contentNode = ChatMessagePaymentAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, optionText: optionText, actions: actions, alignment: .vertical)
|
||||
|
||||
|
|
@ -470,8 +465,6 @@ public func chatMessagePaymentAlertController(
|
|||
return controller
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func chatMessageRemovePaymentAlertController(
|
||||
context: AccountContext? = nil,
|
||||
presentationData: PresentationData,
|
||||
|
|
@ -488,20 +481,19 @@ public func chatMessageRemovePaymentAlertController(
|
|||
var completionImpl: (() -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
//TODO:localize
|
||||
let actions: [TextAlertAction] = [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: "Yes", action: {
|
||||
TextAlertAction(type: .defaultAction, title: strings.Chat_PaidMessage_RemoveFee_Yes, action: {
|
||||
completionImpl?()
|
||||
dismissImpl?()
|
||||
})
|
||||
]
|
||||
|
||||
let title = "Remove Fee"
|
||||
let text = "Are you sure you want to allow **\(peer.compactDisplayTitle)** to message you for free?"
|
||||
let optionText = amount.flatMap { "Refund already paid **\($0.value) Stars**" }
|
||||
let title = strings.Chat_PaidMessage_RemoveFee_Title
|
||||
let text = strings.Chat_PaidMessage_RemoveFee_Text(peer.compactDisplayTitle).string
|
||||
let optionText = amount.flatMap { strings.Chat_PaidMessage_RemoveFee_Refund(strings.Chat_PaidMessage_RemoveFee_Refund_Stars(Int32($0.value))).string }
|
||||
|
||||
let contentNode = ChatMessagePaymentAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, optionText: optionText, actions: actions, alignment: .horizontal)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import Foundation
|
|||
import UIKit
|
||||
|
||||
public enum ChatNavigationButtonAction: Equatable {
|
||||
case openChatInfo(expandAvatar: Bool, recommendedChannels: Bool)
|
||||
public enum ChatInfoSection {
|
||||
case groupsInCommon
|
||||
case recommendedChannels
|
||||
}
|
||||
case openChatInfo(expandAvatar: Bool, section: ChatInfoSection?)
|
||||
case clearHistory
|
||||
case clearCache
|
||||
case cancelMessageSelection
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
|
||||
"//submodules/CountrySelectionUI",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -10,39 +10,40 @@ import TextFormat
|
|||
import UrlEscaping
|
||||
import PhotoResources
|
||||
import AccountContext
|
||||
import UniversalMediaPlayer
|
||||
import TelegramUniversalVideoContent
|
||||
import WallpaperBackgroundNode
|
||||
import ChatControllerInteraction
|
||||
import ChatMessageBubbleContentNode
|
||||
import CountrySelectionUI
|
||||
import TelegramStringFormatting
|
||||
import MergedAvatarsNode
|
||||
import ChatControllerInteraction
|
||||
import TextNodeWithEntities
|
||||
|
||||
public final class ChatUserInfoItem: ListViewItem {
|
||||
fileprivate let title: String
|
||||
fileprivate let peer: EnginePeer
|
||||
fileprivate let verification: PeerVerification?
|
||||
fileprivate let registrationDate: String?
|
||||
fileprivate let phoneCountry: String?
|
||||
fileprivate let locationCountry: String?
|
||||
fileprivate let groupsInCommon: [EnginePeer]
|
||||
fileprivate let groupsInCommonCount: Int32
|
||||
fileprivate let controllerInteraction: ChatControllerInteraction
|
||||
fileprivate let presentationData: ChatPresentationData
|
||||
fileprivate let context: AccountContext
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
peer: EnginePeer,
|
||||
verification: PeerVerification?,
|
||||
registrationDate: String?,
|
||||
phoneCountry: String?,
|
||||
locationCountry: String?,
|
||||
groupsInCommon: [EnginePeer],
|
||||
groupsInCommonCount: Int32,
|
||||
controllerInteraction: ChatControllerInteraction,
|
||||
presentationData: ChatPresentationData,
|
||||
context: AccountContext
|
||||
) {
|
||||
self.title = title
|
||||
self.peer = peer
|
||||
self.verification = verification
|
||||
self.registrationDate = registrationDate
|
||||
self.phoneCountry = phoneCountry
|
||||
self.locationCountry = locationCountry
|
||||
self.groupsInCommon = groupsInCommon
|
||||
self.groupsInCommonCount = groupsInCommonCount
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.presentationData = presentationData
|
||||
self.context = context
|
||||
|
|
@ -106,11 +107,17 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
private let phoneCountryValueTextNode: TextNode
|
||||
private var phoneCountryText: String?
|
||||
|
||||
private let locationCountryTitleTextNode: TextNode
|
||||
private let locationCountryValueTextNode: TextNode
|
||||
private var locationCountryText: String?
|
||||
private let groupsTitleTextNode: TextNode
|
||||
private let groupsValueTextNode: TextNode
|
||||
private let groupsButtonNode: HighlightTrackingButtonNode
|
||||
private let groupsAvatarsNode: MergedAvatarsNode
|
||||
private let groupsArrowNode: ASImageNode
|
||||
|
||||
private let groupsTextNode: TextNode
|
||||
private var groupsInCommonContext: GroupsInCommonContext?
|
||||
private var groupsInCommonDisposable: Disposable?
|
||||
private var groupsInCommon: [Peer] = []
|
||||
|
||||
private let disclaimerTextNode: TextNodeWithEntities
|
||||
|
||||
private var theme: ChatPresentationThemeData?
|
||||
|
||||
|
|
@ -146,16 +153,23 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
self.phoneCountryValueTextNode.isUserInteractionEnabled = false
|
||||
self.phoneCountryValueTextNode.displaysAsynchronously = false
|
||||
|
||||
self.locationCountryTitleTextNode = TextNode()
|
||||
self.locationCountryTitleTextNode.isUserInteractionEnabled = false
|
||||
self.locationCountryTitleTextNode.displaysAsynchronously = false
|
||||
self.locationCountryValueTextNode = TextNode()
|
||||
self.locationCountryValueTextNode.isUserInteractionEnabled = false
|
||||
self.locationCountryValueTextNode.displaysAsynchronously = false
|
||||
self.groupsTitleTextNode = TextNode()
|
||||
self.groupsTitleTextNode.isUserInteractionEnabled = false
|
||||
self.groupsTitleTextNode.displaysAsynchronously = false
|
||||
self.groupsValueTextNode = TextNode()
|
||||
self.groupsValueTextNode.isUserInteractionEnabled = false
|
||||
self.groupsValueTextNode.displaysAsynchronously = false
|
||||
|
||||
self.groupsTextNode = TextNode()
|
||||
self.groupsTextNode.isUserInteractionEnabled = false
|
||||
self.groupsTextNode.displaysAsynchronously = false
|
||||
self.groupsAvatarsNode = MergedAvatarsNode()
|
||||
|
||||
self.groupsArrowNode = ASImageNode()
|
||||
self.groupsArrowNode.displaysAsynchronously = false
|
||||
|
||||
self.groupsButtonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.disclaimerTextNode = TextNodeWithEntities()
|
||||
self.disclaimerTextNode.textNode.isUserInteractionEnabled = false
|
||||
self.disclaimerTextNode.textNode.displaysAsynchronously = false
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: true, rotated: true)
|
||||
|
||||
|
|
@ -164,37 +178,38 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
self.addSubnode(self.offsetContainer)
|
||||
self.offsetContainer.addSubnode(self.titleNode)
|
||||
self.offsetContainer.addSubnode(self.subtitleNode)
|
||||
self.offsetContainer.addSubnode(self.groupsTextNode)
|
||||
self.offsetContainer.addSubnode(self.disclaimerTextNode.textNode)
|
||||
self.offsetContainer.addSubnode(self.groupsAvatarsNode)
|
||||
self.offsetContainer.addSubnode(self.groupsArrowNode)
|
||||
self.offsetContainer.addSubnode(self.groupsButtonNode)
|
||||
self.wantsTrailingItemSpaceUpdates = true
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
// let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
// recognizer.tapActionAtPoint = { [weak self] point in
|
||||
// if let strongSelf = self {
|
||||
// let tapAction = strongSelf.tapActionAtPoint(point, gesture: .tap, isEstimating: true)
|
||||
// switch tapAction.content {
|
||||
// case .none:
|
||||
// break
|
||||
// case .ignore:
|
||||
// return .fail
|
||||
// case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom:
|
||||
// return .waitForSingleTap
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return .waitForDoubleTap
|
||||
// }
|
||||
// recognizer.highlight = { [weak self] point in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.updateTouchesAtPoint(point)
|
||||
// }
|
||||
// }
|
||||
// self.view.addGestureRecognizer(recognizer)
|
||||
self.groupsButtonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let self {
|
||||
if highlighted {
|
||||
self.groupsValueTextNode.layer.removeAnimation(forKey: "opacity")
|
||||
self.groupsValueTextNode.alpha = 0.4
|
||||
|
||||
self.groupsAvatarsNode.layer.removeAnimation(forKey: "opacity")
|
||||
self.groupsAvatarsNode.alpha = 0.4
|
||||
|
||||
self.groupsArrowNode.layer.removeAnimation(forKey: "opacity")
|
||||
self.groupsArrowNode.alpha = 0.4
|
||||
} else {
|
||||
self.groupsValueTextNode.alpha = 1.0
|
||||
self.groupsValueTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
self.groupsAvatarsNode.alpha = 1.0
|
||||
self.groupsAvatarsNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
self.groupsArrowNode.alpha = 1.0
|
||||
self.groupsArrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.groupsButtonNode.addTarget(self, action: #selector(self.groupsPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
|
||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
super.updateAbsoluteRect(rect, within: containerSize)
|
||||
|
||||
|
|
@ -207,6 +222,13 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func groupsPressed() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.openPeer(item.peer, .info(ChatControllerInteractionNavigateToPeer.InfoParams(switchToGroupsInCommon: true)), nil, .default)
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ChatUserInfoItem, _ width: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||
|
|
@ -214,17 +236,17 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
let makeRegistrationDateValueLayout = TextNode.asyncLayout(self.registrationDateValueTextNode)
|
||||
let makePhoneCountryTitleLayout = TextNode.asyncLayout(self.phoneCountryTitleTextNode)
|
||||
let makePhoneCountryValueLayout = TextNode.asyncLayout(self.phoneCountryValueTextNode)
|
||||
let makeLocationCountryTitleLayout = TextNode.asyncLayout(self.locationCountryTitleTextNode)
|
||||
let makeLocationCountryValueLayout = TextNode.asyncLayout(self.locationCountryValueTextNode)
|
||||
let makeGroupsLayout = TextNode.asyncLayout(self.groupsTextNode)
|
||||
|
||||
let makeGroupsTitleLayout = TextNode.asyncLayout(self.groupsTitleTextNode)
|
||||
let makeGroupsValueLayout = TextNode.asyncLayout(self.groupsValueTextNode)
|
||||
let makeDisclaimerLayout = TextNodeWithEntities.asyncLayout(self.disclaimerTextNode)
|
||||
|
||||
let currentItem = self.item
|
||||
let currentRegistrationDateText = self.registrationDateText
|
||||
let currentPhoneCountryText = self.phoneCountryText
|
||||
let currentLocationCountryText = self.locationCountryText
|
||||
|
||||
return { [weak self] item, params in
|
||||
self?.item = item
|
||||
|
||||
let themeUpdated = item.presentationData.theme !== currentItem?.presentationData.theme
|
||||
|
||||
var backgroundSize = CGSize(width: 240.0, height: 0.0)
|
||||
|
||||
let verticalItemInset: CGFloat = 10.0
|
||||
|
|
@ -239,13 +261,13 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
let subtitleColor = primaryTextColor.withAlphaComponent(item.presentationData.theme.theme.overallDarkAppearance ? 0.7 : 0.8)
|
||||
|
||||
backgroundSize.height += verticalInset
|
||||
//TODO:localize
|
||||
|
||||
let constrainedWidth = params.width - (horizontalInset + horizontalContentInset) * 2.0
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: Font.semibold(15.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += titleLayout.size.height
|
||||
backgroundSize.height += verticalSpacing
|
||||
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Not a contact", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_Subtitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += subtitleLayout.size.height
|
||||
backgroundSize.height += verticalSpacing + paragraphSpacing
|
||||
|
||||
|
|
@ -254,6 +276,36 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
var maxTitleWidth: CGFloat = 0.0
|
||||
var maxValueWidth: CGFloat = 0.0
|
||||
|
||||
var phoneCountryText: String?
|
||||
let phoneCountryTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
let phoneCountryValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
if let phoneCountry = item.phoneCountry {
|
||||
if let currentPhoneCountryText {
|
||||
phoneCountryText = currentPhoneCountryText
|
||||
} else {
|
||||
var countryName = ""
|
||||
let countriesConfiguration = item.context.currentCountriesConfiguration.with { $0 }
|
||||
if let country = countriesConfiguration.countries.first(where: { $0.id == phoneCountry }) {
|
||||
countryName = country.localizedName ?? country.name
|
||||
} else if phoneCountry == "FT" {
|
||||
countryName = item.presentationData.strings.Chat_NonContactUser_AnonymousNumber
|
||||
} else if phoneCountry == "TS" {
|
||||
countryName = "Test"
|
||||
}
|
||||
phoneCountryText = emojiFlagForISOCountryCode(phoneCountry) + " " + countryName
|
||||
}
|
||||
phoneCountryTitleLayoutAndApply = makePhoneCountryTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_PhoneNumber, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
phoneCountryValueLayoutAndApply = makePhoneCountryValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: phoneCountryText ?? "", font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += verticalSpacing
|
||||
backgroundSize.height += phoneCountryValueLayoutAndApply?.0.size.height ?? 0
|
||||
|
||||
maxTitleWidth = max(maxTitleWidth, (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0))
|
||||
maxValueWidth = max(maxValueWidth, (phoneCountryValueLayoutAndApply?.0.size.width ?? 0))
|
||||
} else {
|
||||
phoneCountryTitleLayoutAndApply = nil
|
||||
phoneCountryValueLayoutAndApply = nil
|
||||
}
|
||||
|
||||
var registrationDateText: String?
|
||||
let registrationDateTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
let registrationDateValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
|
|
@ -270,7 +322,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
registrationDateText = ""
|
||||
}
|
||||
}
|
||||
registrationDateTitleLayoutAndApply = makeRegistrationDateTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Registration", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
registrationDateTitleLayoutAndApply = makeRegistrationDateTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_Registration, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
registrationDateValueLayoutAndApply = makeRegistrationDateValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: registrationDateText ?? "", font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += verticalSpacing
|
||||
backgroundSize.height += registrationDateValueLayoutAndApply?.0.size.height ?? 0
|
||||
|
|
@ -282,63 +334,57 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
registrationDateValueLayoutAndApply = nil
|
||||
}
|
||||
|
||||
var phoneCountryText: String?
|
||||
let phoneCountryTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
let phoneCountryValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
if let phoneCountry = item.phoneCountry {
|
||||
if let currentPhoneCountryText {
|
||||
phoneCountryText = currentPhoneCountryText
|
||||
} else {
|
||||
var countryName = ""
|
||||
let countriesConfiguration = item.context.currentCountriesConfiguration.with { $0 }
|
||||
if let country = countriesConfiguration.countries.first(where: { $0.id == phoneCountry }) {
|
||||
countryName = country.localizedName ?? country.name
|
||||
}
|
||||
phoneCountryText = emojiFlagForISOCountryCode(phoneCountry) + " " + countryName
|
||||
}
|
||||
phoneCountryTitleLayoutAndApply = makePhoneCountryTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Phone Number", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
phoneCountryValueLayoutAndApply = makePhoneCountryValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: phoneCountryText ?? "", font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += verticalSpacing
|
||||
backgroundSize.height += phoneCountryValueLayoutAndApply?.0.size.height ?? 0
|
||||
|
||||
maxTitleWidth = max(maxTitleWidth, (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0))
|
||||
maxValueWidth = max(maxValueWidth, (phoneCountryValueLayoutAndApply?.0.size.width ?? 0))
|
||||
let avatarImageSize: CGFloat = 18.0
|
||||
let avatarSpacing: CGFloat = 9.0
|
||||
let avatarBorder: CGFloat = 1.0
|
||||
|
||||
let groupsValueText: NSMutableAttributedString
|
||||
let groupsInCommonCount = item.groupsInCommonCount
|
||||
var estimatedValueOffset: CGFloat = 0.0
|
||||
if groupsInCommonCount > 0 {
|
||||
groupsValueText = NSMutableAttributedString(string: item.presentationData.strings.Chat_NonContactUser_GroupsCount(groupsInCommonCount), font: Font.semibold(13.0), textColor: primaryTextColor)
|
||||
estimatedValueOffset = avatarImageSize + CGFloat(min(2, max(0, item.groupsInCommonCount - 1))) * avatarSpacing + 4.0
|
||||
} else {
|
||||
phoneCountryTitleLayoutAndApply = nil
|
||||
phoneCountryValueLayoutAndApply = nil
|
||||
groupsValueText = NSMutableAttributedString(string: "", font: Font.semibold(13.0), textColor: primaryTextColor)
|
||||
}
|
||||
|
||||
let groupsTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
let groupsValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
if !groupsValueText.string.isEmpty {
|
||||
groupsTitleLayoutAndApply = makeGroupsTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_Groups, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
groupsValueLayoutAndApply = makeGroupsValueLayout(TextNodeLayoutArguments(attributedString: groupsValueText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += verticalSpacing
|
||||
backgroundSize.height += groupsValueLayoutAndApply?.0.size.height ?? 0.0
|
||||
|
||||
maxTitleWidth = max(maxTitleWidth, groupsTitleLayoutAndApply?.0.size.width ?? 0)
|
||||
maxValueWidth = max(maxValueWidth, groupsValueLayoutAndApply?.0.size.width ?? 0 + estimatedValueOffset)
|
||||
} else {
|
||||
groupsTitleLayoutAndApply = nil
|
||||
groupsValueLayoutAndApply = nil
|
||||
}
|
||||
|
||||
backgroundSize.width = horizontalContentInset * 2.0 + maxTitleWidth + attributeSpacing + maxValueWidth
|
||||
|
||||
let disclaimerText: NSMutableAttributedString
|
||||
if let verification = item.verification {
|
||||
disclaimerText = NSMutableAttributedString(string: " # \(verification.description)", font: Font.regular(13.0), textColor: subtitleColor)
|
||||
if let range = disclaimerText.string.range(of: "#") {
|
||||
disclaimerText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSRange(range, in: disclaimerText.string))
|
||||
disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string))
|
||||
disclaimerText.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: disclaimerText.string))
|
||||
}
|
||||
} else {
|
||||
disclaimerText = NSMutableAttributedString(string: " # \(item.presentationData.strings.Chat_NonContactUser_Disclaimer)", font: Font.regular(13.0), textColor: subtitleColor)
|
||||
if let range = disclaimerText.string.range(of: "#") {
|
||||
disclaimerText.addAttribute(.attachment, value: PresentationResourcesChat.chatUserInfoWarningIcon(item.presentationData.theme.theme)!, range: NSRange(range, in: disclaimerText.string))
|
||||
disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string))
|
||||
disclaimerText.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: disclaimerText.string))
|
||||
}
|
||||
}
|
||||
|
||||
var locationCountryText: String?
|
||||
let locationCountryTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
let locationCountryValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
if let locationCountry = item.locationCountry {
|
||||
if let currentLocationCountryText {
|
||||
locationCountryText = currentLocationCountryText
|
||||
} else {
|
||||
var countryName = ""
|
||||
let countriesConfiguration = item.context.currentCountriesConfiguration.with { $0 }
|
||||
if let country = countriesConfiguration.countries.first(where: { $0.id == locationCountry }) {
|
||||
countryName = country.localizedName ?? country.name
|
||||
}
|
||||
locationCountryText = emojiFlagForISOCountryCode(locationCountry) + " " + countryName
|
||||
}
|
||||
locationCountryTitleLayoutAndApply = makeLocationCountryTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Location", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
locationCountryValueLayoutAndApply = makeLocationCountryValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: locationCountryText ?? "", font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += verticalSpacing
|
||||
backgroundSize.height += locationCountryValueLayoutAndApply?.0.size.height ?? 0
|
||||
|
||||
maxTitleWidth = max(maxTitleWidth, (locationCountryTitleLayoutAndApply?.0.size.width ?? 0))
|
||||
maxValueWidth = max(maxValueWidth, (locationCountryValueLayoutAndApply?.0.size.width ?? 0))
|
||||
} else {
|
||||
locationCountryTitleLayoutAndApply = nil
|
||||
locationCountryValueLayoutAndApply = nil
|
||||
}
|
||||
|
||||
backgroundSize.width = horizontalContentInset * 3.0 + maxTitleWidth + attributeSpacing + maxValueWidth
|
||||
|
||||
let (groupsLayout, groupsApply) = makeGroupsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No groups in common", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (disclaimerLayout, disclaimerApply) = makeDisclaimerLayout(TextNodeLayoutArguments(attributedString: disclaimerText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: backgroundSize.width - horizontalContentInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
backgroundSize.height += verticalSpacing * 2.0 + paragraphSpacing
|
||||
backgroundSize.height += groupsLayout.size.height
|
||||
backgroundSize.height += disclaimerLayout.size.height
|
||||
|
||||
backgroundSize.height += verticalInset
|
||||
|
||||
|
|
@ -347,25 +393,44 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: backgroundSize.height + verticalItemInset * 2.0), insets: UIEdgeInsets())
|
||||
return (itemLayout, { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.theme = item.presentationData.theme
|
||||
|
||||
if themeUpdated {
|
||||
strongSelf.groupsArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: primaryTextColor)
|
||||
}
|
||||
|
||||
if item.groupsInCommonCount > 0 {
|
||||
if strongSelf.groupsInCommonContext == nil {
|
||||
let groupsInCommonContext = GroupsInCommonContext(account: item.context.account, peerId: item.peer.id)
|
||||
strongSelf.groupsInCommonContext = groupsInCommonContext
|
||||
strongSelf.groupsInCommonDisposable = (groupsInCommonContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self, let item = self.item else {
|
||||
return
|
||||
}
|
||||
self.groupsInCommon = Array(state.peers.compactMap { $0.peer }.prefix(3))
|
||||
self.groupsAvatarsNode.update(context: item.context, peers: self.groupsInCommon, synchronousLoad: true, imageSize: avatarImageSize, imageSpacing: avatarSpacing, borderWidth: avatarBorder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if item.presentationData.theme.theme.overallDarkAppearance {
|
||||
strongSelf.registrationDateTitleTextNode.layer.compositingFilter = nil
|
||||
strongSelf.phoneCountryTitleTextNode.layer.compositingFilter = nil
|
||||
strongSelf.locationCountryTitleTextNode.layer.compositingFilter = nil
|
||||
strongSelf.groupsTitleTextNode.layer.compositingFilter = nil
|
||||
strongSelf.subtitleNode.layer.compositingFilter = nil
|
||||
strongSelf.groupsTextNode.layer.compositingFilter = nil
|
||||
strongSelf.disclaimerTextNode.textNode.layer.compositingFilter = nil
|
||||
} else {
|
||||
strongSelf.registrationDateTitleTextNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.phoneCountryTitleTextNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.locationCountryTitleTextNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.groupsTitleTextNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.subtitleNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.groupsTextNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.disclaimerTextNode.textNode.layer.compositingFilter = "overlayBlendMode"
|
||||
}
|
||||
|
||||
strongSelf.registrationDateText = registrationDateText
|
||||
strongSelf.phoneCountryText = phoneCountryText
|
||||
strongSelf.locationCountryText = locationCountryText
|
||||
|
||||
strongSelf.controllerInteraction = item.controllerInteraction
|
||||
|
||||
|
|
@ -387,25 +452,24 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
var attributeMidpoints: [CGFloat] = []
|
||||
|
||||
func appendAttributeMidpoint(titleLayout: TextNodeLayout?, valueLayout: TextNodeLayout?) {
|
||||
if let titleLayout, let valueLayout {
|
||||
let totalWidth = titleLayout.size.width + attributeSpacing + valueLayout.size.width
|
||||
let titleOffset = titleLayout.size.width + attributeSpacing / 2.0
|
||||
let midpoint = (backgroundSize.width - totalWidth) / 2.0 + titleOffset
|
||||
if let valueLayout {
|
||||
let midpoint = backgroundSize.width - horizontalContentInset - valueLayout.size.width - attributeSpacing / 2.0
|
||||
attributeMidpoints.append(midpoint)
|
||||
}
|
||||
}
|
||||
appendAttributeMidpoint(titleLayout: registrationDateTitleLayoutAndApply?.0, valueLayout: registrationDateValueLayoutAndApply?.0)
|
||||
appendAttributeMidpoint(titleLayout: phoneCountryTitleLayoutAndApply?.0, valueLayout: phoneCountryValueLayoutAndApply?.0)
|
||||
appendAttributeMidpoint(titleLayout: locationCountryTitleLayoutAndApply?.0, valueLayout: locationCountryValueLayoutAndApply?.0)
|
||||
|
||||
let middleX = floorToScreenPixels(attributeMidpoints.isEmpty ? backgroundSize.width / 2.0 : attributeMidpoints.reduce(0, +) / CGFloat(attributeMidpoints.count))
|
||||
appendAttributeMidpoint(titleLayout: registrationDateTitleLayoutAndApply?.0, valueLayout: registrationDateValueLayoutAndApply?.0)
|
||||
appendAttributeMidpoint(titleLayout: groupsTitleLayoutAndApply?.0, valueLayout: groupsValueLayoutAndApply?.0)
|
||||
|
||||
let middleX = floorToScreenPixels(attributeMidpoints.min() ?? backgroundSize.width / 2.0)
|
||||
|
||||
let titleMaxX: CGFloat = backgroundFrame.minX + middleX - attributeSpacing / 2.0
|
||||
let valueMinX: CGFloat = backgroundFrame.minX + middleX + attributeSpacing / 2.0
|
||||
|
||||
func positionAttributeNodes(
|
||||
titleTextNode: TextNode,
|
||||
valueTextNode: TextNode,
|
||||
valueOffset: CGFloat = 0.0,
|
||||
titleLayoutAndApply: (TextNodeLayout, () -> TextNode)?,
|
||||
valueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
) {
|
||||
|
|
@ -425,19 +489,13 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
}
|
||||
let _ = valueApply()
|
||||
valueTextNode.frame = CGRect(
|
||||
origin: CGPoint(x: valueMinX, y: contentOriginY),
|
||||
origin: CGPoint(x: valueMinX + valueOffset, y: contentOriginY),
|
||||
size: valueLayout.size
|
||||
)
|
||||
contentOriginY += valueLayout.size.height + verticalSpacing
|
||||
}
|
||||
}
|
||||
|
||||
positionAttributeNodes(
|
||||
titleTextNode: strongSelf.registrationDateTitleTextNode,
|
||||
valueTextNode: strongSelf.registrationDateValueTextNode,
|
||||
titleLayoutAndApply: registrationDateTitleLayoutAndApply,
|
||||
valueLayoutAndApply: registrationDateValueLayoutAndApply
|
||||
)
|
||||
positionAttributeNodes(
|
||||
titleTextNode: strongSelf.phoneCountryTitleTextNode,
|
||||
valueTextNode: strongSelf.phoneCountryValueTextNode,
|
||||
|
|
@ -445,16 +503,48 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
valueLayoutAndApply: phoneCountryValueLayoutAndApply
|
||||
)
|
||||
positionAttributeNodes(
|
||||
titleTextNode: strongSelf.locationCountryTitleTextNode,
|
||||
valueTextNode: strongSelf.locationCountryValueTextNode,
|
||||
titleLayoutAndApply: locationCountryTitleLayoutAndApply,
|
||||
valueLayoutAndApply: locationCountryValueLayoutAndApply
|
||||
titleTextNode: strongSelf.registrationDateTitleTextNode,
|
||||
valueTextNode: strongSelf.registrationDateValueTextNode,
|
||||
titleLayoutAndApply: registrationDateTitleLayoutAndApply,
|
||||
valueLayoutAndApply: registrationDateValueLayoutAndApply
|
||||
)
|
||||
|
||||
var valueOffset: CGFloat = 0.0
|
||||
if let groupsValueLayoutAndApply {
|
||||
let avatarsFrame = CGRect(origin: CGPoint(x: valueMinX + groupsValueLayoutAndApply.0.size.width + 4.0, y: contentOriginY + floor((groupsValueLayoutAndApply.0.size.height - avatarImageSize) / 2.0)), size: CGSize(width: avatarImageSize + avatarSpacing * 2.0, height: avatarImageSize))
|
||||
strongSelf.groupsAvatarsNode.frame = avatarsFrame
|
||||
strongSelf.groupsAvatarsNode.updateLayout(size: avatarsFrame.size)
|
||||
strongSelf.groupsAvatarsNode.update(context: item.context, peers: strongSelf.groupsInCommon, synchronousLoad: true, imageSize: avatarImageSize, imageSpacing: avatarSpacing, borderWidth: avatarBorder)
|
||||
|
||||
if groupsInCommonCount > 0 {
|
||||
valueOffset = avatarImageSize + CGFloat(min(2, max(0, groupsInCommonCount - 1))) * avatarSpacing + 4.0
|
||||
strongSelf.groupsButtonNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: contentOriginY), size: CGSize(width: groupsValueLayoutAndApply.0.size.width + 20.0, height: 18.0))
|
||||
|
||||
strongSelf.groupsButtonNode.isHidden = false
|
||||
strongSelf.groupsAvatarsNode.isHidden = false
|
||||
strongSelf.groupsArrowNode.isHidden = false
|
||||
|
||||
if let icon = strongSelf.groupsArrowNode.image {
|
||||
strongSelf.groupsArrowNode.frame = CGRect(origin: CGPoint(x: avatarsFrame.minX + valueOffset, y: contentOriginY + 4.0 - UIScreenPixel), size: icon.size)
|
||||
}
|
||||
} else {
|
||||
strongSelf.groupsAvatarsNode.isHidden = true
|
||||
strongSelf.groupsButtonNode.isHidden = true
|
||||
strongSelf.groupsArrowNode.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
positionAttributeNodes(
|
||||
titleTextNode: strongSelf.groupsTitleTextNode,
|
||||
valueTextNode: strongSelf.groupsValueTextNode,
|
||||
titleLayoutAndApply: groupsTitleLayoutAndApply,
|
||||
valueLayoutAndApply: groupsValueLayoutAndApply
|
||||
)
|
||||
|
||||
contentOriginY += verticalSpacing + paragraphSpacing
|
||||
let _ = groupsApply()
|
||||
let groupsFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + floor((backgroundSize.width - groupsLayout.size.width) / 2.0), y: contentOriginY), size: groupsLayout.size)
|
||||
strongSelf.groupsTextNode.frame = groupsFrame
|
||||
let _ = disclaimerApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: primaryTextColor.withMultipliedAlpha(0.4), attemptSynchronous: true))
|
||||
let disclaimerFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + floor((backgroundSize.width - disclaimerLayout.size.width) / 2.0), y: contentOriginY), size: disclaimerLayout.size)
|
||||
strongSelf.disclaimerTextNode.textNode.frame = disclaimerFrame
|
||||
|
||||
if strongSelf.backgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
backgroundContent.clipsToBounds = true
|
||||
|
|
@ -502,88 +592,4 @@ public final class ChatUserInfoItemNode: ListViewItemNode {
|
|||
let extra = self.offsetContainer.frame.contains(point)
|
||||
return result || extra
|
||||
}
|
||||
|
||||
// public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
// let textNodeFrame = self.textNode.frame
|
||||
// if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
|
||||
// if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
// var concealed = true
|
||||
// if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
// concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
// }
|
||||
// return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
// } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
// return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
// } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
// return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
// } else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
// return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
// } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
// return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
// } else {
|
||||
// return ChatMessageBubbleContentTapAction(content: .none)
|
||||
// }
|
||||
// } else {
|
||||
// return ChatMessageBubbleContentTapAction(content: .none)
|
||||
// }
|
||||
// }
|
||||
|
||||
// @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
// switch recognizer.state {
|
||||
// case .ended:
|
||||
// if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
// switch gesture {
|
||||
// case .tap:
|
||||
// let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
|
||||
// switch tapAction.content {
|
||||
// case .none, .ignore:
|
||||
// break
|
||||
// case let .url(url):
|
||||
// self.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, progress: tapAction.activate?()))
|
||||
// case let .peerMention(peerId, _, _):
|
||||
// if let item = self.item {
|
||||
// let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
// |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||
// if let peer = peer {
|
||||
// self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// case let .textMention(name):
|
||||
// self.item?.controllerInteraction.openPeerMention(name, tapAction.activate?())
|
||||
// case let .botCommand(command):
|
||||
// self.item?.controllerInteraction.sendBotCommand(nil, command)
|
||||
// case let .hashtag(peerName, hashtag):
|
||||
// self.item?.controllerInteraction.openHashtag(peerName, hashtag)
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// case .longTap, .doubleTap:
|
||||
// if let item = self.item, self.backgroundNode.frame.contains(location) {
|
||||
// let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
|
||||
// switch tapAction.content {
|
||||
// case .none, .ignore:
|
||||
// break
|
||||
// case let .url(url):
|
||||
// item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams())
|
||||
// case let .peerMention(peerId, mention, _):
|
||||
// item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams())
|
||||
// case let .textMention(name):
|
||||
// item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams())
|
||||
// case let .botCommand(command):
|
||||
// item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams())
|
||||
// case let .hashtag(_, hashtag):
|
||||
// item.controllerInteraction.longTap(.hashtag(hashtag), ChatControllerInteraction.LongTapParams())
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,11 @@ public final class GiftItemComponent: Component {
|
|||
let isHidden: Bool
|
||||
let isSoldOut: Bool
|
||||
let isSelected: Bool
|
||||
let isPinned: Bool
|
||||
let isEditing: Bool
|
||||
let mode: Mode
|
||||
let action: (() -> Void)?
|
||||
let contextAction: ((UIView, ContextGesture) -> Void)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
|
|
@ -120,7 +124,11 @@ public final class GiftItemComponent: Component {
|
|||
isHidden: Bool = false,
|
||||
isSoldOut: Bool = false,
|
||||
isSelected: Bool = false,
|
||||
mode: Mode = .generic
|
||||
isPinned: Bool = false,
|
||||
isEditing: Bool = false,
|
||||
mode: Mode = .generic,
|
||||
action: (() -> Void)? = nil,
|
||||
contextAction: ((UIView, ContextGesture) -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
|
|
@ -135,7 +143,11 @@ public final class GiftItemComponent: Component {
|
|||
self.isHidden = isHidden
|
||||
self.isSoldOut = isSoldOut
|
||||
self.isSelected = isSelected
|
||||
self.isPinned = isPinned
|
||||
self.isEditing = isEditing
|
||||
self.mode = mode
|
||||
self.action = action
|
||||
self.contextAction = contextAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: GiftItemComponent, rhs: GiftItemComponent) -> Bool {
|
||||
|
|
@ -178,16 +190,27 @@ public final class GiftItemComponent: Component {
|
|||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
if lhs.isPinned != rhs.isPinned {
|
||||
return false
|
||||
}
|
||||
if lhs.isEditing != rhs.isEditing {
|
||||
return false
|
||||
}
|
||||
if lhs.mode != rhs.mode {
|
||||
return false
|
||||
}
|
||||
if (lhs.contextAction == nil) != (rhs.contextAction == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
public final class View: ContextControllerSourceView {
|
||||
private var component: GiftItemComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
private let containerButton = HighlightTrackingButton()
|
||||
|
||||
private let backgroundLayer = SimpleLayer()
|
||||
private var loadingBackground: ComponentView<Empty>?
|
||||
|
||||
|
|
@ -207,8 +230,9 @@ public final class GiftItemComponent: Component {
|
|||
private var disposables = DisposableSet()
|
||||
private var fetchedFiles = Set<Int64>()
|
||||
|
||||
private var hiddenIconBackground: UIVisualEffectView?
|
||||
private var iconBackground: UIVisualEffectView?
|
||||
private var hiddenIcon: UIImageView?
|
||||
private var pinnedIcon: UIImageView?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
|
@ -219,6 +243,16 @@ public final class GiftItemComponent: Component {
|
|||
self.backgroundLayer.cornerCurve = .circular
|
||||
}
|
||||
self.backgroundLayer.masksToBounds = true
|
||||
|
||||
self.activated = { [weak self] gesture, _ in
|
||||
guard let self, let component = self.component else {
|
||||
gesture.cancel()
|
||||
return
|
||||
}
|
||||
component.contextAction?(self, gesture)
|
||||
}
|
||||
|
||||
self.containerButton.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -229,12 +263,18 @@ public final class GiftItemComponent: Component {
|
|||
self.disposables.dispose()
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.component?.action?()
|
||||
}
|
||||
|
||||
func update(component: GiftItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let isFirstTime = self.component == nil
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
self.isGestureEnabled = component.contextAction != nil
|
||||
|
||||
var themeUpdated = false
|
||||
if previousComponent?.theme !== component.theme {
|
||||
themeUpdated = true
|
||||
|
|
@ -590,7 +630,7 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if let peer = component.peer {
|
||||
if let peer = component.peer, !component.isPinned {
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
|
|
@ -608,6 +648,9 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
|
||||
avatarNode.frame = CGRect(origin: CGPoint(x: 5.0, y: 5.0), size: CGSize(width: 20.0, height: 20.0))
|
||||
} else if let avatarNode = self.avatarNode {
|
||||
self.avatarNode = nil
|
||||
avatarNode.view.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let backgroundColor, let _ = secondBackgroundColor {
|
||||
|
|
@ -617,50 +660,110 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
|
||||
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size))
|
||||
transition.setFrame(view: self.containerButton, frame: CGRect(origin: .zero, size: size))
|
||||
|
||||
if component.isHidden {
|
||||
let hiddenIconBackground: UIVisualEffectView
|
||||
let hiddenIcon: UIImageView
|
||||
if let currentBackground = self.hiddenIconBackground, let currentIcon = self.hiddenIcon {
|
||||
hiddenIconBackground = currentBackground
|
||||
hiddenIcon = currentIcon
|
||||
var iconBackgroundSize: CGSize?
|
||||
if component.isEditing {
|
||||
if !component.isPinned && backgroundColor != nil {
|
||||
iconBackgroundSize = CGSize(width: 48.0, height: 48.0)
|
||||
}
|
||||
} else {
|
||||
if component.isHidden {
|
||||
iconBackgroundSize = CGSize(width: 30.0, height: 30.0)
|
||||
}
|
||||
}
|
||||
|
||||
if let iconBackgroundSize {
|
||||
let iconBackground: UIVisualEffectView
|
||||
var iconBackgroundTransition = transition
|
||||
if let currentBackground = self.iconBackground {
|
||||
iconBackground = currentBackground
|
||||
} else {
|
||||
iconBackgroundTransition = .immediate
|
||||
|
||||
let blurEffect: UIBlurEffect
|
||||
if #available(iOS 13.0, *) {
|
||||
blurEffect = UIBlurEffect(style: .systemThinMaterialDark)
|
||||
} else {
|
||||
blurEffect = UIBlurEffect(style: .dark)
|
||||
}
|
||||
hiddenIconBackground = UIVisualEffectView(effect: blurEffect)
|
||||
hiddenIconBackground.clipsToBounds = true
|
||||
hiddenIconBackground.layer.cornerRadius = 15.0
|
||||
self.hiddenIconBackground = hiddenIconBackground
|
||||
iconBackground = UIVisualEffectView(effect: blurEffect)
|
||||
iconBackground.clipsToBounds = true
|
||||
self.iconBackground = iconBackground
|
||||
|
||||
hiddenIcon = UIImageView(image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/HiddenIcon"), color: .white))
|
||||
self.hiddenIcon = hiddenIcon
|
||||
|
||||
self.addSubview(hiddenIconBackground)
|
||||
hiddenIconBackground.contentView.addSubview(hiddenIcon)
|
||||
self.addSubview(iconBackground)
|
||||
|
||||
if !isFirstTime {
|
||||
hiddenIconBackground.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
hiddenIconBackground.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
iconBackground.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
iconBackground.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
iconBackgroundTransition.containedViewLayoutTransition.animateView {
|
||||
iconBackground.frame = iconBackgroundSize.centered(around: animationFrame.center)
|
||||
iconBackground.layer.cornerRadius = iconBackgroundSize.width / 2.0
|
||||
}
|
||||
} else if let iconBackground = self.iconBackground {
|
||||
self.iconBackground = nil
|
||||
iconBackground.layer.animateAlpha(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
iconBackground.removeFromSuperview()
|
||||
})
|
||||
iconBackground.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
if component.isPinned || (component.isEditing && backgroundColor != nil) {
|
||||
let pinnedIcon: UIImageView
|
||||
if let currentIcon = self.pinnedIcon {
|
||||
pinnedIcon = currentIcon
|
||||
} else {
|
||||
pinnedIcon = UIImageView(image: UIImage(bundleImageName: !component.isPinned ? "Peer Info/PinnedLargeIcon" : "Peer Info/PinnedIcon")?.withRenderingMode(.alwaysTemplate))
|
||||
self.pinnedIcon = pinnedIcon
|
||||
self.addSubview(pinnedIcon)
|
||||
|
||||
if !isFirstTime {
|
||||
pinnedIcon.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
pinnedIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
if component.isPinned {
|
||||
pinnedIcon.frame = CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: CGSize(width: 24.0, height: 24.0))
|
||||
pinnedIcon.tintColor = backgroundColor == nil ? component.theme.list.itemSecondaryTextColor : .white
|
||||
} else {
|
||||
let iconSize = CGSize(width: 48.0, height: 48.0)
|
||||
pinnedIcon.frame = iconSize.centered(around: animationFrame.center)
|
||||
pinnedIcon.tintColor = .white
|
||||
}
|
||||
} else if let pinnedIcon = self.pinnedIcon {
|
||||
self.pinnedIcon = nil
|
||||
pinnedIcon.layer.animateAlpha(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
pinnedIcon.removeFromSuperview()
|
||||
})
|
||||
pinnedIcon.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
if component.isHidden && !component.isEditing {
|
||||
let hiddenIcon: UIImageView
|
||||
if let currentIcon = self.hiddenIcon {
|
||||
hiddenIcon = currentIcon
|
||||
} else {
|
||||
hiddenIcon = UIImageView(image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/HiddenIcon"), color: .white))
|
||||
self.hiddenIcon = hiddenIcon
|
||||
self.addSubview(hiddenIcon)
|
||||
|
||||
if !isFirstTime {
|
||||
hiddenIcon.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
hiddenIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 30.0, height: 30.0)
|
||||
hiddenIconBackground.frame = iconSize.centered(around: animationFrame.center)
|
||||
hiddenIcon.frame = CGRect(origin: .zero, size: iconSize)
|
||||
} else {
|
||||
if let hiddenIconBackground = self.hiddenIconBackground {
|
||||
self.hiddenIconBackground = nil
|
||||
self.hiddenIcon = nil
|
||||
|
||||
hiddenIconBackground.layer.animateAlpha(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
hiddenIconBackground.removeFromSuperview()
|
||||
})
|
||||
hiddenIconBackground.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
hiddenIcon.frame = iconSize.centered(around: animationFrame.center)
|
||||
} else if let hiddenIcon = self.hiddenIcon {
|
||||
self.hiddenIcon = nil
|
||||
hiddenIcon.layer.animateAlpha(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
hiddenIcon.removeFromSuperview()
|
||||
})
|
||||
hiddenIcon.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
if case .grid = component.mode {
|
||||
|
|
@ -700,6 +803,13 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if let _ = component.action {
|
||||
self.addSubview(self.containerButton)
|
||||
self.containerButton.isUserInteractionEnabled = true
|
||||
} else {
|
||||
self.containerButton.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,16 +246,6 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||
|
||||
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
|
||||
|
||||
if let chargeStars = item.chargeStars {
|
||||
let media = [
|
||||
TelegramMediaAction(
|
||||
action: .customText(text: "You paid \(chargeStars) Stars to send a message", entities: [MessageTextEntity(range: 9 ..< 18, type: .Bold)], additionalAttributes: nil)
|
||||
)
|
||||
]
|
||||
let message = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
|
||||
}
|
||||
}
|
||||
|
||||
var nodes: [ListViewItemNode] = []
|
||||
|
|
|
|||
|
|
@ -223,21 +223,23 @@ final class GiftSetupScreenComponent: Component {
|
|||
guard let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
|
||||
switch component.subject {
|
||||
case let .premium(product):
|
||||
if self.payWithStars, let starsPrice = product.starsPrice, let peer = self.peerMap[component.peerId] {
|
||||
//TODO:localize
|
||||
if let balance = component.context.starsContext?.currentState?.balance, balance.value < starsPrice {
|
||||
self.proceedWithStarGift()
|
||||
} else {
|
||||
let priceString = presentationStringsFormattedNumber(Int32(starsPrice), environment.dateTimeFormat.groupingSeparator)
|
||||
let controller = textAlertController(
|
||||
context: component.context,
|
||||
title: "Send a Gift",
|
||||
text: "Are you sure you want to gift **Telegram Premium** to \(peer.compactDisplayTitle) for **\(priceString) Stars**?",
|
||||
title: environment.strings.Gift_Send_Premium_Confirmation_Title,
|
||||
text: environment.strings.Gift_Send_Premium_Confirmation_Text(
|
||||
peer.compactDisplayTitle,
|
||||
environment.strings.Gift_Send_Premium_Confirmation_Text_Stars(Int32(starsPrice))
|
||||
).string,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: "Cancel", action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: "Confirm", action: { [weak self] in
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: environment.strings.Gift_Send_Premium_Confirmation_Confirm, action: { [weak self] in
|
||||
if let self {
|
||||
self.proceedWithStarGift()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ private extension MessageInputActionButtonComponent.Mode {
|
|||
return ButtonIcon(icon: .apply)
|
||||
case .send:
|
||||
return ButtonIcon(icon: .send)
|
||||
case .stars:
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
@ -121,6 +123,7 @@ public final class MessageInputActionButtonComponent: Component {
|
|||
public enum Mode: Equatable {
|
||||
case none
|
||||
case send
|
||||
case stars(Int64)
|
||||
case apply
|
||||
case voiceInput
|
||||
case videoInput
|
||||
|
|
@ -472,7 +475,7 @@ public final class MessageInputActionButtonComponent: Component {
|
|||
break
|
||||
case .captionUp, .captionDown:
|
||||
sendAlpha = 0.0
|
||||
case .send, .apply, .attach, .delete, .forward, .removeVideoInput, .repost:
|
||||
case .send, .apply, .attach, .delete, .forward, .removeVideoInput, .repost, .stars:
|
||||
sendAlpha = 1.0
|
||||
case let .like(reaction, _, _):
|
||||
if reaction != nil {
|
||||
|
|
@ -671,7 +674,7 @@ public final class MessageInputActionButtonComponent: Component {
|
|||
|
||||
if previousComponent?.mode != component.mode {
|
||||
switch component.mode {
|
||||
case .none, .send, .apply, .voiceInput, .attach, .delete, .forward, .unavailableVoiceInput, .more, .like, .repost, .captionUp, .captionDown:
|
||||
case .none, .send, .apply, .voiceInput, .attach, .delete, .forward, .unavailableVoiceInput, .more, .like, .repost, .captionUp, .captionDown, .stars:
|
||||
micButton.updateMode(mode: .audio, animated: !transition.animation.isImmediate)
|
||||
case .videoInput, .removeVideoInput:
|
||||
micButton.updateMode(mode: .video, animated: !transition.animation.isImmediate)
|
||||
|
|
|
|||
|
|
@ -1452,7 +1452,11 @@ public final class MessageInputPanelComponent: Component {
|
|||
inputActionButtonMode = .send
|
||||
} else {
|
||||
if self.textFieldExternalState.hasText {
|
||||
inputActionButtonMode = .send
|
||||
if let sendPaidMessageStars = component.sendPaidMessageStars {
|
||||
inputActionButtonMode = .stars(sendPaidMessageStars.value)
|
||||
} else {
|
||||
inputActionButtonMode = .send
|
||||
}
|
||||
} else if !isEditing && component.forwardAction != nil {
|
||||
inputActionButtonMode = .forward
|
||||
} else {
|
||||
|
|
@ -1477,7 +1481,7 @@ public final class MessageInputPanelComponent: Component {
|
|||
switch mode {
|
||||
case .none:
|
||||
break
|
||||
case .send:
|
||||
case .send, .stars:
|
||||
if case .up = action {
|
||||
if component.recordedAudioPreview != nil {
|
||||
component.sendMessageAction()
|
||||
|
|
|
|||
|
|
@ -1859,6 +1859,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
hasSavedMessageTags = .single(false)
|
||||
}
|
||||
|
||||
let starsRevenueContextAndState = context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.CanViewStarsRevenue(id: peerId)
|
||||
)
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { canViewStarsRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
|
||||
guard canViewStarsRevenue else {
|
||||
return .single((nil, nil))
|
||||
}
|
||||
let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId)
|
||||
return starsRevenueStatsContext.state
|
||||
|> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in
|
||||
return (starsRevenueStatsContext, state.stats)
|
||||
}
|
||||
}
|
||||
|
||||
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
|
||||
|
||||
return combineLatest(queue: .mainQueue(),
|
||||
|
|
@ -1878,9 +1893,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
hasSavedMessages,
|
||||
hasSavedMessagesChats,
|
||||
hasSavedMessageTags,
|
||||
isPremiumRequiredForStoryPosting
|
||||
isPremiumRequiredForStoryPosting,
|
||||
starsRevenueContextAndState
|
||||
)
|
||||
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting -> Signal<PeerInfoScreenData, NoError> in
|
||||
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState -> Signal<PeerInfoScreenData, NoError> in
|
||||
var discussionPeer: Peer?
|
||||
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
||||
discussionPeer = peer
|
||||
|
|
@ -1980,8 +1996,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting,
|
||||
personalChannel: nil,
|
||||
starsState: nil,
|
||||
starsRevenueStatsState: nil,
|
||||
starsRevenueStatsContext: nil,
|
||||
starsRevenueStatsState: starsRevenueContextAndState.1,
|
||||
starsRevenueStatsContext: starsRevenueContextAndState.0,
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
|
|
|
|||
|
|
@ -43,9 +43,11 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
|||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private var iconLayers: [InlineStickerItemLayer] = []
|
||||
private var iconLayers: [AnyHashable: InlineStickerItemLayer] = [:]
|
||||
|
||||
private var isSelected: Bool = false
|
||||
private var icons: [ProfileGiftsContext.State.StarGift] = []
|
||||
private var titleWidth: CGFloat?
|
||||
|
||||
init(pressed: @escaping () -> Void) {
|
||||
self.pressed = pressed
|
||||
|
|
@ -67,27 +69,53 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
|||
self.pressed()
|
||||
}
|
||||
|
||||
func updateText(context: AccountContext, title: String, icons: [TelegramMediaFile] = [], isSelected: Bool, presentationData: PresentationData) {
|
||||
func updateText(context: AccountContext, title: String, icons: [ProfileGiftsContext.State.StarGift] = [], isSelected: Bool, presentationData: PresentationData) {
|
||||
self.isSelected = isSelected
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
|
||||
self.icons = icons
|
||||
|
||||
if !icons.isEmpty {
|
||||
if self.iconLayers.isEmpty {
|
||||
for icon in icons {
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
var validIds = Set<AnyHashable>()
|
||||
var index = 0
|
||||
for icon in icons {
|
||||
let id: AnyHashable
|
||||
if let reference = icon.reference {
|
||||
id = reference
|
||||
} else {
|
||||
id = index
|
||||
}
|
||||
validIds.insert(id)
|
||||
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
if let _ = self.iconLayers[id] {
|
||||
|
||||
} else {
|
||||
var file: TelegramMediaFile?
|
||||
switch icon.gift {
|
||||
case let .generic(gift):
|
||||
file = gift.file
|
||||
case let .unique(gift):
|
||||
for attribute in gift.attributes {
|
||||
if case let .model(_, fileValue, _) = attribute {
|
||||
file = fileValue
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let file else {
|
||||
continue
|
||||
}
|
||||
|
||||
let emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||
interactivelySelectedFromPackId: nil,
|
||||
fileId: icon.fileId.id,
|
||||
file: icon
|
||||
fileId: file.fileId.id,
|
||||
file: file
|
||||
)
|
||||
|
||||
let animationLayer = InlineStickerItemLayer(
|
||||
context: .account(context),
|
||||
userLocation: .other,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: emoji,
|
||||
file: icon,
|
||||
file: file,
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
unique: true,
|
||||
|
|
@ -96,12 +124,30 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
|||
loopCount: 1
|
||||
)
|
||||
animationLayer.isVisibleForAnimations = true
|
||||
self.iconLayers.append(animationLayer)
|
||||
self.iconLayers[id] = animationLayer
|
||||
self.layer.addSublayer(animationLayer)
|
||||
|
||||
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
animationLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, layer) in self.iconLayers {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
layer.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.iconLayers.removeValue(forKey: id)
|
||||
}
|
||||
} else {
|
||||
for layer in self.iconLayers {
|
||||
for (_, layer) in self.iconLayers {
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
self.iconLayers.removeAll()
|
||||
|
|
@ -115,24 +161,54 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
func updateLayout(height: CGFloat) -> CGFloat {
|
||||
var totalWidth: CGFloat = 0.0
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
totalWidth = titleSize.width
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
let spacing: CGFloat = 1.0
|
||||
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
self.titleWidth = titleSize.width
|
||||
|
||||
var totalWidth = titleSize.width
|
||||
if !self.iconLayers.isEmpty {
|
||||
totalWidth += 2.0
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
let spacing: CGFloat = 1.0
|
||||
for iconlayer in self.iconLayers {
|
||||
iconlayer.frame = CGRect(origin: CGPoint(x: totalWidth, y: 15.0), size: iconSize)
|
||||
totalWidth += iconSize.width + spacing
|
||||
}
|
||||
totalWidth += (iconSize.width + spacing) * CGFloat(self.iconLayers.count)
|
||||
totalWidth -= spacing
|
||||
}
|
||||
|
||||
self.layoutIcons(transition: .animated(duration: 0.3, curve: .spring))
|
||||
|
||||
return totalWidth
|
||||
}
|
||||
|
||||
func layoutIcons(transition: ContainedViewLayoutTransition) {
|
||||
guard let titleWidth = self.titleWidth else {
|
||||
return
|
||||
}
|
||||
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||
let spacing: CGFloat = 1.0
|
||||
|
||||
var origin = CGPoint(x: titleWidth + 2.0, y: 15.0)
|
||||
|
||||
var index = 0
|
||||
for icon in self.icons {
|
||||
let id: AnyHashable
|
||||
if let reference = icon.reference {
|
||||
id = reference
|
||||
} else {
|
||||
id = index
|
||||
}
|
||||
if let layer = self.iconLayers[id] {
|
||||
var iconTransition = transition
|
||||
if layer.frame.width.isZero {
|
||||
iconTransition = .immediate
|
||||
}
|
||||
iconTransition.updateFrame(layer: layer, frame: CGRect(origin: origin, size: iconSize))
|
||||
}
|
||||
origin.x += iconSize.width + spacing
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
func updateArea(size: CGSize, sideInset: CGFloat) {
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height))
|
||||
}
|
||||
|
|
@ -141,7 +217,7 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
|||
struct PeerInfoPaneSpecifier: Equatable {
|
||||
var key: PeerInfoPaneKey
|
||||
var title: String
|
||||
var icons: [TelegramMediaFile]
|
||||
var icons: [ProfileGiftsContext.State.StarGift]
|
||||
}
|
||||
|
||||
private func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
|
||||
|
|
@ -471,7 +547,7 @@ private final class PeerInfoPendingPane {
|
|||
}
|
||||
}
|
||||
}
|
||||
paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, profileGifts: data.profileGiftsContext!, canManage: canManage)
|
||||
paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, profileGifts: data.profileGiftsContext!, canManage: canManage)
|
||||
case .stories, .storyArchive, .botPreview:
|
||||
var canManage = false
|
||||
if let peer = data.peer {
|
||||
|
|
@ -884,11 +960,15 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
|||
for (_, pane) in self.currentPanes {
|
||||
if let paneNode = pane.node as? PeerInfoStoryPaneNode {
|
||||
paneNode.updateIsReordering(isReordering: isReordering, animated: animated)
|
||||
} else if let paneNode = pane.node as? PeerInfoGiftsPaneNode {
|
||||
paneNode.updateIsReordering(isReordering: isReordering, animated: animated)
|
||||
}
|
||||
}
|
||||
for (_, pane) in self.pendingPanes {
|
||||
if let paneNode = pane.pane.node as? PeerInfoStoryPaneNode {
|
||||
paneNode.updateIsReordering(isReordering: isReordering, animated: false)
|
||||
} else if let paneNode = pane.pane.node as? PeerInfoGiftsPaneNode {
|
||||
paneNode.updateIsReordering(isReordering: isReordering, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1185,7 +1265,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
|||
|
||||
self.tabsContainerNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: tabsHeight), presentationData: presentationData, paneList: availablePanes.map { key in
|
||||
let title: String
|
||||
var icons: [TelegramMediaFile] = []
|
||||
var icons: [ProfileGiftsContext.State.StarGift] = []
|
||||
switch key {
|
||||
case .stories:
|
||||
title = presentationData.strings.PeerInfo_PaneStories
|
||||
|
|
@ -1219,19 +1299,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
|||
title = presentationData.strings.PeerInfo_SavedMessagesTabTitle
|
||||
case .gifts:
|
||||
title = presentationData.strings.PeerInfo_PaneGifts
|
||||
icons = data?.profileGiftsContext?.currentState?.gifts.prefix(3).compactMap { gift in
|
||||
switch gift.gift {
|
||||
case let .generic(gift):
|
||||
return gift.file
|
||||
case let .unique(gift):
|
||||
for attribute in gift.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} ?? []
|
||||
if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) {
|
||||
icons = Array(gifts)
|
||||
}
|
||||
}
|
||||
return PeerInfoPaneSpecifier(key: key, title: title, icons: icons)
|
||||
}, selectedPane: self.currentPaneKey, disableSwitching: disableTabSwitching, transitionFraction: self.transitionFraction, transition: transition)
|
||||
|
|
|
|||
|
|
@ -1233,6 +1233,7 @@ private enum InfoSection: Int, CaseIterable {
|
|||
case balances
|
||||
case permissions
|
||||
case peerInfoTrailing
|
||||
case peerSettings
|
||||
case peerMembers
|
||||
case botAffiliateProgram
|
||||
}
|
||||
|
|
@ -1854,53 +1855,68 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||
interaction.openParticipantsSection(.memberRequests)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if channel.adminRights != nil || channel.flags.contains(.isCreator) {
|
||||
let section: InfoSection
|
||||
if case .group = channel.info {
|
||||
section = .peerSettings
|
||||
} else {
|
||||
section = .peerMembers
|
||||
}
|
||||
if cachedData.flags.contains(.canViewRevenue) || cachedData.flags.contains(.canViewStarsRevenue) {
|
||||
let revenueBalance = data.revenueStatsState?.balances.currentBalance ?? 0
|
||||
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? StarsAmount.zero
|
||||
|
||||
let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue ?? 0
|
||||
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? StarsAmount.zero
|
||||
|
||||
if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero {
|
||||
let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0))
|
||||
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
|
||||
let labelColor = presentationData.theme.list.itemSecondaryTextColor
|
||||
|
||||
if cachedData.flags.contains(.canViewRevenue) || cachedData.flags.contains(.canViewStarsRevenue) {
|
||||
let revenueBalance = data.revenueStatsState?.balances.currentBalance ?? 0
|
||||
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? StarsAmount.zero
|
||||
|
||||
let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue ?? 0
|
||||
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? StarsAmount.zero
|
||||
|
||||
if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero {
|
||||
let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0))
|
||||
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
|
||||
let labelColor = presentationData.theme.list.itemSecondaryTextColor
|
||||
|
||||
let attributedString = NSMutableAttributedString()
|
||||
if overallRevenueBalance > 0 {
|
||||
attributedString.append(NSAttributedString(string: "#\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))", font: labelFont, textColor: labelColor))
|
||||
}
|
||||
if overallStarsBalance > StarsAmount.zero {
|
||||
if !attributedString.string.isEmpty {
|
||||
attributedString.append(NSAttributedString(string: " ", font: labelFont, textColor: labelColor))
|
||||
}
|
||||
attributedString.append(NSAttributedString(string: "*", font: labelFont, textColor: labelColor))
|
||||
|
||||
let formattedLabel = formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
let starsAttributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
|
||||
attributedString.append(starsAttributedString)
|
||||
}
|
||||
if let range = attributedString.string.range(of: "#") {
|
||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton), range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
if let range = attributedString.string.range(of: "*") {
|
||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
|
||||
items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemBalance, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_Bot_Balance, icon: PresentationResourcesSettings.balance, action: {
|
||||
interaction.openStats(.monetization)
|
||||
}))
|
||||
let attributedString = NSMutableAttributedString()
|
||||
if overallRevenueBalance > 0 {
|
||||
attributedString.append(NSAttributedString(string: "#\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))", font: labelFont, textColor: labelColor))
|
||||
}
|
||||
if overallStarsBalance > StarsAmount.zero {
|
||||
if !attributedString.string.isEmpty {
|
||||
attributedString.append(NSAttributedString(string: " ", font: labelFont, textColor: labelColor))
|
||||
}
|
||||
attributedString.append(NSAttributedString(string: "*", font: labelFont, textColor: labelColor))
|
||||
|
||||
let formattedLabel = formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
let starsAttributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
|
||||
attributedString.append(starsAttributedString)
|
||||
}
|
||||
if let range = attributedString.string.range(of: "#") {
|
||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton), range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
if let range = attributedString.string.range(of: "*") {
|
||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
|
||||
items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemEdit, label: .none, text: presentationData.strings.Channel_Info_Settings, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: {
|
||||
interaction.openEditing()
|
||||
items[section]!.append(PeerInfoScreenDisclosureItem(id: ItemBalance, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_Bot_Balance, icon: PresentationResourcesSettings.balance, action: {
|
||||
interaction.openStats(.monetization)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let settingsTitle: String
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
settingsTitle = presentationData.strings.Channel_Info_Settings
|
||||
case .group:
|
||||
settingsTitle = presentationData.strings.Group_Info_Settings
|
||||
}
|
||||
items[section]!.append(PeerInfoScreenDisclosureItem(id: ItemEdit, label: .none, text: settingsTitle, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: {
|
||||
interaction.openEditing()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8282,7 +8298,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
let statsController: ViewController
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
statsController = groupStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id)
|
||||
if case .monetization = section {
|
||||
statsController = channelStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, section: section, existingStarsRevenueContext: data.starsRevenueStatsContext, boostStatus: boostStatus)
|
||||
} else {
|
||||
statsController = groupStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id)
|
||||
}
|
||||
} else {
|
||||
statsController = channelStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, section: section, boostStatus: boostStatus)
|
||||
}
|
||||
|
|
@ -10951,6 +10971,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let currentPaneKey = self.paneContainerNode.currentPaneKey, case .gifts = currentPaneKey else {
|
||||
return
|
||||
}
|
||||
guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoGiftsPaneNode else {
|
||||
return
|
||||
}
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
|
@ -10968,14 +10991,22 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
let strings = self.presentationData.strings
|
||||
let items: Signal<ContextController.Items, NoError> = giftsContext.state
|
||||
|> map { state in
|
||||
return (state.filter, state.sorting)
|
||||
var hasPinnedGifts = false
|
||||
for gift in state.gifts {
|
||||
if gift.pinnedToTop {
|
||||
hasPinnedGifts = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return (state.filter, state.sorting, hasPinnedGifts)
|
||||
}
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in
|
||||
let filterEquals = lhs.0 == rhs.0
|
||||
let sortingEquals = lhs.1 == rhs.1
|
||||
return filterEquals && sortingEquals
|
||||
let hasPinnedGiftsEquals = lhs.2 == rhs.2
|
||||
return filterEquals && sortingEquals && hasPinnedGiftsEquals
|
||||
})
|
||||
|> map { [weak giftsContext] filter, sorting -> ContextController.Items in
|
||||
|> map { [weak giftsContext] filter, sorting, hasPinnedGifts -> ContextController.Items in
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in
|
||||
|
|
@ -10986,6 +11017,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
giftsContext?.updateSorting(sorting == .date ? .value : .date)
|
||||
})))
|
||||
|
||||
if hasPinnedGifts {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Reorder, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
pane.beginReordering()
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.separator)
|
||||
|
||||
let toggleFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in
|
||||
|
|
@ -12499,6 +12540,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
fileprivate let starsContext: StarsContext?
|
||||
private let switchToRecommendedChannels: Bool
|
||||
private let switchToGifts: Bool
|
||||
private let switchToGroupsInCommon: Bool
|
||||
let chatLocation: ChatLocation
|
||||
private let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||
|
||||
|
|
@ -12555,7 +12597,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false, switchToGifts: Bool = false) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false, switchToGifts: Bool = false, switchToGroupsInCommon: Bool = false) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.peerId = peerId
|
||||
|
|
@ -12570,6 +12612,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
self.requestsContext = requestsContext
|
||||
self.switchToRecommendedChannels = switchToRecommendedChannels
|
||||
self.switchToGifts = switchToGifts
|
||||
self.switchToGroupsInCommon = switchToGroupsInCommon
|
||||
|
||||
if let forumTopicThread = forumTopicThread {
|
||||
self.chatLocation = .replyThread(message: forumTopicThread)
|
||||
|
|
@ -12915,6 +12958,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||
initialPaneKey = .similarChannels
|
||||
} else if self.switchToGifts {
|
||||
initialPaneKey = .gifts
|
||||
} else if self.switchToGroupsInCommon {
|
||||
initialPaneKey = .groupsInCommon
|
||||
}
|
||||
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: initialPaneKey)
|
||||
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,276 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import GiftAnimationComponent
|
||||
|
||||
private final class GiftContextPreviewComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let gift: ProfileGiftsContext.State.StarGift
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift
|
||||
) {
|
||||
self.context = context
|
||||
self.gift = gift
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftContextPreviewComponent, rhs: GiftContextPreviewComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.gift != rhs.gift {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let animation = ComponentView<Empty>()
|
||||
private let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
|
||||
private var component: GiftContextPreviewComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: GiftContextPreviewComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
self.environment = environment
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let subject: GiftCompositionComponent.Subject
|
||||
switch component.gift.gift {
|
||||
case let .generic(gift):
|
||||
subject = .generic(gift.file)
|
||||
case let .unique(gift):
|
||||
subject = .unique(gift)
|
||||
}
|
||||
|
||||
let animationSize = self.animation.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GiftCompositionComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
subject: subject,
|
||||
animationOffset: nil,
|
||||
animationScale: nil,
|
||||
displayAnimationStars: false,
|
||||
externalState: self.giftCompositionExternalState,
|
||||
requestUpdate: { [weak state] in
|
||||
state?.updated()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.animation.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: .zero, size: animationSize)
|
||||
}
|
||||
|
||||
if case let .unique(uniqueGift) = component.gift.gift {
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(
|
||||
NSAttributedString(string: uniqueGift.title, font: Font.semibold(20.0), textColor: .white)
|
||||
))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height - 40.0), size: titleSize)
|
||||
}
|
||||
|
||||
let vibrantColor: UIColor
|
||||
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||
vibrantColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
|
||||
} else {
|
||||
vibrantColor = UIColor.white.withAlphaComponent(0.6)
|
||||
}
|
||||
let subtitleSize = self.subtitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(
|
||||
NSAttributedString(string: "\(environment.strings.Gift_Unique_Collectible) #\(uniqueGift.number)", font: Font.regular(13.0), textColor: vibrantColor)
|
||||
))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.subtitle.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: availableSize.height - subtitleSize.height - 20.0), size: subtitleSize)
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class GiftContextPreviewController: ViewController {
|
||||
fileprivate final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate {
|
||||
private weak var controller: GiftContextPreviewController?
|
||||
|
||||
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
init(controller: GiftContextPreviewController) {
|
||||
self.controller = controller
|
||||
self.presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
self.view.disablesInteractiveKeyboardGestureRecognizer = true
|
||||
}
|
||||
|
||||
func requestLayout(transition: ComponentTransition) {
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: ComponentTransition) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
let environment = ViewControllerComponentContainer.Environment(
|
||||
statusBarHeight: 0.0,
|
||||
navigationHeight: 0.0,
|
||||
safeInsets: layout.safeInsets,
|
||||
additionalInsets: layout.additionalInsets,
|
||||
inputHeight: 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: nil,
|
||||
isVisible: true,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
dateTimeFormat: self.presentationData.dateTimeFormat,
|
||||
controller: { [weak self] in
|
||||
return self?.controller
|
||||
}
|
||||
)
|
||||
|
||||
let componentSize = self.componentHost.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
GiftContextPreviewComponent(
|
||||
context: controller.context,
|
||||
gift: controller.gift
|
||||
)
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
},
|
||||
forceUpdate: forceUpdate,
|
||||
containerSize: layout.size
|
||||
)
|
||||
if let componentView = self.componentHost.view {
|
||||
if componentView.superview == nil {
|
||||
componentView.clipsToBounds = true
|
||||
self.view.addSubview(componentView)
|
||||
}
|
||||
let componentFrame = CGRect(origin: .zero, size: componentSize)
|
||||
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var node: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
fileprivate let context: AccountContext
|
||||
fileprivate let gift: ProfileGiftsContext.State.StarGift
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift
|
||||
) {
|
||||
self.context = context
|
||||
self.gift = gift
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self)
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
let minSide = min(layout.size.width, layout.size.height)
|
||||
if case .unique = self.gift.gift {
|
||||
return CGSize(width: minSide, height: floor(minSide * 0.66))
|
||||
} else {
|
||||
return CGSize(width: minSide, height: 180.0)
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -62,11 +62,11 @@ public final class StarsBalanceOverlayComponent: Component {
|
|||
func update(component: StarsBalanceOverlayComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let balance = component.context.starsContext?.currentState?.balance.value ?? 0
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let balance = presentationStringsFormattedNumber(Int32(component.context.starsContext?.currentState?.balance.value ?? 0), presentationData.dateTimeFormat.groupingSeparator)
|
||||
|
||||
//TODO:localize
|
||||
let attributedText = parseMarkdownIntoAttributedString(
|
||||
"Your balance is **⭐️\(balance)**",
|
||||
presentationData.strings.StarsBalance_YourBalance("**⭐️\(balance)**").string,
|
||||
attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
|
|
@ -100,7 +100,7 @@ public final class StarsBalanceOverlayComponent: Component {
|
|||
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== component.theme {
|
||||
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: component.theme.rootController.navigationBar.accentTextColor)!, component.theme)
|
||||
}
|
||||
let actionText = NSMutableAttributedString(string: "Get More Stars >", font: Font.regular(13.0), textColor: component.theme.rootController.navigationBar.accentTextColor)
|
||||
let actionText = NSMutableAttributedString(string: presentationData.strings.StarsBalance_GetMoreStars, font: Font.regular(13.0), textColor: component.theme.rootController.navigationBar.accentTextColor)
|
||||
if let range = actionText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
|
||||
actionText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: actionText.string))
|
||||
actionText.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: actionText.string))
|
||||
|
|
@ -130,7 +130,7 @@ public final class StarsBalanceOverlayComponent: Component {
|
|||
if actionView.superview == nil {
|
||||
self.addSubview(actionView)
|
||||
}
|
||||
actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 28.0), size: actionSize)
|
||||
actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize)
|
||||
}
|
||||
|
||||
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@ final class StarsBalanceComponent: Component {
|
|||
let actionAvailable: Bool
|
||||
let actionIsEnabled: Bool
|
||||
let actionCooldownUntilTimestamp: Int32?
|
||||
let actionIcon: UIImage?
|
||||
let action: () -> Void
|
||||
let buyAds: (() -> Void)?
|
||||
let secondaryActionTitle: String?
|
||||
let secondaryActionIcon: UIImage?
|
||||
let secondaryAction: (() -> Void)?
|
||||
let additionalAction: AnyComponent<Empty>?
|
||||
|
||||
init(
|
||||
|
|
@ -36,8 +39,11 @@ final class StarsBalanceComponent: Component {
|
|||
actionAvailable: Bool,
|
||||
actionIsEnabled: Bool,
|
||||
actionCooldownUntilTimestamp: Int32? = nil,
|
||||
actionIcon: UIImage? = nil,
|
||||
action: @escaping () -> Void,
|
||||
buyAds: (() -> Void)?,
|
||||
secondaryActionTitle: String? = nil,
|
||||
secondaryActionIcon: UIImage? = nil,
|
||||
secondaryAction: (() -> Void)? = nil,
|
||||
additionalAction: AnyComponent<Empty>? = nil
|
||||
) {
|
||||
self.theme = theme
|
||||
|
|
@ -49,8 +55,11 @@ final class StarsBalanceComponent: Component {
|
|||
self.actionAvailable = actionAvailable
|
||||
self.actionIsEnabled = actionIsEnabled
|
||||
self.actionCooldownUntilTimestamp = actionCooldownUntilTimestamp
|
||||
self.actionIcon = actionIcon
|
||||
self.action = action
|
||||
self.buyAds = buyAds
|
||||
self.secondaryActionTitle = secondaryActionTitle
|
||||
self.secondaryActionIcon = secondaryActionIcon
|
||||
self.secondaryAction = secondaryAction
|
||||
self.additionalAction = additionalAction
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +85,9 @@ final class StarsBalanceComponent: Component {
|
|||
if lhs.actionCooldownUntilTimestamp != rhs.actionCooldownUntilTimestamp {
|
||||
return false
|
||||
}
|
||||
if lhs.secondaryActionTitle != rhs.secondaryActionTitle {
|
||||
return false
|
||||
}
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
|
|
@ -90,7 +102,7 @@ final class StarsBalanceComponent: Component {
|
|||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private var button = ComponentView<Empty>()
|
||||
private var buyAdsButton = ComponentView<Empty>()
|
||||
private var secondaryButton = ComponentView<Empty>()
|
||||
|
||||
private var additionalButton = ComponentView<Empty>()
|
||||
|
||||
|
|
@ -205,18 +217,16 @@ final class StarsBalanceComponent: Component {
|
|||
if component.actionAvailable {
|
||||
contentHeight += 12.0
|
||||
|
||||
var actionTitle = component.actionTitle
|
||||
var withdrawWidth = availableSize.width - sideInset * 2.0
|
||||
if let _ = component.buyAds {
|
||||
if let _ = component.secondaryAction {
|
||||
withdrawWidth = (withdrawWidth - 10.0) / 2.0
|
||||
actionTitle = component.strings.Stars_BotRevenue_Withdraw_WithdrawShort
|
||||
}
|
||||
|
||||
let content: AnyComponentWithIdentity<Empty>
|
||||
if remainingCooldownSeconds > 0 {
|
||||
content = AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(
|
||||
VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(BundleIconComponent(name: "Chat List/StatusLockIcon", tintColor: component.theme.list.itemCheckColors.fillColor.mixedWith(component.theme.list.itemCheckColors.foregroundColor, alpha: 0.7)))),
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(Text(text: stringForRemainingTime(remainingCooldownSeconds), font: Font.with(size: 11.0, weight: .medium, traits: [.monospacedNumbers]), color: component.theme.list.itemCheckColors.fillColor.mixedWith(component.theme.list.itemCheckColors.foregroundColor, alpha: 0.7))))
|
||||
|
|
@ -224,7 +234,17 @@ final class StarsBalanceComponent: Component {
|
|||
], spacing: 1.0)
|
||||
))
|
||||
} else {
|
||||
content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor)))
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
if let icon = component.actionIcon {
|
||||
items.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(Image(image: icon, tintColor: component.theme.list.itemCheckColors.foregroundColor, size: icon.size))))
|
||||
}
|
||||
items.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))))
|
||||
content = AnyComponentWithIdentity(
|
||||
id: AnyHashable(0 as Int),
|
||||
component: AnyComponent(
|
||||
HStack(items, spacing: 7.0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
|
|
@ -257,8 +277,14 @@ final class StarsBalanceComponent: Component {
|
|||
buttonView.frame = buttonFrame
|
||||
}
|
||||
|
||||
if let _ = component.buyAds {
|
||||
let buttonSize = self.buyAdsButton.update(
|
||||
if let secondaryActionTitle = component.secondaryActionTitle {
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
if let icon = component.secondaryActionIcon {
|
||||
items.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(Image(image: icon, tintColor: component.theme.list.itemCheckColors.foregroundColor, size: icon.size))))
|
||||
}
|
||||
items.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: secondaryActionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))))
|
||||
|
||||
let buttonSize = self.secondaryButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
|
|
@ -266,7 +292,12 @@ final class StarsBalanceComponent: Component {
|
|||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: component.strings.Stars_BotRevenue_Withdraw_BuyAds, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0 as Int),
|
||||
component: AnyComponent(
|
||||
HStack(items, spacing: 7.0)
|
||||
)
|
||||
),
|
||||
isEnabled: component.actionIsEnabled,
|
||||
allowActionWhenDisabled: false,
|
||||
displaysProgress: false,
|
||||
|
|
@ -274,13 +305,13 @@ final class StarsBalanceComponent: Component {
|
|||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.buyAds?()
|
||||
component.secondaryAction?()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: withdrawWidth, height: 50.0)
|
||||
)
|
||||
if let buttonView = self.buyAdsButton.view {
|
||||
if let buttonView = self.secondaryButton.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -604,7 +604,7 @@ final class StarsStatisticsScreenComponent: Component {
|
|||
dateTimeFormat: environment.dateTimeFormat,
|
||||
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
|
||||
rate: self.starsState?.usdRate ?? 0,
|
||||
actionTitle: strings.Stars_BotRevenue_Withdraw_Withdraw,
|
||||
actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort,
|
||||
actionAvailable: true,
|
||||
actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true,
|
||||
actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp,
|
||||
|
|
@ -626,7 +626,8 @@ final class StarsStatisticsScreenComponent: Component {
|
|||
component.withdraw()
|
||||
}
|
||||
},
|
||||
buyAds: { [weak self] in
|
||||
secondaryActionTitle: strings.Stars_BotRevenue_Withdraw_BuyAds,
|
||||
secondaryAction: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import StarsAvatarComponent
|
|||
import TelegramStringFormatting
|
||||
import ListItemComponentAdaptor
|
||||
import ItemListUI
|
||||
import StarsWithdrawalScreen
|
||||
|
||||
private let initialSubscriptionsDisplayedLimit: Int32 = 3
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
let openTransaction: (StarsContext.State.Transaction) -> Void
|
||||
let openSubscription: (StarsContext.State.Subscription) -> Void
|
||||
let buy: () -> Void
|
||||
let withdraw: () -> Void
|
||||
let gift: () -> Void
|
||||
|
||||
init(
|
||||
|
|
@ -44,6 +46,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
openTransaction: @escaping (StarsContext.State.Transaction) -> Void,
|
||||
openSubscription: @escaping (StarsContext.State.Subscription) -> Void,
|
||||
buy: @escaping () -> Void,
|
||||
withdraw: @escaping () -> Void,
|
||||
gift: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
|
|
@ -52,6 +55,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
self.openTransaction = openTransaction
|
||||
self.openSubscription = openSubscription
|
||||
self.buy = buy
|
||||
self.withdraw = withdraw
|
||||
self.gift = gift
|
||||
}
|
||||
|
||||
|
|
@ -602,7 +606,15 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
|
||||
contentHeight += descriptionSize.height
|
||||
contentHeight += 29.0
|
||||
|
||||
let withdrawAvailable: Bool
|
||||
#if DEBUG
|
||||
withdrawAvailable = "".isEmpty
|
||||
#else
|
||||
withdrawAvailable = "".isEmpty
|
||||
#endif
|
||||
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
let balanceSize = self.balanceView.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -617,16 +629,24 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
dateTimeFormat: environment.dateTimeFormat,
|
||||
count: self.starsState?.balance ?? StarsAmount.zero,
|
||||
rate: nil,
|
||||
actionTitle: environment.strings.Stars_Intro_Buy,
|
||||
actionTitle: withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy,
|
||||
actionAvailable: !premiumConfiguration.areStarsDisabled && !premiumConfiguration.isPremiumDisabled,
|
||||
actionIsEnabled: true,
|
||||
actionIcon: PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.buy()
|
||||
},
|
||||
buyAds: nil,
|
||||
secondaryActionTitle: withdrawAvailable ? environment.strings.Stars_Intro_Withdraw : nil,
|
||||
secondaryActionIcon: withdrawAvailable ? PresentationResourcesItemList.itemListRoundWithdrawIcon(environment.theme) : nil,
|
||||
secondaryAction: withdrawAvailable ? { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.withdraw()
|
||||
} : nil,
|
||||
additionalAction: (premiumConfiguration.starsGiftsPurchaseAvailable && !premiumConfiguration.isPremiumDisabled) ? AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
|
|
@ -1057,6 +1077,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
|||
self.subscriptionsContext = context.engine.payments.peerStarsSubscriptionsContext(starsContext: starsContext)
|
||||
|
||||
var buyImpl: (() -> Void)?
|
||||
var withdrawImpl: (() -> Void)?
|
||||
var giftImpl: (() -> Void)?
|
||||
var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)?
|
||||
var openSubscriptionImpl: ((StarsContext.State.Subscription) -> Void)?
|
||||
|
|
@ -1073,6 +1094,9 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
|||
buy: {
|
||||
buyImpl?()
|
||||
},
|
||||
withdraw: {
|
||||
withdrawImpl?()
|
||||
},
|
||||
gift: {
|
||||
giftImpl?()
|
||||
}
|
||||
|
|
@ -1168,6 +1192,47 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
|||
})
|
||||
}
|
||||
|
||||
withdrawImpl = { [weak self] in
|
||||
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 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: { 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) {
|
||||
context.starsContext?.load(force: true)
|
||||
}
|
||||
})
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
giftImpl = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -116,6 +116,14 @@ private final class SheetContent: CombinedComponent {
|
|||
minAmount = configuration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
maxAmount = status.balances.availableBalance
|
||||
amountLabel = nil
|
||||
case .accountWithdraw:
|
||||
titleString = environment.strings.Stars_Withdraw_Title
|
||||
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
|
||||
amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder
|
||||
|
||||
minAmount = configuration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
maxAmount = state.balance
|
||||
amountLabel = nil
|
||||
case .paidMedia:
|
||||
titleString = environment.strings.Stars_PaidContent_Title
|
||||
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
|
||||
|
|
@ -124,7 +132,6 @@ private final class SheetContent: CombinedComponent {
|
|||
minAmount = StarsAmount(value: 1, nanos: 0)
|
||||
maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
|
||||
|
||||
if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
|
||||
let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
|
||||
amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
|
||||
|
|
@ -153,7 +160,9 @@ private final class SheetContent: CombinedComponent {
|
|||
contentSize.height += 40.0
|
||||
|
||||
let balance: StarsAmount?
|
||||
if case .reaction = component.mode {
|
||||
if case .accountWithdraw = component.mode {
|
||||
balance = state.balance
|
||||
} else if case .reaction = component.mode {
|
||||
balance = state.balance
|
||||
} else if case let .withdraw(starsState) = component.mode {
|
||||
balance = starsState.balances.availableBalance
|
||||
|
|
@ -378,7 +387,9 @@ private final class SheetContent: CombinedComponent {
|
|||
var amount: StarsAmount?
|
||||
switch mode {
|
||||
case let .withdraw(stats):
|
||||
amount = stats.balances.availableBalance
|
||||
amount = StarsAmount(value: stats.balances.availableBalance.value, nanos: 0)
|
||||
case .accountWithdraw:
|
||||
amount = context.starsContext?.currentState.flatMap { StarsAmount(value: $0.balance.value, nanos: 0) }
|
||||
case let .paidMedia(initialValue):
|
||||
amount = initialValue.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
case .reaction:
|
||||
|
|
@ -500,6 +511,7 @@ private final class StarsWithdrawSheetComponent: CombinedComponent {
|
|||
public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
||||
public enum Mode: Equatable {
|
||||
case withdraw(StarsRevenueStats)
|
||||
case accountWithdraw
|
||||
case paidMedia(Int64?)
|
||||
case reaction(Int64?)
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Peer Info/HideIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/HideIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "hide_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/HideIcon.imageset/hide_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/HideIcon.imageset/hide_24.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "pin_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedIcon.imageset/pin_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedIcon.imageset/pin_24.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedLargeIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedLargeIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "pin_48.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedLargeIcon.imageset/pin_48.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/PinnedLargeIcon.imageset/pin_48.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/ShowIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/ShowIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "show_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/ShowIcon.imageset/show_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/ShowIcon.imageset/show_24.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/TransferIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/TransferIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "transfer_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/TransferIcon.imageset/transfer_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/TransferIcon.imageset/transfer_24.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/WearIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/WearIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wear_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/WearIcon.imageset/wear_24.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/WearIcon.imageset/wear_24.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -2591,7 +2591,7 @@ extension ChatControllerImpl {
|
|||
}
|
||||
})
|
||||
}, openPeerInfo: { [weak self] in
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: false, recommendedChannels: false))
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: false, section: nil))
|
||||
}, togglePeerNotifications: { [weak self] in
|
||||
if let strongSelf = self, let peerId = strongSelf.chatLocation.peerId {
|
||||
let _ = strongSelf.context.engine.peers.togglePeerMuted(peerId: peerId, threadId: strongSelf.chatLocation.threadId).startStandalone()
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ extension ChatControllerImpl {
|
|||
strongSelf.present(actionSheet, in: .window(.root))
|
||||
})
|
||||
}
|
||||
case let .openChatInfo(expandAvatar, recommendedChannels):
|
||||
case let .openChatInfo(expandAvatar, section):
|
||||
let _ = self.presentVoiceMessageDiscardAlert(action: {
|
||||
switch self.chatLocationInfoData {
|
||||
case let .peer(peerView):
|
||||
|
|
@ -400,7 +400,16 @@ extension ChatControllerImpl {
|
|||
if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet {
|
||||
expandAvatar = false
|
||||
}
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: recommendedChannels ? .recommendedChannels : .generic, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: strongSelf.inviteRequestsContext) {
|
||||
let mode: PeerInfoControllerMode
|
||||
switch section {
|
||||
case .groupsInCommon:
|
||||
mode = .groupsInCommon
|
||||
case .recommendedChannels:
|
||||
mode = .recommendedChannels
|
||||
default:
|
||||
mode = .generic
|
||||
}
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: strongSelf.inviteRequestsContext) {
|
||||
strongSelf.effectiveNavigationController?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,17 +130,19 @@ extension ChatControllerImpl {
|
|||
if case let .peer(currentPeerId) = self.chatLocation, peer?.id == currentPeerId {
|
||||
switch navigation {
|
||||
case let .info(params):
|
||||
var recommendedChannels = false
|
||||
var section: ChatNavigationButtonAction.ChatInfoSection?
|
||||
if let params {
|
||||
if params.switchToRecommendedChannels {
|
||||
recommendedChannels = true
|
||||
section = .recommendedChannels
|
||||
} else if params.switchToGroupsInCommon {
|
||||
section = .groupsInCommon
|
||||
}
|
||||
if params.ignoreInSavedMessages && currentPeerId == self.context.account.peerId {
|
||||
self.playShakeAnimation()
|
||||
return
|
||||
}
|
||||
}
|
||||
self.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar, recommendedChannels: recommendedChannels))
|
||||
self.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar, section: section))
|
||||
case let .chat(textInputState, _, _):
|
||||
if let textInputState = textInputState {
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
|
|
@ -189,8 +191,12 @@ extension ChatControllerImpl {
|
|||
if let fromReactionMessageId = fromReactionMessageId {
|
||||
mode = .reaction(fromReactionMessageId)
|
||||
}
|
||||
if case let .info(params) = navigation, let params, params.switchToRecommendedChannels {
|
||||
mode = .recommendedChannels
|
||||
if case let .info(params) = navigation, let params {
|
||||
if params.switchToRecommendedChannels {
|
||||
mode = .recommendedChannels
|
||||
} else if params.switchToGroupsInCommon {
|
||||
mode = .groupsInCommon
|
||||
}
|
||||
}
|
||||
if peer.id == strongSelf.context.account.peerId {
|
||||
mode = .myProfile
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ extension ChatControllerImpl {
|
|||
return
|
||||
}
|
||||
if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars {
|
||||
let totalAmount = sendPaidMessageStars.value * Int64(count)
|
||||
|
||||
let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] dismissedAmount in
|
||||
guard let self, let starsContext = self.context.starsContext else {
|
||||
return
|
||||
}
|
||||
if let dismissedAmount, dismissedAmount == sendPaidMessageStars.value, let currentState = starsContext.currentState, currentState.balance > sendPaidMessageStars {
|
||||
if let dismissedAmount, dismissedAmount == sendPaidMessageStars.value, let currentState = starsContext.currentState, currentState.balance.value > totalAmount, count < 3 && totalAmount < 100 {
|
||||
completion(true)
|
||||
self.displayPaidMessageUndo(count: count, amount: sendPaidMessageStars)
|
||||
} else {
|
||||
|
|
@ -52,14 +54,14 @@ extension ChatControllerImpl {
|
|||
let _ = ApplicationSpecificNotice.setDismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id, amount: sendPaidMessageStars.value).start()
|
||||
}
|
||||
|
||||
if let currentState = starsContext.currentState, currentState.balance < sendPaidMessageStars {
|
||||
if let currentState = starsContext.currentState, currentState.balance.value < totalAmount {
|
||||
let _ = (self.context.engine.payments.starsTopUpOptions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] options in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: sendPaidMessageStars.value), completion: { _ in
|
||||
let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), completion: { _ in
|
||||
completion(false)
|
||||
})
|
||||
self.push(controller)
|
||||
|
|
@ -89,18 +91,11 @@ extension ChatControllerImpl {
|
|||
self.context.engine.messages.forceSendPostponedPaidMessage(peerId: peerId)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let title: String
|
||||
if count > 1 {
|
||||
title = "\(count) Messages Sent"
|
||||
} else {
|
||||
title = "Message Sent"
|
||||
}
|
||||
|
||||
let title = self.presentationData.strings.Chat_PaidMessage_Sent_Title(count)
|
||||
let text = self.presentationData.strings.Chat_PaidMessage_Sent_Text(self.presentationData.strings.Chat_PaidMessage_Sent_Text_Stars(Int32(amount.value * Int64(count)))).string
|
||||
let textItems: [AnimatedTextComponent.Item] = [
|
||||
AnimatedTextComponent.Item(id: 0, content: .text("You paid \(amount.value * Int64(count)) Stars"))
|
||||
AnimatedTextComponent.Item(id: 0, content: .text(text))
|
||||
]
|
||||
|
||||
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in
|
||||
guard let self else {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -2271,36 +2271,41 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
}
|
||||
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.collapseInput()
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
strongSelf.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.collapseInput()
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
}
|
||||
return current
|
||||
}
|
||||
return current
|
||||
})
|
||||
}
|
||||
}, nil)
|
||||
|
||||
var messages = [EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: fileReference.abstract, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
|
||||
if silentPosting {
|
||||
messages = strongSelf.transformEnqueueMessages(messages, silentPosting: true)
|
||||
strongSelf.sendMessages(messages)
|
||||
} else if schedule {
|
||||
strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in
|
||||
if let strongSelf = self {
|
||||
let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
|
||||
strongSelf.sendMessages(transformedMessages)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
messages = strongSelf.transformEnqueueMessages(messages)
|
||||
strongSelf.sendMessages(messages)
|
||||
}
|
||||
}, nil)
|
||||
|
||||
var messages = [EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: fileReference.abstract, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
|
||||
if silentPosting {
|
||||
messages = strongSelf.transformEnqueueMessages(messages, silentPosting: true)
|
||||
strongSelf.sendMessages(messages)
|
||||
} else if schedule {
|
||||
strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in
|
||||
if let strongSelf = self {
|
||||
let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
|
||||
strongSelf.sendMessages(transformedMessages)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
messages = strongSelf.transformEnqueueMessages(messages)
|
||||
strongSelf.sendMessages(messages)
|
||||
}
|
||||
})
|
||||
}
|
||||
return true
|
||||
}, sendBotContextResultAsGif: { [weak self] collection, result, sourceView, sourceRect, silentPosting, resetTextInputState in
|
||||
|
|
@ -4826,7 +4831,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: true, recommendedChannels: false))
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: true, section: nil))
|
||||
})))
|
||||
|
||||
if canViewStats {
|
||||
|
|
@ -5157,7 +5162,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
chatInfoButtonItem.target = self
|
||||
chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)
|
||||
self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo(expandAvatar: true, recommendedChannels: false), buttonItem: chatInfoButtonItem)
|
||||
self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo(expandAvatar: true, section: nil), buttonItem: chatInfoButtonItem)
|
||||
|
||||
self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor)))
|
||||
self.moreInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: UIBarButtonItem(customDisplayNode: self.moreBarButton)!)
|
||||
|
|
@ -5179,7 +5184,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
|
||||
self.navigationItem.titleView = self.chatTitleView
|
||||
self.chatTitleView?.pressed = { [weak self] in
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: false, recommendedChannels: false))
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: false, section: nil))
|
||||
}
|
||||
|
||||
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
|
||||
|
|
@ -5779,10 +5784,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
|
||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.flags.contains(.isCreator) || peer.adminRights != nil {
|
||||
|
||||
} else {
|
||||
sendPaidMessageStars = cachedData.sendPaidMessageStars
|
||||
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel {
|
||||
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
|
||||
|
||||
} else {
|
||||
sendPaidMessageStars = channel.sendPaidMessageStars
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,14 +89,13 @@ final class ChatFeePanelNode: ASDisplayNode {
|
|||
if self.theme !== interfaceState.theme {
|
||||
self.theme = interfaceState.theme
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
//TODO:localize
|
||||
self.removeTextNode.attributedText = NSAttributedString(string: "Remove Fee", font: Font.regular(17.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
|
||||
self.removeTextNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PaidMessageFee_RemoveFee, font: Font.regular(17.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
|
||||
}
|
||||
|
||||
let paidMessageStars = interfaceState.contactStatus?.peerStatusSettings?.paidMessageStars?.value ?? 0
|
||||
|
||||
if let peer = interfaceState.renderedPeer?.peer.flatMap(EnginePeer.init) {
|
||||
let attributedText = NSMutableAttributedString(string: "\(peer.compactDisplayTitle) must pay ⭐️\(paidMessageStars) for each message to you.", font: Font.regular(12.0), textColor: interfaceState.theme.rootController.navigationBar.secondaryTextColor)
|
||||
let attributedText = NSMutableAttributedString(string: interfaceState.strings.Chat_PaidMessageFee_Text(peer.compactDisplayTitle, "⭐️\(paidMessageStars)").string, font: Font.regular(12.0), textColor: interfaceState.theme.rootController.navigationBar.secondaryTextColor)
|
||||
let range = (attributedText.string as NSString).range(of: "⭐️")
|
||||
if range.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: range)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ChatMessageItemCommon
|
|||
import TextFormat
|
||||
import Markdown
|
||||
import Display
|
||||
import TelegramStringFormatting
|
||||
|
||||
struct ChatHistoryEntriesForViewState {
|
||||
private var messageStableIdToLocalId: [UInt32: Int64] = [:]
|
||||
|
|
@ -67,17 +68,19 @@ func chatHistoryEntriesForView(
|
|||
var entries: [ChatHistoryEntry] = []
|
||||
var adminRanks: [PeerId: CachedChannelAdminRank] = [:]
|
||||
var stickersEnabled = true
|
||||
var channelPeer: Peer?
|
||||
var chatPeer: Peer?
|
||||
if let peerId = location.peerId, peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
for additionalEntry in view.additionalData {
|
||||
if case let .cacheEntry(id, data) = additionalEntry {
|
||||
if id == cachedChannelAdminRanksEntryId(peerId: peerId), let data = data?.get(CachedChannelAdminRanks.self) {
|
||||
adminRanks = data.ranks
|
||||
}
|
||||
} else if case let .peer(_, peer) = additionalEntry, let channel = peer as? TelegramChannel, !channel.flags.contains(.isGigagroup) {
|
||||
channelPeer = channel
|
||||
if let defaultBannedRights = channel.defaultBannedRights, defaultBannedRights.flags.contains(.banSendStickers) {
|
||||
stickersEnabled = false
|
||||
} else if case let .peer(_, peer) = additionalEntry {
|
||||
chatPeer = peer
|
||||
if let channel = peer as? TelegramChannel, !channel.flags.contains(.isGigagroup) {
|
||||
if let defaultBannedRights = channel.defaultBannedRights, defaultBannedRights.flags.contains(.banSendStickers) {
|
||||
stickersEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +90,7 @@ func chatHistoryEntriesForView(
|
|||
if (associatedData.subject?.isService ?? false) {
|
||||
|
||||
} else {
|
||||
if let peer = channelPeer as? TelegramChannel, case .broadcast = peer.info, case .member = peer.participationStatus, !peer.flags.contains(.isCreator) {
|
||||
if let peer = chatPeer as? TelegramChannel, case .broadcast = peer.info, case .member = peer.participationStatus, !peer.flags.contains(.isCreator) {
|
||||
joinMessage = Message(
|
||||
stableId: UInt32.max - 1000,
|
||||
stableVersion: 0,
|
||||
|
|
@ -103,7 +106,7 @@ func chatHistoryEntriesForView(
|
|||
localTags: [],
|
||||
customTags: [],
|
||||
forwardInfo: nil,
|
||||
author: channelPeer,
|
||||
author: chatPeer,
|
||||
text: "",
|
||||
attributes: [],
|
||||
media: [TelegramMediaAction(action: .joinedChannel)],
|
||||
|
|
@ -436,10 +439,92 @@ func chatHistoryEntriesForView(
|
|||
} else if let cachedPeerData = cachedPeerData as? CachedUserData {
|
||||
if let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty {
|
||||
entries.insert(.ChatInfoEntry(.botInfo(title: presentationData.strings.Bot_DescriptionTitle, text: botInfo.description, photo: botInfo.photo, video: botInfo.video), presentationData), at: 0)
|
||||
} else if let peerStatusSettings = cachedPeerData.peerStatusSettings, peerStatusSettings.registrationDate != nil || peerStatusSettings.phoneCountry != nil || peerStatusSettings.locationCountry != nil {
|
||||
} else if let peerStatusSettings = cachedPeerData.peerStatusSettings, peerStatusSettings.registrationDate != nil || peerStatusSettings.phoneCountry != nil {
|
||||
if peerStatusSettings.flags.contains(.canAddContact) || peerStatusSettings.flags.contains(.canReport) || peerStatusSettings.flags.contains(.canBlock) {
|
||||
let title = chatPeer.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) ?? ""
|
||||
entries.insert(.ChatInfoEntry(.userInfo(title: title, registrationDate: peerStatusSettings.registrationDate, phoneCountry: peerStatusSettings.phoneCountry, locationCountry: peerStatusSettings.locationCountry, groupsInCommon: []), presentationData), at: 0)
|
||||
|
||||
if let chatPeer, let photoChangeDate = peerStatusSettings.photoChangeDate, photoChangeDate > 0 {
|
||||
let timeText = stringForIntervalSinceUpdateAction(strings: presentationData.strings, value: photoChangeDate)
|
||||
let text = presentationData.strings.Chat_NonContactUser_UpdatedPhoto(timeText)
|
||||
var entities: [MessageTextEntity] = []
|
||||
for range in text.ranges {
|
||||
entities.append(MessageTextEntity(range: range.range.lowerBound ..< range.range.upperBound, type: .Bold))
|
||||
}
|
||||
let message = Message(
|
||||
stableId: UInt32.max - 1001,
|
||||
stableVersion: 0,
|
||||
id: MessageId(peerId: chatPeer.id, namespace: Namespaces.Message.Local, id: -1),
|
||||
globallyUniqueId: nil,
|
||||
groupingKey: nil,
|
||||
groupInfo: nil,
|
||||
threadId: nil,
|
||||
timestamp: 2,
|
||||
flags: [.Incoming],
|
||||
tags: [],
|
||||
globalTags: [],
|
||||
localTags: [],
|
||||
customTags: [],
|
||||
forwardInfo: nil,
|
||||
author: chatPeer,
|
||||
text: "",
|
||||
attributes: [],
|
||||
media: [TelegramMediaAction(action: .customText(
|
||||
text: text.string,
|
||||
entities: entities,
|
||||
additionalAttributes: nil
|
||||
))],
|
||||
peers: SimpleDictionary<PeerId, Peer>(),
|
||||
associatedMessages: SimpleDictionary<MessageId, Message>(),
|
||||
associatedMessageIds: [],
|
||||
associatedMedia: [:],
|
||||
associatedThreadInfo: nil,
|
||||
associatedStories: [:]
|
||||
)
|
||||
entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)), at: 0)
|
||||
}
|
||||
|
||||
if let chatPeer, let nameChangeDate = peerStatusSettings.nameChangeDate, nameChangeDate > 0 {
|
||||
let timeText = stringForIntervalSinceUpdateAction(strings: presentationData.strings, value: nameChangeDate)
|
||||
let text = presentationData.strings.Chat_NonContactUser_UpdatedName(timeText)
|
||||
var entities: [MessageTextEntity] = []
|
||||
for range in text.ranges {
|
||||
entities.append(MessageTextEntity(range: range.range.lowerBound ..< range.range.upperBound, type: .Bold))
|
||||
}
|
||||
let message = Message(
|
||||
stableId: UInt32.max - 1002,
|
||||
stableVersion: 0,
|
||||
id: MessageId(peerId: chatPeer.id, namespace: Namespaces.Message.Local, id: -2),
|
||||
globallyUniqueId: nil,
|
||||
groupingKey: nil,
|
||||
groupInfo: nil,
|
||||
threadId: nil,
|
||||
timestamp: 1,
|
||||
flags: [.Incoming],
|
||||
tags: [],
|
||||
globalTags: [],
|
||||
localTags: [],
|
||||
customTags: [],
|
||||
forwardInfo: nil,
|
||||
author: chatPeer,
|
||||
text: "",
|
||||
attributes: [],
|
||||
media: [TelegramMediaAction(action: .customText(
|
||||
text: text.string,
|
||||
entities: entities,
|
||||
additionalAttributes: nil
|
||||
))],
|
||||
peers: SimpleDictionary<PeerId, Peer>(),
|
||||
associatedMessages: SimpleDictionary<MessageId, Message>(),
|
||||
associatedMessageIds: [],
|
||||
associatedMedia: [:],
|
||||
associatedThreadInfo: nil,
|
||||
associatedStories: [:]
|
||||
)
|
||||
entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)), at: 0)
|
||||
}
|
||||
|
||||
if let peer = chatPeer.flatMap(EnginePeer.init) {
|
||||
entries.insert(.ChatInfoEntry(.userInfo(peer: peer, verification: cachedPeerData.verification, registrationDate: peerStatusSettings.registrationDate, phoneCountry: peerStatusSettings.phoneCountry, groupsInCommonCount: cachedPeerData.commonGroupCount), presentationData), at: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
let item: ListViewItem
|
||||
switch mode {
|
||||
case .bubbles:
|
||||
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
|
||||
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10)
|
||||
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
|
||||
let displayHeader: Bool
|
||||
switch displayHeaders {
|
||||
|
|
@ -260,8 +260,8 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
switch data {
|
||||
case let .botInfo(title, text, photo, video):
|
||||
item = ChatBotInfoItem(title: title, text: text, photo: photo, video: video, controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
|
||||
case let .userInfo(title, registrationDate, phoneCountry, locationCountry, groupsInCommon):
|
||||
item = ChatUserInfoItem(title: title, registrationDate: registrationDate, phoneCountry: phoneCountry, locationCountry: locationCountry, groupsInCommon: groupsInCommon, controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
|
||||
case let .userInfo(peer, verification, registrationDate, phoneCountry, groupsInCommonCount):
|
||||
item = ChatUserInfoItem(peer: peer, verification: verification, registrationDate: registrationDate, phoneCountry: phoneCountry, groupsInCommonCount: groupsInCommonCount, controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
|
||||
}
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||
case let .SearchEntry(theme, strings):
|
||||
|
|
@ -284,7 +284,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
let item: ListViewItem
|
||||
switch mode {
|
||||
case .bubbles:
|
||||
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
|
||||
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10)
|
||||
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
|
||||
let displayHeader: Bool
|
||||
switch displayHeaders {
|
||||
|
|
@ -317,8 +317,8 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
|
|||
switch data {
|
||||
case let .botInfo(title, text, photo, video):
|
||||
item = ChatBotInfoItem(title: title, text: text, photo: photo, video: video, controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
|
||||
case let .userInfo(title, registrationDate, phoneCountry, locationCountry, groupsInCommon):
|
||||
item = ChatUserInfoItem(title: title, registrationDate: registrationDate, phoneCountry: phoneCountry, locationCountry: locationCountry, groupsInCommon: groupsInCommon, controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
|
||||
case let .userInfo(peer, verification, registrationDate, phoneCountry, groupsInCommonCount):
|
||||
item = ChatUserInfoItem(peer: peer, verification: verification, registrationDate: registrationDate, phoneCountry: phoneCountry, groupsInCommonCount: groupsInCommonCount, controllerInteraction: controllerInteraction, presentationData: presentationData, context: context)
|
||||
}
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||
case let .SearchEntry(theme, strings):
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
|||
}
|
||||
|
||||
if let view = self.scrubber.view {
|
||||
view.frame = CGRect(origin: CGPoint(x: max(leftInset + 45.0, floorToScreenPixels((width - view.bounds.width) / 2.0)), y: 7.0 - UIScreenPixel), size: view.bounds.size)
|
||||
view.frame = CGRect(origin: CGPoint(x: min(width - innerSize.width - view.bounds.width, max(leftInset + 45.0, floorToScreenPixels((width - view.bounds.width) / 2.0))), y: 7.0 - UIScreenPixel), size: view.bounds.size)
|
||||
}
|
||||
|
||||
let panelHeight = defaultHeight(metrics: metrics)
|
||||
|
|
|
|||
|
|
@ -1895,7 +1895,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||
peerUpdated = true
|
||||
}
|
||||
|
||||
if peerUpdated || previousState?.interfaceState.silentPosting != interfaceState.interfaceState.silentPosting || themeUpdated || !self.initializedPlaceholder || previousState?.keyboardButtonsMessage?.id != interfaceState.keyboardButtonsMessage?.id || previousState?.keyboardButtonsMessage?.visibleReplyMarkupPlaceholder != interfaceState.keyboardButtonsMessage?.visibleReplyMarkupPlaceholder || dismissedButtonMessageUpdated || replyMessageUpdated || (previousState?.interfaceState.editMessage == nil) != (interfaceState.interfaceState.editMessage == nil) || previousState?.forumTopicData != interfaceState.forumTopicData || previousState?.replyMessage?.id != interfaceState.replyMessage?.id {
|
||||
if peerUpdated || previousState?.interfaceState.silentPosting != interfaceState.interfaceState.silentPosting || themeUpdated || !self.initializedPlaceholder || previousState?.keyboardButtonsMessage?.id != interfaceState.keyboardButtonsMessage?.id || previousState?.keyboardButtonsMessage?.visibleReplyMarkupPlaceholder != interfaceState.keyboardButtonsMessage?.visibleReplyMarkupPlaceholder || dismissedButtonMessageUpdated || replyMessageUpdated || (previousState?.interfaceState.editMessage == nil) != (interfaceState.interfaceState.editMessage == nil) || previousState?.forumTopicData != interfaceState.forumTopicData || previousState?.replyMessage?.id != interfaceState.replyMessage?.id || previousState?.sendPaidMessageStars != interfaceState.sendPaidMessageStars {
|
||||
self.initializedPlaceholder = true
|
||||
|
||||
var placeholder: String = ""
|
||||
|
|
|
|||
|
|
@ -3314,6 +3314,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return StarsWithdrawScreen(context: context, mode: .withdraw(stats), completion: completion)
|
||||
}
|
||||
|
||||
public func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController {
|
||||
return StarsWithdrawScreen(context: context, mode: .accountWithdraw, completion: completion)
|
||||
}
|
||||
|
||||
public func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController {
|
||||
return StarsTransactionScreen(context: context, subject: .gift(message))
|
||||
}
|
||||
|
|
@ -3474,6 +3478,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||
var forumTopicThread: ChatReplyThreadMessage?
|
||||
var switchToRecommendedChannels = false
|
||||
var switchToGifts = false
|
||||
var switchToGroupsInCommon = false
|
||||
switch mode {
|
||||
case let .forumTopic(thread):
|
||||
forumTopicThread = thread
|
||||
|
|
@ -3481,10 +3486,12 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||
switchToRecommendedChannels = true
|
||||
case .gifts:
|
||||
switchToGifts = true
|
||||
case .groupsInCommon:
|
||||
switchToGroupsInCommon = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGifts: switchToGifts)
|
||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGifts: switchToGifts, switchToGroupsInCommon: switchToGroupsInCommon)
|
||||
} else if peer is TelegramUser {
|
||||
var nearbyPeerDistance: Int32?
|
||||
var reactionSourceMessageId: MessageId?
|
||||
|
|
@ -3493,6 +3500,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||
var forumTopicThread: ChatReplyThreadMessage?
|
||||
var isMyProfile = false
|
||||
var switchToGifts = false
|
||||
var switchToGroupsInCommon = false
|
||||
|
||||
switch mode {
|
||||
case let .nearbyPeer(distance):
|
||||
|
|
@ -3514,10 +3522,12 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
|||
case .myProfileGifts:
|
||||
isMyProfile = true
|
||||
switchToGifts = true
|
||||
case .groupsInCommon:
|
||||
switchToGroupsInCommon = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, switchToGifts: switchToGifts)
|
||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, switchToGifts: switchToGifts, switchToGroupsInCommon: switchToGroupsInCommon)
|
||||
} else if peer is TelegramSecretChat {
|
||||
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue