mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
44b0464161
commit
ef28f034d8
47 changed files with 1703 additions and 411 deletions
|
|
@ -10637,3 +10637,50 @@ Sorry for the inconvenience.";
|
|||
"BoostGift.WinnersInfo" = "Choose whether to make the list of winners public when the giveaway ends.";
|
||||
|
||||
"Story.ViewList.TitleReactions" = "Reactions";
|
||||
|
||||
"Wallpaper.ApplyForChannel" = "Apply For This Channel";
|
||||
|
||||
"Notification.ChannelChangedWallpaper" = "Channel set a new wallpaper";
|
||||
|
||||
"Chat.Giveaway.Message.WinnersSelectedTitle.One" = "Winner Selected!";
|
||||
"Chat.Giveaway.Message.WinnersSelectedTitle.Many" = "Winners Selected!";
|
||||
"Chat.Giveaway.Message.WinnersSelectedText_1" = "**%@** winner of the [Giveaway]() was randomly selected by Telegram.";
|
||||
"Chat.Giveaway.Message.WinnersSelectedText_any" = "**%@** winners of the [Giveaway]() were randomly selected by Telegram.";
|
||||
|
||||
"Chat.Giveaway.Message.WinnersTitle.One" = "Winner";
|
||||
"Chat.Giveaway.Message.WinnersTitle.Many" = "Winners";
|
||||
|
||||
"Chat.Giveaway.Message.WinnersMore_1" = "and %@ more!";
|
||||
"Chat.Giveaway.Message.WinnersMore_any" = "and %@ more!";
|
||||
|
||||
"Chat.Giveaway.Message.WinnersInfo.One" = "The winner received their gift link in a private message.";
|
||||
"Chat.Giveaway.Message.WinnersInfo.Many" = "All winners received gift links in private messages.";
|
||||
|
||||
"ChannelBoost.Wallpaper" = "Set Wallpaper";
|
||||
"ChannelBoost.WallpaperText" = "Your channel needs **Level %1$@** to set custom wallpaper.\n\nAsk your **Premium** subscribers to boost your channel with this link:";
|
||||
|
||||
"Settings.PremiumGift" = "Premium Gifting";
|
||||
|
||||
"Story.ViewList.ContextSortChannelInfo" = "Choose the order for the list of reactions.";
|
||||
|
||||
"Premium.Gift.MultipleDescription" = "Get **%1$@**%2$@ access to exclusive features with **Telegram Premium**.";
|
||||
|
||||
"Premium.Gift.NamesAndMore_1" = " and %@ more";
|
||||
"Premium.Gift.NamesAndMore_any" = " and %@ more";
|
||||
|
||||
"Premium.Gift.YouWillReceiveBoosts_1" = "You will receive []() **%@ Boost**!";
|
||||
"Premium.Gift.YouWillReceiveBoosts_any" = "You will receive []() **%@ Boosts**!";
|
||||
|
||||
"Premium.Gift.ContactSelection.Title" = "Gift Premium";
|
||||
"Premium.Gift.ContactSelection.Placeholder" = "Search people to gift Premium to...";
|
||||
"Premium.Gift.ContactSelection.Proceed" = "Proceed";
|
||||
"Premium.Gift.ContactSelection.FrequentContacts" = "FREQUENT CONTACTS";
|
||||
"Premium.Gift.ContactSelection.MaximumReached" = "You can select up to %@ users.";
|
||||
|
||||
"Premium.Gift.GiftMultipleSubscriptionsFormat" = "%1$@ for %2$@";
|
||||
"Premium.Gift.GiftMultipleSubscriptions_1" = "Gift %@ Subscription";
|
||||
"Premium.Gift.GiftMultipleSubscriptions_any" = "Gift %@ Subscriptions";
|
||||
|
||||
"Message.GiveawayEndedWinners_1" = "%@ winner of the giveaway was randomly selected by Telegram";
|
||||
"Message.GiveawayEndedWinners_any" = "%@ winners of the giveaway was randomly selected by Telegram";
|
||||
"Message.GiveawayEndedNoWinners" = "Due to the giveaway terms, no winners could be selected by Telegram";
|
||||
|
|
|
|||
|
|
@ -1101,7 +1101,7 @@ public protocol AccountContext: AnyObject {
|
|||
|
||||
public struct PremiumConfiguration {
|
||||
public static var defaultValue: PremiumConfiguration {
|
||||
return PremiumConfiguration(isPremiumDisabled: false, showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, giveawayGiftsPurchaseAvailable: false, boostsPerGiftCount: 3, minChannelNameColorLevel: 5, audioTransciptionTrialMaxDuration: 300, audioTransciptionTrialCount: 2)
|
||||
return PremiumConfiguration(isPremiumDisabled: false, showPremiumGiftInAttachMenu: false, showPremiumGiftInTextField: false, giveawayGiftsPurchaseAvailable: false, boostsPerGiftCount: 3, minChannelNameColorLevel: 5, audioTransciptionTrialMaxDuration: 300, audioTransciptionTrialCount: 2, minChannelWallpaperLevel: 5)
|
||||
}
|
||||
|
||||
public let isPremiumDisabled: Bool
|
||||
|
|
@ -1112,8 +1112,9 @@ public struct PremiumConfiguration {
|
|||
public let minChannelNameColorLevel: Int32
|
||||
public let audioTransciptionTrialMaxDuration: Int32
|
||||
public let audioTransciptionTrialCount: Int32
|
||||
public let minChannelWallpaperLevel: Int32
|
||||
|
||||
fileprivate init(isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, giveawayGiftsPurchaseAvailable: Bool, boostsPerGiftCount: Int32, minChannelNameColorLevel: Int32, audioTransciptionTrialMaxDuration: Int32, audioTransciptionTrialCount: Int32) {
|
||||
fileprivate init(isPremiumDisabled: Bool, showPremiumGiftInAttachMenu: Bool, showPremiumGiftInTextField: Bool, giveawayGiftsPurchaseAvailable: Bool, boostsPerGiftCount: Int32, minChannelNameColorLevel: Int32, audioTransciptionTrialMaxDuration: Int32, audioTransciptionTrialCount: Int32, minChannelWallpaperLevel: Int32) {
|
||||
self.isPremiumDisabled = isPremiumDisabled
|
||||
self.showPremiumGiftInAttachMenu = showPremiumGiftInAttachMenu
|
||||
self.showPremiumGiftInTextField = showPremiumGiftInTextField
|
||||
|
|
@ -1122,6 +1123,7 @@ public struct PremiumConfiguration {
|
|||
self.minChannelNameColorLevel = minChannelNameColorLevel
|
||||
self.audioTransciptionTrialMaxDuration = audioTransciptionTrialMaxDuration
|
||||
self.audioTransciptionTrialCount = audioTransciptionTrialCount
|
||||
self.minChannelWallpaperLevel = minChannelWallpaperLevel
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> PremiumConfiguration {
|
||||
|
|
@ -1134,7 +1136,8 @@ public struct PremiumConfiguration {
|
|||
boostsPerGiftCount: Int32(data["boosts_per_sent_gift"] as? Double ?? 3),
|
||||
minChannelNameColorLevel: Int32(data["channel_color_level_min"] as? Double ?? 5),
|
||||
audioTransciptionTrialMaxDuration: Int32(data["transcribe_audio_trial_duration_max"] as? Double ?? 300),
|
||||
audioTransciptionTrialCount: Int32(data["transcribe_audio_trial_weekly_number"] as? Double ?? 2)
|
||||
audioTransciptionTrialCount: Int32(data["transcribe_audio_trial_weekly_number"] as? Double ?? 2),
|
||||
minChannelWallpaperLevel: Int32(data["channel_wallpaper_level_min"] as? Double ?? 5)
|
||||
)
|
||||
} else {
|
||||
return .defaultValue
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ public enum ContactMultiselectionControllerMode {
|
|||
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||
case channelCreation
|
||||
case chatSelection(ChatSelection)
|
||||
case premiumGifting
|
||||
}
|
||||
|
||||
public enum ContactListFilter {
|
||||
|
|
|
|||
|
|
@ -311,6 +311,12 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
|||
} else {
|
||||
messageText = strings.Message_GiveawayStarted
|
||||
}
|
||||
case let results as TelegramMediaGiveawayResults:
|
||||
if results.winnersCount == 0 {
|
||||
messageText = strings.Message_GiveawayEndedNoWinners
|
||||
} else {
|
||||
messageText = strings.Message_GiveawayEndedWinners(results.winnersCount)
|
||||
}
|
||||
case let webpage as TelegramMediaWebpage:
|
||||
if messageText.isEmpty, case let .Loaded(content) = webpage.content {
|
||||
messageText = content.displayUrl
|
||||
|
|
|
|||
|
|
@ -11,16 +11,18 @@ public final class RoundedRectangle: Component {
|
|||
public let cornerRadius: CGFloat
|
||||
public let gradientDirection: GradientDirection
|
||||
public let stroke: CGFloat?
|
||||
public let strokeColor: UIColor?
|
||||
|
||||
public convenience init(color: UIColor, cornerRadius: CGFloat, stroke: CGFloat? = nil) {
|
||||
self.init(colors: [color], cornerRadius: cornerRadius, stroke: stroke)
|
||||
public convenience init(color: UIColor, cornerRadius: CGFloat, stroke: CGFloat? = nil, strokeColor: UIColor? = nil) {
|
||||
self.init(colors: [color], cornerRadius: cornerRadius, stroke: stroke, strokeColor: strokeColor)
|
||||
}
|
||||
|
||||
public init(colors: [UIColor], cornerRadius: CGFloat, gradientDirection: GradientDirection = .horizontal, stroke: CGFloat? = nil) {
|
||||
public init(colors: [UIColor], cornerRadius: CGFloat, gradientDirection: GradientDirection = .horizontal, stroke: CGFloat? = nil, strokeColor: UIColor? = nil) {
|
||||
self.colors = colors
|
||||
self.cornerRadius = cornerRadius
|
||||
self.gradientDirection = gradientDirection
|
||||
self.stroke = stroke
|
||||
self.strokeColor = strokeColor
|
||||
}
|
||||
|
||||
public static func ==(lhs: RoundedRectangle, rhs: RoundedRectangle) -> Bool {
|
||||
|
|
@ -36,6 +38,9 @@ public final class RoundedRectangle: Component {
|
|||
if lhs.stroke != rhs.stroke {
|
||||
return false
|
||||
}
|
||||
if lhs.strokeColor != rhs.strokeColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -48,11 +53,19 @@ public final class RoundedRectangle: Component {
|
|||
let imageSize = CGSize(width: max(component.stroke ?? 0.0, component.cornerRadius) * 2.0, height: max(component.stroke ?? 0.0, component.cornerRadius) * 2.0)
|
||||
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0.0)
|
||||
if let context = UIGraphicsGetCurrentContext() {
|
||||
context.setFillColor(color.cgColor)
|
||||
if let strokeColor = component.strokeColor {
|
||||
context.setFillColor(strokeColor.cgColor)
|
||||
} else {
|
||||
context.setFillColor(color.cgColor)
|
||||
}
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: imageSize))
|
||||
|
||||
if let stroke = component.stroke, stroke > 0.0 {
|
||||
context.setBlendMode(.clear)
|
||||
if let _ = component.strokeColor {
|
||||
context.setFillColor(color.cgColor)
|
||||
} else {
|
||||
context.setBlendMode(.clear)
|
||||
}
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: imageSize).insetBy(dx: stroke, dy: stroke))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set<EnginePeer.Id>, authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?) -> [ContactListNodeEntry] {
|
||||
private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set<EnginePeer.Id>, authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer]) -> [ContactListNodeEntry] {
|
||||
var entries: [ContactListNodeEntry] = []
|
||||
|
||||
var commonHeader: ListViewItemHeader?
|
||||
|
|
@ -421,7 +421,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||
for i in 0 ..< options.count {
|
||||
entries.append(.option(i, options[i], commonHeader, theme, strings))
|
||||
}
|
||||
case let .natural(options, _):
|
||||
case let .natural(options, _, _):
|
||||
let sortedPeers = peers.sorted(by: { lhs, rhs in
|
||||
let result = EnginePeer.IndexName(lhs.indexName).isLessThan(other: EnginePeer.IndexName(rhs.indexName), ordering: sortOrder)
|
||||
if result == .orderedSame {
|
||||
|
|
@ -513,6 +513,27 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||
}
|
||||
}
|
||||
|
||||
var existingPeerIds = Set<ContactListPeerId>()
|
||||
if !topPeers.isEmpty {
|
||||
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(strings.Premium_Gift_ContactSelection_FrequentContacts, AnyHashable(0)), theme: theme, strings: strings)
|
||||
|
||||
var index: Int = 0
|
||||
for peer in topPeers.prefix(15) {
|
||||
existingPeerIds.insert(.peer(peer.id))
|
||||
|
||||
let selection: ContactsPeerItemSelection
|
||||
if let selectionState = selectionState {
|
||||
selection = .selectable(selected: selectionState.selectedPeerIndices[.peer(peer.id)] != nil)
|
||||
} else {
|
||||
selection = .none
|
||||
}
|
||||
|
||||
let presence = presences[peer.id]
|
||||
entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, nil))
|
||||
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
if let storySubscriptions {
|
||||
let _ = storySubscriptions
|
||||
|
|
@ -528,7 +549,6 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||
|
||||
var index: Int = 0
|
||||
|
||||
var existingPeerIds = Set<ContactListPeerId>()
|
||||
if let selectionState = selectionState {
|
||||
for peer in selectionState.foundPeers {
|
||||
if existingPeerIds.contains(peer.id) {
|
||||
|
|
@ -657,7 +677,7 @@ private struct ContactsListNodeTransition {
|
|||
|
||||
public enum ContactListPresentation {
|
||||
case orderedByPresence(options: [ContactListAdditionalOption])
|
||||
case natural(options: [ContactListAdditionalOption], includeChatList: Bool)
|
||||
case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: Bool)
|
||||
case search(signal: Signal<String, NoError>, searchChatList: Bool, searchDeviceContacts: Bool, searchGroups: Bool, searchChannels: Bool, globalSearch: Bool)
|
||||
|
||||
public var sortOrder: ContactsSortOrder? {
|
||||
|
|
@ -983,9 +1003,11 @@ public final class ContactListNode: ASDisplayNode {
|
|||
|> mapToSignal { presentation in
|
||||
var generateSections = false
|
||||
var includeChatList = false
|
||||
if case let .natural(_, includeChatListValue) = presentation {
|
||||
var displayTopPeers = false
|
||||
if case let .natural(_, includeChatListValue, displayTopPeersValue) = presentation {
|
||||
generateSections = true
|
||||
includeChatList = includeChatListValue
|
||||
displayTopPeers = displayTopPeersValue
|
||||
}
|
||||
|
||||
if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels, globalSearch) = presentation {
|
||||
|
|
@ -1214,7 +1236,7 @@ public final class ContactListNode: ASDisplayNode {
|
|||
peers.append(.deviceContact(stableId, contact.0))
|
||||
}
|
||||
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil)
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [])
|
||||
let previous = previousEntries.swap(entries)
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch))
|
||||
}
|
||||
|
|
@ -1274,8 +1296,24 @@ public final class ContactListNode: ASDisplayNode {
|
|||
chatListSignal = .single([])
|
||||
}
|
||||
|
||||
return (combineLatest(self.contactPeersViewPromise.get(), chatListSignal, selectionStateSignal, presentationDataPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get(), self.storySubscriptions.get())
|
||||
|> mapToQueue { view, chatListPeers, selectionState, presentationData, authorizationStatus, warningSuppressed, storySubscriptions -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let recentPeers: Signal<RecentPeers, NoError>
|
||||
if displayTopPeers {
|
||||
recentPeers = context.engine.peers.recentPeers()
|
||||
} else {
|
||||
recentPeers = .single(.disabled)
|
||||
}
|
||||
|
||||
return (combineLatest(
|
||||
self.contactPeersViewPromise.get(),
|
||||
chatListSignal,
|
||||
selectionStateSignal,
|
||||
presentationDataPromise.get(),
|
||||
contactsAuthorization.get(),
|
||||
contactsWarningSuppressed.get(),
|
||||
self.storySubscriptions.get(),
|
||||
recentPeers
|
||||
)
|
||||
|> mapToQueue { view, chatListPeers, selectionState, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, recentPeers -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) })
|
||||
for (peer, memberCount) in chatListPeers {
|
||||
|
|
@ -1312,11 +1350,16 @@ public final class ContactListNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
var topPeers: [EnginePeer] = []
|
||||
if case let .peers(peers) = recentPeers {
|
||||
topPeers = peers.map(EnginePeer.init)
|
||||
}
|
||||
|
||||
var isEmpty = false
|
||||
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
|
||||
isEmpty = true
|
||||
}
|
||||
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions)
|
||||
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers)
|
||||
let previous = previousEntries.swap(entries)
|
||||
let previousSelection = previousSelectionState.swap(selectionState)
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||
case .presence:
|
||||
return .orderedByPresence(options: options)
|
||||
case .natural:
|
||||
return .natural(options: options, includeChatList: false)
|
||||
return .natural(options: options, includeChatList: false, topPeers: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||
"//submodules/InvisibleInkDustNode",
|
||||
"//submodules/AlertUI",
|
||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -10,24 +10,29 @@ import LegacyComponents
|
|||
import AvatarNode
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MergedAvatarsNode
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
|
||||
class GiftAvatarComponent: Component {
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer?
|
||||
let theme: PresentationTheme
|
||||
let peers: [EnginePeer]
|
||||
let isVisible: Bool
|
||||
let hasIdleAnimations: Bool
|
||||
|
||||
init(context: AccountContext, peer: EnginePeer?, isVisible: Bool, hasIdleAnimations: Bool) {
|
||||
init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], isVisible: Bool, hasIdleAnimations: Bool) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.theme = theme
|
||||
self.peers = peers
|
||||
self.isVisible = isVisible
|
||||
self.hasIdleAnimations = hasIdleAnimations
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftAvatarComponent, rhs: GiftAvatarComponent) -> Bool {
|
||||
return lhs.peer == rhs.peer && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations
|
||||
return lhs.peers == rhs.peers && lhs.theme === rhs.theme && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations
|
||||
}
|
||||
|
||||
final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
|
||||
|
|
@ -52,6 +57,10 @@ class GiftAvatarComponent: Component {
|
|||
|
||||
private let sceneView: SCNView
|
||||
private let avatarNode: ImageNode
|
||||
private var mergedAvatarsNode: MergedAvatarsNode?
|
||||
|
||||
private let badgeBackground = ComponentView<Empty>()
|
||||
private let badge = ComponentView<Empty>()
|
||||
|
||||
private var previousInteractionTimestamp: Double = 0.0
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
|
|
@ -242,11 +251,75 @@ class GiftAvatarComponent: Component {
|
|||
}
|
||||
|
||||
self.hasIdleAnimations = component.hasIdleAnimations
|
||||
let avatarSize = CGSize(width: 100.0, height: 100.0)
|
||||
if let peer = component.peer {
|
||||
self.avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: avatarSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true))
|
||||
|
||||
if component.peers.count > 1 {
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
|
||||
let mergedAvatarsNode: MergedAvatarsNode
|
||||
if let current = self.mergedAvatarsNode {
|
||||
mergedAvatarsNode = current
|
||||
} else {
|
||||
mergedAvatarsNode = MergedAvatarsNode()
|
||||
mergedAvatarsNode.isUserInteractionEnabled = false
|
||||
self.addSubview(mergedAvatarsNode.view)
|
||||
self.mergedAvatarsNode = mergedAvatarsNode
|
||||
}
|
||||
|
||||
mergedAvatarsNode.update(context: component.context, peers: Array(component.peers.map { $0._asPeer() }.prefix(3)), synchronousLoad: false, imageSize: avatarSize.width, imageSpacing: 30.0, borderWidth: 2.0, avatarFontSize: 26.0)
|
||||
let avatarsSize = CGSize(width: avatarSize.width + 60.0, height: avatarSize.height)
|
||||
mergedAvatarsNode.updateLayout(size: avatarsSize)
|
||||
mergedAvatarsNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarsSize.width) / 2.0), y: 113.0 - avatarSize.height / 2.0), size: avatarsSize)
|
||||
self.avatarNode.isHidden = true
|
||||
} else {
|
||||
self.mergedAvatarsNode?.view.removeFromSuperview()
|
||||
self.mergedAvatarsNode = nil
|
||||
self.avatarNode.isHidden = false
|
||||
|
||||
let avatarSize = CGSize(width: 100.0, height: 100.0)
|
||||
if let peer = component.peers.first {
|
||||
self.avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: avatarSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true))
|
||||
}
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarSize.width) / 2.0), y: 113.0 - avatarSize.height / 2.0), size: avatarSize)
|
||||
}
|
||||
|
||||
if component.peers.count > 3 {
|
||||
let badgeTextSize = self.badge.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "+\(component.peers.count - 3)", font: Font.with(size: 10.0, design: .round, weight: .semibold), textColor: component.theme.list.itemCheckColors.foregroundColor))
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
|
||||
let lineWidth = 1.0 + UIScreenPixel
|
||||
let badgeSize = CGSize(width: max(17.0, badgeTextSize.width + 7.0) + lineWidth * 2.0, height: 17.0 + lineWidth * 2.0)
|
||||
let _ = self.badgeBackground.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
RoundedRectangle(color: component.theme.list.itemCheckColors.fillColor, cornerRadius: badgeSize.height / 2.0, stroke: lineWidth, strokeColor: component.theme.list.blocksBackgroundColor)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: badgeSize
|
||||
)
|
||||
|
||||
if let badgeTextView = self.badge.view, let badgeBackgroundView = self.badgeBackground.view {
|
||||
if badgeBackgroundView.superview == nil {
|
||||
self.addSubview(badgeBackgroundView)
|
||||
self.addSubview(badgeTextView)
|
||||
}
|
||||
|
||||
let avatarsSize = CGSize(width: 120.0, height: 60.0)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width + avatarsSize.width) / 2.0) - 19.0 - lineWidth, y: 113.0 + avatarsSize.height / 2.0 - 15.0 - lineWidth), size: badgeSize)
|
||||
badgeBackgroundView.frame = backgroundFrame
|
||||
badgeTextView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeTextSize.width / 2.0), y: floorToScreenPixels(backgroundFrame.midY - badgeTextSize.height / 2.0) - UIScreenPixel), size: badgeTextSize)
|
||||
}
|
||||
} else {
|
||||
self.badge.view?.removeFromSuperview()
|
||||
self.badgeBackground.view?.removeFromSuperview()
|
||||
}
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarSize.width) / 2.0), y: 63.0), size: avatarSize)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,19 +31,66 @@ public func presentGiveawayInfoController(
|
|||
guard let message else {
|
||||
return
|
||||
}
|
||||
guard let giveaway = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway else {
|
||||
return
|
||||
|
||||
let giveaway = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway
|
||||
let giveawayResults = message.media.first(where: { $0 is TelegramMediaGiveawayResults }) as? TelegramMediaGiveawayResults
|
||||
|
||||
var channelPeerId: EnginePeer.Id?
|
||||
if let giveaway {
|
||||
if let peerId = giveaway.channelPeerIds.first {
|
||||
channelPeerId = peerId
|
||||
}
|
||||
} else if let _ = giveawayResults {
|
||||
channelPeerId = message.author?.id
|
||||
}
|
||||
|
||||
var quantity: Int32 = 0
|
||||
if let giveaway {
|
||||
quantity = giveaway.quantity
|
||||
} else if let giveawayResults {
|
||||
quantity = giveawayResults.winnersCount + giveawayResults.unclaimedCount
|
||||
}
|
||||
|
||||
var months: Int32 = 0
|
||||
if let giveaway {
|
||||
months = giveaway.months
|
||||
} else if let giveawayResults {
|
||||
months = giveawayResults.months
|
||||
}
|
||||
|
||||
var prizeDescription: String?
|
||||
if let giveaway {
|
||||
prizeDescription = giveaway.prizeDescription
|
||||
} else if let giveawayResults {
|
||||
prizeDescription = giveawayResults.prizeDescription
|
||||
}
|
||||
|
||||
var untilDateValue: Int32 = 0
|
||||
if let giveaway {
|
||||
untilDateValue = giveaway.untilDate
|
||||
} else if let _ = giveawayResults {
|
||||
untilDateValue = message.timestamp
|
||||
}
|
||||
|
||||
var onlyNewSubscribers = false
|
||||
if let giveaway, giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
onlyNewSubscribers = true
|
||||
}
|
||||
|
||||
var channelsCount = 1
|
||||
if let giveaway {
|
||||
channelsCount = giveaway.channelPeerIds.count
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var peerName = ""
|
||||
if let peerId = giveaway.channelPeerIds.first, let peer = message.peers[peerId] {
|
||||
if let peerId = channelPeerId, let peer = message.peers[peerId] {
|
||||
peerName = EnginePeer(peer).compactDisplayTitle
|
||||
}
|
||||
|
||||
let timeZone = TimeZone.current
|
||||
let untilDate = stringForDate(timestamp: giveaway.untilDate, timeZone: timeZone, strings: presentationData.strings)
|
||||
let untilDate = stringForDate(timestamp: untilDateValue, timeZone: timeZone, strings: presentationData.strings)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
|
|
@ -56,8 +103,8 @@ public func presentGiveawayInfoController(
|
|||
})]
|
||||
|
||||
var additionalPrizes = ""
|
||||
if let prizeDescription = giveaway.prizeDescription, !prizeDescription.isEmpty {
|
||||
additionalPrizes = "\n\n" + presentationData.strings.Chat_Giveaway_Info_AdditionalPrizes(peerName, "\(giveaway.quantity) \(prizeDescription)").string
|
||||
if let prizeDescription, !prizeDescription.isEmpty {
|
||||
additionalPrizes = "\n\n" + presentationData.strings.Chat_Giveaway_Info_AdditionalPrizes(peerName, "\(quantity) \(prizeDescription)").string
|
||||
}
|
||||
|
||||
switch giveawayInfo {
|
||||
|
|
@ -71,23 +118,23 @@ public func presentGiveawayInfoController(
|
|||
|
||||
let intro: String
|
||||
if case .almostOver = status {
|
||||
intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(giveaway.quantity), presentationData.strings.Chat_Giveaway_Info_Months(giveaway.months)).string
|
||||
intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string
|
||||
} else {
|
||||
intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(giveaway.quantity), presentationData.strings.Chat_Giveaway_Info_Months(giveaway.months)).string
|
||||
intro = presentationData.strings.Chat_Giveaway_Info_OngoingIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string
|
||||
}
|
||||
|
||||
let ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
let randomUsers = presentationData.strings.Chat_Giveaway_Info_RandomUsers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingNewMany(untilDate, randomUsers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1)), startDate).string
|
||||
if onlyNewSubscribers {
|
||||
let randomUsers = presentationData.strings.Chat_Giveaway_Info_RandomUsers(quantity)
|
||||
if channelsCount > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingNewMany(untilDate, randomUsers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(channelsCount - 1)), startDate).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingNew(untilDate, randomUsers, peerName, startDate).string
|
||||
}
|
||||
} else {
|
||||
let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingMany(untilDate, randomSubscribers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1))).string
|
||||
let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity)
|
||||
if channelsCount > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_OngoingMany(untilDate, randomSubscribers, peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(channelsCount - 1))).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_Ongoing(untilDate, randomSubscribers, peerName).string
|
||||
}
|
||||
|
|
@ -96,8 +143,8 @@ public func presentGiveawayInfoController(
|
|||
var participation: String
|
||||
switch status {
|
||||
case .notQualified:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotQualifiedMany(peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1)), untilDate).string
|
||||
if channelsCount > 1 {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotQualifiedMany(peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(channelsCount - 1)), untilDate).string
|
||||
} else {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_NotQualified(peerName, untilDate).string
|
||||
}
|
||||
|
|
@ -116,8 +163,8 @@ public func presentGiveawayInfoController(
|
|||
participation = presentationData.strings.Chat_Giveaway_Info_NotAllowedCountry
|
||||
}
|
||||
case .participating:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_ParticipatingMany(peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(giveaway.channelPeerIds.count - 1))).string
|
||||
if channelsCount > 1 {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_ParticipatingMany(peerName, presentationData.strings.Chat_Giveaway_Info_OtherChannels(Int32(channelsCount - 1))).string
|
||||
} else {
|
||||
participation = presentationData.strings.Chat_Giveaway_Info_Participating(peerName).string
|
||||
}
|
||||
|
|
@ -139,19 +186,19 @@ public func presentGiveawayInfoController(
|
|||
let finishDate = stringForDate(timestamp: finish, timeZone: timeZone, strings: presentationData.strings)
|
||||
title = presentationData.strings.Chat_Giveaway_Info_EndedTitle
|
||||
|
||||
let intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(giveaway.quantity), presentationData.strings.Chat_Giveaway_Info_Months(giveaway.months)).string
|
||||
let intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string
|
||||
|
||||
var ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
let randomUsers = presentationData.strings.Chat_Giveaway_Info_RandomUsers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
if onlyNewSubscribers {
|
||||
let randomUsers = presentationData.strings.Chat_Giveaway_Info_RandomUsers(quantity)
|
||||
if channelsCount > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_EndedNewMany(finishDate, randomUsers, peerName, startDate).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_EndedNew(finishDate, randomUsers, peerName, startDate).string
|
||||
}
|
||||
} else {
|
||||
let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(giveaway.quantity)
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
let randomSubscribers = presentationData.strings.Chat_Giveaway_Info_RandomSubscribers(quantity)
|
||||
if channelsCount > 1 {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_EndedMany(finishDate, randomSubscribers, peerName).string
|
||||
} else {
|
||||
ending = presentationData.strings.Chat_Giveaway_Info_Ended(finishDate, randomSubscribers, peerName).string
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import UniversalMediaPlayer
|
|||
public enum PremiumGiftSource: Equatable {
|
||||
case profile
|
||||
case attachMenu
|
||||
case settings
|
||||
|
||||
var identifier: String? {
|
||||
switch self {
|
||||
|
|
@ -30,6 +31,8 @@ public enum PremiumGiftSource: Equatable {
|
|||
return "profile"
|
||||
case .attachMenu:
|
||||
return "attach"
|
||||
case .settings:
|
||||
return "settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +42,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||
|
||||
let context: AccountContext
|
||||
let source: PremiumGiftSource
|
||||
let peer: EnginePeer?
|
||||
let peers: [EnginePeer]
|
||||
let products: [PremiumGiftProduct]?
|
||||
let selectedProductId: String?
|
||||
|
||||
|
|
@ -47,10 +50,10 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||
let selectProduct: (String) -> Void
|
||||
let buy: () -> Void
|
||||
|
||||
init(context: AccountContext, source: PremiumGiftSource, peer: EnginePeer?, products: [PremiumGiftProduct]?, selectedProductId: String?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void) {
|
||||
init(context: AccountContext, source: PremiumGiftSource, peers: [EnginePeer], products: [PremiumGiftProduct]?, selectedProductId: String?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.source = source
|
||||
self.peer = peer
|
||||
self.peers = peers
|
||||
self.products = products
|
||||
self.selectedProductId = selectedProductId
|
||||
self.present = present
|
||||
|
|
@ -65,7 +68,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||
if lhs.source != rhs.source {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
if lhs.peers != rhs.peers {
|
||||
return false
|
||||
}
|
||||
if lhs.products != rhs.products {
|
||||
|
|
@ -88,6 +91,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||
private var stickersDisposable: Disposable?
|
||||
private var preloadDisposableSet = DisposableSet()
|
||||
|
||||
var cachedBoostIcon: UIImage?
|
||||
|
||||
var price: String?
|
||||
|
||||
init(context: AccountContext, source: PremiumGiftSource) {
|
||||
|
|
@ -186,7 +191,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||
var size = CGSize(width: context.availableSize.width, height: 0.0)
|
||||
|
||||
let overscroll = overscroll.update(
|
||||
component: Rectangle(color: theme.list.plainBackgroundColor),
|
||||
component: Rectangle(color: theme.list.blocksBackgroundColor),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 1000),
|
||||
transition: context.transition
|
||||
)
|
||||
|
|
@ -205,15 +210,63 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
|||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in
|
||||
|
||||
var descriptionString: String = ""
|
||||
if context.component.peers.count > 1 {
|
||||
if context.component.peers.count < 4 {
|
||||
var names = ""
|
||||
for i in 0 ..< context.component.peers.count {
|
||||
if i == 0 {
|
||||
} else if i < context.component.peers.count - 1 {
|
||||
names.append(environment.strings.CreateGroup_PeersTitleDelimeter)
|
||||
} else {
|
||||
names.append(environment.strings.CreateGroup_PeersTitleLastDelimeter)
|
||||
}
|
||||
names.append(context.component.peers[i].compactDisplayTitle)
|
||||
}
|
||||
descriptionString = environment.strings.Premium_Gift_MultipleDescription(names, "").string
|
||||
} else {
|
||||
var names = ""
|
||||
for i in 0 ..< min(3, context.component.peers.count) {
|
||||
if i == 0 {
|
||||
|
||||
} else {
|
||||
names.append(environment.strings.CreateGroup_PeersTitleDelimeter)
|
||||
}
|
||||
names.append(context.component.peers[i].compactDisplayTitle)
|
||||
}
|
||||
let more = environment.strings.Premium_Gift_NamesAndMore(Int32(context.component.peers.count - 3))
|
||||
|
||||
descriptionString = environment.strings.Premium_Gift_MultipleDescription(names, more).string
|
||||
}
|
||||
} else {
|
||||
descriptionString = strings.Premium_Gift_Description(component.peers.first?.compactDisplayTitle ?? "").string
|
||||
}
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.component.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
descriptionString += "\n\n"
|
||||
descriptionString += environment.strings.Premium_Gift_YouWillReceiveBoosts(Int32(context.component.peers.count) * premiumConfiguration.boostsPerGiftCount).replacingOccurrences(of: "[]()", with: " [ ]() ")
|
||||
|
||||
let boostIcon: UIImage
|
||||
if let current = context.state.cachedBoostIcon {
|
||||
boostIcon = current
|
||||
} else {
|
||||
boostIcon = generateImage(CGSize(width: 14.0, height: 20.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
if let cgImage = UIImage(bundleImageName: "Premium/BoostChannel")?.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: .zero, size: size), byTiling: false)
|
||||
}
|
||||
})!
|
||||
context.state.cachedBoostIcon = boostIcon
|
||||
}
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: environment.theme.list.itemAccentColor, additionalAttributes: [NSAttributedString.Key.attachment.rawValue: boostIcon]), linkAttribute: { _ in
|
||||
return nil
|
||||
})
|
||||
let descriptionText = parseMarkdownIntoAttributedString(descriptionString, attributes: markdownAttributes, textAlignment: .center)
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(
|
||||
text: strings.Premium_Gift_Description(component.peer?.compactDisplayTitle ?? "").string,
|
||||
attributes: markdownAttributes
|
||||
),
|
||||
text: .plain(descriptionText),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
|
|
@ -489,7 +542,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peerId: PeerId
|
||||
let peerIds: [EnginePeer.Id]
|
||||
let options: [CachedPremiumGiftOption]
|
||||
let source: PremiumGiftSource
|
||||
let buttonStatePromise: Promise<AttachmentMainButtonState?>
|
||||
|
|
@ -502,7 +555,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peerId: PeerId,
|
||||
peerIds: [EnginePeer.Id],
|
||||
options: [CachedPremiumGiftOption],
|
||||
source: PremiumGiftSource,
|
||||
buttonStatePromise: Promise<AttachmentMainButtonState?>,
|
||||
|
|
@ -514,7 +567,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
completion: @escaping (Int32) -> Void)
|
||||
{
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.peerIds = peerIds
|
||||
self.options = options
|
||||
self.source = source
|
||||
self.buttonStatePromise = buttonStatePromise
|
||||
|
|
@ -530,7 +583,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
if lhs.peerIds != rhs.peerIds {
|
||||
return false
|
||||
}
|
||||
if lhs.options != rhs.options {
|
||||
|
|
@ -544,7 +597,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
|
||||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let peerIds: [EnginePeer.Id]
|
||||
private let options: [CachedPremiumGiftOption]
|
||||
private let source: PremiumGiftSource
|
||||
private let buttonStatePromise: Promise<AttachmentMainButtonState?>
|
||||
|
|
@ -564,7 +617,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
}
|
||||
}
|
||||
|
||||
var peer: EnginePeer?
|
||||
var peers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
var products: [PremiumGiftProduct]?
|
||||
var selectedProductId: String?
|
||||
|
||||
|
|
@ -574,7 +627,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peerId: PeerId,
|
||||
peerIds: [EnginePeer.Id],
|
||||
options: [CachedPremiumGiftOption],
|
||||
source: PremiumGiftSource,
|
||||
buttonStatePromise: Promise<AttachmentMainButtonState?>,
|
||||
|
|
@ -584,7 +637,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
completion: @escaping (Int32) -> Void)
|
||||
{
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.peerIds = peerIds
|
||||
self.options = options
|
||||
self.source = source
|
||||
self.buttonAction = buttonAction
|
||||
|
|
@ -605,8 +658,10 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
self.disposable = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
availableProducts,
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
).start(next: { [weak self] products, peer in
|
||||
context.engine.data.get(
|
||||
EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
)
|
||||
).start(next: { [weak self] products, peers in
|
||||
if let strongSelf = self {
|
||||
var gifts: [PremiumGiftProduct] = []
|
||||
for option in strongSelf.options {
|
||||
|
|
@ -619,7 +674,15 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
if strongSelf.selectedProductId == nil && strongSelf.source != .attachMenu {
|
||||
strongSelf.selectedProductId = strongSelf.products?.first?.id
|
||||
}
|
||||
strongSelf.peer = peer
|
||||
|
||||
var unwrappedPeers: [EnginePeer.Id: EnginePeer] = [:]
|
||||
for (peerId, maybePeer) in peers {
|
||||
if let peer = maybePeer {
|
||||
unwrappedPeers[peerId] = peer
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.peers = unwrappedPeers
|
||||
strongSelf.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
|
|
@ -675,7 +738,9 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
self.updateInProgress(true)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
let purpose: AppStoreTransactionPurpose = .gift(peerId: self.peerId, currency: currency, amount: amount)
|
||||
//TODO:update buy when api arrives
|
||||
|
||||
let purpose: AppStoreTransactionPurpose = .gift(peerId: self.peerIds.first!, currency: currency, amount: amount)
|
||||
let _ = (self.context.engine.payments.canPurchasePremium(purpose: purpose)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] available in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -741,7 +806,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
func makeState() -> State {
|
||||
return State(
|
||||
context: self.context,
|
||||
peerId: self.peerId,
|
||||
peerIds: self.peerIds,
|
||||
options: self.options,
|
||||
source: self.source,
|
||||
buttonStatePromise: self.buttonStatePromise,
|
||||
|
|
@ -823,12 +888,19 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
var peers: [EnginePeer] = []
|
||||
for peerId in context.component.peerIds {
|
||||
if let peer = state.peers[peerId] {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
|
||||
let scrollContent = scrollContent.update(
|
||||
component: ScrollComponent<EnvironmentType>(
|
||||
content: AnyComponent(PremiumGiftScreenContentComponent(
|
||||
context: context.component.context,
|
||||
source: context.component.source,
|
||||
peer: state.peer,
|
||||
peers: peers,
|
||||
products: state.products,
|
||||
selectedProductId: state.selectedProductId,
|
||||
present: context.component.present,
|
||||
|
|
@ -887,7 +959,8 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
let star = star.update(
|
||||
component: GiftAvatarComponent(
|
||||
context: context.component.context,
|
||||
peer: context.state.peer,
|
||||
theme: environment.theme,
|
||||
peers: peers,
|
||||
isVisible: starIsVisible,
|
||||
hasIdleAnimations: state.hasIdleAnimations
|
||||
),
|
||||
|
|
@ -939,9 +1012,18 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
|||
context.component.updateTabBarAlpha(bottomPanelAlpha, .immediate)
|
||||
} else {
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let buttonText: String
|
||||
if context.component.peerIds.count > 1 {
|
||||
let subscriptions = environment.strings.Premium_Gift_GiftMultipleSubscriptions(Int32(context.component.peerIds.count))
|
||||
buttonText = environment.strings.Premium_Gift_GiftMultipleSubscriptionsFormat(subscriptions, price ?? "—").string
|
||||
} else {
|
||||
buttonText = environment.strings.Premium_Gift_GiftSubscription(price ?? "—").string
|
||||
}
|
||||
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: environment.strings.Premium_Gift_GiftSubscription(price ?? "—").string,
|
||||
title: buttonText,
|
||||
theme: SolidRoundedButtonComponent.Theme(
|
||||
backgroundColor: UIColor(rgb: 0x8878ff),
|
||||
backgroundColors: [
|
||||
|
|
@ -1015,7 +1097,7 @@ open class PremiumGiftScreen: ViewControllerComponentContainer {
|
|||
public let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||
private let mainButtonActionSlot = ActionSlot<Void>()
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, options: [CachedPremiumGiftOption], source: PremiumGiftSource, pushController: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
|
||||
public init(context: AccountContext, peerIds: [EnginePeer.Id], options: [CachedPremiumGiftOption], source: PremiumGiftSource, pushController: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
|
||||
self.context = context
|
||||
|
||||
var updateInProgressImpl: ((Bool) -> Void)?
|
||||
|
|
@ -1025,7 +1107,7 @@ open class PremiumGiftScreen: ViewControllerComponentContainer {
|
|||
var updateTabBarAlphaImpl: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
super.init(context: context, component: PremiumGiftScreenComponent(
|
||||
context: context,
|
||||
peerId: peerId,
|
||||
peerIds: peerIds,
|
||||
options: options,
|
||||
source: source,
|
||||
buttonStatePromise: self.mainButtonStatePromise,
|
||||
|
|
|
|||
|
|
@ -1195,6 +1195,9 @@ private final class LimitSheetContent: CombinedComponent {
|
|||
case let .channelReactions(reactionCount):
|
||||
titleText = strings.ChannelBoost_CustomReactions
|
||||
string = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string
|
||||
case .wallpaper:
|
||||
titleText = strings.ChannelBoost_Wallpaper
|
||||
string = strings.ChannelBoost_WallpaperText("\(premiumConfiguration.minChannelWallpaperLevel)").string
|
||||
}
|
||||
} else {
|
||||
let storiesString = strings.ChannelBoost_StoriesPerDay(level)
|
||||
|
|
@ -1780,6 +1783,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
|||
case stories
|
||||
case nameColors
|
||||
case channelReactions(reactionCount: Int)
|
||||
case wallpaper
|
||||
}
|
||||
|
||||
case storiesChannelBoost(peer: EnginePeer, boostSubject: BoostSubject, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32, canBoostAgain: Bool)
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
|
|||
selectionPosition: .right,
|
||||
isEnabled: isEnabled,
|
||||
hasNext: i != occupiedBoosts.count - 1,
|
||||
action: { [weak state] _ in
|
||||
action: { [weak state] _, _ in
|
||||
guard let state, hasSelection else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,7 +308,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||
if case .edit(_, _, _, _, _, true, _) = self.mode {
|
||||
doneButtonType = .proceed
|
||||
} else if case let .peer(peer) = resultMode {
|
||||
doneButtonType = .setPeer(peer.compactDisplayTitle, context.isPremium)
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
doneButtonType = .setPeer(peer.compactDisplayTitle, context.isPremium)
|
||||
} else {
|
||||
doneButtonType = .setChannel
|
||||
}
|
||||
} else {
|
||||
doneButtonType = .set
|
||||
}
|
||||
|
|
@ -775,7 +779,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||
if case .edit(_, _, _, _, _, true, _) = self.mode {
|
||||
doneButtonType = .proceed
|
||||
} else if case let .peer(peer) = self.resultMode {
|
||||
doneButtonType = .setPeer(peer.compactDisplayTitle, self.context.isPremium)
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
doneButtonType = .setPeer(peer.compactDisplayTitle, self.context.isPremium)
|
||||
} else {
|
||||
doneButtonType = .setChannel
|
||||
}
|
||||
} else {
|
||||
doneButtonType = .set
|
||||
}
|
||||
|
|
@ -1182,7 +1190,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||
if case .background = self.mode, toolbarBottomInset.isZero {
|
||||
toolbarBottomInset = 16.0
|
||||
}
|
||||
if case .peer = self.resultMode, !self.state.displayPatternPanel {
|
||||
if case let .peer(peer) = self.resultMode, case .user = peer, !self.state.displayPatternPanel {
|
||||
toolbarBottomInset += 58.0
|
||||
}
|
||||
let toolbarHeight = 49.0 + toolbarBottomInset
|
||||
|
|
|
|||
|
|
@ -498,7 +498,11 @@ public class WallpaperGalleryController: ViewController {
|
|||
break
|
||||
}
|
||||
if case let .peer(peer, _) = self.mode {
|
||||
doneButtonType = .setPeer(peer.compactDisplayTitle, self.context.isPremium)
|
||||
if case .user = peer {
|
||||
doneButtonType = .setPeer(peer.compactDisplayTitle, self.context.isPremium)
|
||||
} else {
|
||||
doneButtonType = .setChannel
|
||||
}
|
||||
}
|
||||
|
||||
let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings, doneButtonType: doneButtonType)
|
||||
|
|
@ -969,7 +973,7 @@ public class WallpaperGalleryController: ViewController {
|
|||
self.overlayNode?.frame = self.galleryNode.bounds
|
||||
|
||||
var toolbarHeight: CGFloat = 66.0
|
||||
if case .peer = self.mode {
|
||||
if case let .peer(peer, _) = self.mode, case .user = peer {
|
||||
toolbarHeight += 58.0
|
||||
}
|
||||
transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom)))
|
||||
|
|
|
|||
|
|
@ -1330,7 +1330,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||
let buttonSpacing: CGFloat = 18.0
|
||||
|
||||
var toolbarHeight: CGFloat = 66.0
|
||||
if let mode = self.mode, case .peer = mode {
|
||||
if let mode = self.mode, case let .peer(peer, _) = mode, case .user = peer {
|
||||
toolbarHeight += 58.0
|
||||
}
|
||||
|
||||
|
|
@ -1482,7 +1482,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
|
|||
self.nativeNode.updateBubbleTheme(bubbleTheme: self.presentationData.theme, bubbleCorners: self.presentationData.chatBubbleCorners)
|
||||
|
||||
var bottomInset: CGFloat = 132.0
|
||||
if let mode = self.mode, case .peer = mode {
|
||||
if let mode = self.mode, case let .peer(peer, _) = mode, case .user = peer {
|
||||
bottomInset += 58.0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ enum WallpaperGalleryToolbarCancelButtonType {
|
|||
enum WallpaperGalleryToolbarDoneButtonType {
|
||||
case set
|
||||
case setPeer(String, Bool)
|
||||
case setChannel
|
||||
case proceed
|
||||
case apply
|
||||
case none
|
||||
|
|
@ -284,6 +285,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryToolbar
|
|||
applyTitle = strings.Wallpaper_ApplyForMe
|
||||
applyForBothTitle = strings.Wallpaper_ApplyForBoth(name).string
|
||||
applyForBothLocked = !isPremium
|
||||
case .setChannel:
|
||||
applyTitle = strings.Wallpaper_ApplyForChannel
|
||||
case .proceed:
|
||||
applyTitle = strings.Theme_Colors_Proceed
|
||||
case .apply:
|
||||
|
|
@ -423,7 +426,7 @@ final class WallpaperGalleryOldToolbarNode: ASDisplayNode, WallpaperGalleryToolb
|
|||
}
|
||||
let doneTitle: String
|
||||
switch self.doneButtonType {
|
||||
case .set, .setPeer:
|
||||
case .set, .setPeer, .setChannel:
|
||||
doneTitle = strings.Wallpaper_Set
|
||||
case .proceed:
|
||||
doneTitle = strings.Theme_Colors_Proceed
|
||||
|
|
|
|||
|
|
@ -2612,3 +2612,148 @@ private func restrictedSendingContentsText(peer: EnginePeer, presentationData: P
|
|||
|
||||
return presentationData.strings.Chat_SendAllowedContentPeerText(peer.compactDisplayTitle, itemListString).string
|
||||
}
|
||||
|
||||
public final class MessageStoryRenderer {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
private let messages: [Message]
|
||||
|
||||
let containerNode: ASDisplayNode
|
||||
private let wallpaperBackgroundNode: WallpaperBackgroundNode
|
||||
private let messagesContainerNode: ASDisplayNode
|
||||
private var dateHeaderNode: ListViewItemHeaderNode?
|
||||
private var messageNodes: [ListViewItemNode]?
|
||||
private let addressNode: ImmediateTextNode
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
return self._ready.get()
|
||||
}
|
||||
|
||||
public init(context: AccountContext, messages: [Message]) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.messages = messages
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false)
|
||||
self.wallpaperBackgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.messagesContainerNode = ASDisplayNode()
|
||||
self.messagesContainerNode.clipsToBounds = true
|
||||
self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
|
||||
|
||||
let message = messages.first!
|
||||
let addressName = message.peers[message.id.peerId]?.addressName ?? ""
|
||||
|
||||
self.addressNode = ImmediateTextNode()
|
||||
self.addressNode.displaysAsynchronously = false
|
||||
self.addressNode.attributedText = NSAttributedString(string: "t.me/\(addressName)/\(message.id.id)", font: Font.medium(14.0), textColor: UIColor(rgb: 0xffffff))
|
||||
self.addressNode.textShadowColor = UIColor(rgb: 0x929292, alpha: 0.8)
|
||||
|
||||
self.containerNode.addSubnode(self.wallpaperBackgroundNode)
|
||||
self.containerNode.addSubnode(self.messagesContainerNode)
|
||||
self.containerNode.addSubnode(self.addressNode)
|
||||
|
||||
let wallpaper = context.sharedContext.currentPresentationData.with { $0 }.chatWallpaper
|
||||
self.wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false)
|
||||
|
||||
self._ready.set(self.wallpaperBackgroundNode.isReady)
|
||||
}
|
||||
|
||||
public func update(layout: ContainerViewLayout, completion: @escaping (UIImage?) -> Void) {
|
||||
self.updateMessagesLayout(layout: layout)
|
||||
|
||||
Queue.mainQueue().after(0.55) {
|
||||
UIGraphicsBeginImageContextWithOptions(layout.size, false, 3.0)
|
||||
self.containerNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: layout.size), afterScreenUpdates: true)
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
let finalImage = generateImage(CGSize(width: 1080, height: 1920), contextGenerator: { size, context in
|
||||
if let cgImage = img?.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: .zero, size: size), byTiling: false)
|
||||
}
|
||||
}, opaque: true, scale: 1.0)
|
||||
if let finalImage {
|
||||
completion(finalImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateMessagesLayout(layout: ContainerViewLayout) {
|
||||
let size = layout.size
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.wallpaperBackgroundNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.wallpaperBackgroundNode.updateLayout(size: size, displayMode: .aspectFill, transition: .immediate)
|
||||
self.messagesContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
|
||||
let addressLayout = self.addressNode.updateLayout(size)
|
||||
|
||||
let theme = self.presentationData.theme.withUpdated(preview: true)
|
||||
let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, theme: theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder)
|
||||
|
||||
let items: [ListViewItem] = [self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: self.messages, theme: theme, strings: self.presentationData.strings, wallpaper: self.presentationData.theme.chat.defaultWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: nil, availableReactions: nil, accountPeer: nil, isCentered: false)]
|
||||
|
||||
let inset: CGFloat = 16.0
|
||||
let width = layout.size.width - inset * 2.0
|
||||
let params = ListViewItemLayoutParams(width: width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
|
||||
if let messageNodes = self.messageNodes {
|
||||
for i in 0 ..< items.count {
|
||||
let itemNode = messageNodes[i]
|
||||
items[i].updateNode(async: { $0() }, node: {
|
||||
return itemNode
|
||||
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
|
||||
let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - layout.size.height) / 2.0)), size: CGSize(width: width, height: layout.size.height))
|
||||
|
||||
itemNode.contentSize = layout.contentSize
|
||||
itemNode.insets = layout.insets
|
||||
itemNode.frame = nodeFrame
|
||||
itemNode.isUserInteractionEnabled = false
|
||||
|
||||
apply(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var messageNodes: [ListViewItemNode] = []
|
||||
for i in 0 ..< items.count {
|
||||
var itemNode: ListViewItemNode?
|
||||
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: true, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
|
||||
itemNode = node
|
||||
apply().1(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
itemNode!.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
itemNode!.isUserInteractionEnabled = false
|
||||
messageNodes.append(itemNode!)
|
||||
self.messagesContainerNode.addSubnode(itemNode!)
|
||||
}
|
||||
self.messageNodes = messageNodes
|
||||
}
|
||||
|
||||
var bottomOffset: CGFloat = 0.0
|
||||
if let messageNodes = self.messageNodes {
|
||||
for itemNode in messageNodes {
|
||||
itemNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - itemNode.frame.height) / 2.0)), size: itemNode.frame.size)
|
||||
bottomOffset += itemNode.frame.maxY
|
||||
itemNode.updateFrame(itemNode.frame, within: layout.size)
|
||||
}
|
||||
}
|
||||
|
||||
self.addressNode.frame = CGRect(origin: CGPoint(x: inset + 16.0, y: bottomOffset + 3.0), size: CGSize(width: addressLayout.width, height: addressLayout.height + 3.0))
|
||||
|
||||
let dateHeaderNode: ListViewItemHeaderNode
|
||||
if let currentDateHeaderNode = self.dateHeaderNode {
|
||||
dateHeaderNode = currentDateHeaderNode
|
||||
headerItem.updateNode(dateHeaderNode, previous: nil, next: headerItem)
|
||||
} else {
|
||||
dateHeaderNode = headerItem.node(synchronousLoad: true)
|
||||
dateHeaderNode.subnodeTransform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
self.messagesContainerNode.addSubnode(dateHeaderNode)
|
||||
self.dateHeaderNode = dateHeaderNode
|
||||
}
|
||||
|
||||
dateHeaderNode.frame = CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height))
|
||||
dateHeaderNode.updateLayout(size: self.containerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1461,11 +1461,33 @@ public final class SolidRoundedButtonView: UIView {
|
|||
let spacingOffset: CGFloat = 9.0
|
||||
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
|
||||
let iconSpacing: CGFloat = self.iconSpacing
|
||||
let badgeSpacing: CGFloat = 6.0
|
||||
|
||||
var contentWidth: CGFloat = titleSize.width
|
||||
if !iconSize.width.isZero {
|
||||
contentWidth += iconSize.width + iconSpacing
|
||||
}
|
||||
|
||||
var badgeSize: CGSize = .zero
|
||||
if let badge = self.badge {
|
||||
let badgeNode: BadgeNode
|
||||
if let current = self.badgeNode {
|
||||
badgeNode = current
|
||||
} else {
|
||||
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
|
||||
badgeNode.alpha = self.titleNode.alpha == 0.0 ? 0.0 : 1.0
|
||||
self.badgeNode = badgeNode
|
||||
self.addSubnode(badgeNode)
|
||||
}
|
||||
badgeNode.text = badge
|
||||
badgeSize = badgeNode.update(CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
contentWidth += badgeSize.width + badgeSpacing
|
||||
} else if let badgeNode = self.badgeNode {
|
||||
self.badgeNode = nil
|
||||
badgeNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0)
|
||||
|
||||
let iconFrame: CGRect
|
||||
|
|
@ -1484,6 +1506,7 @@ public final class SolidRoundedButtonView: UIView {
|
|||
}
|
||||
iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
}
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + badgeSpacing, y: titleFrame.minY + floor((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize)
|
||||
|
||||
transition.updateFrame(view: self.iconNode, frame: iconFrame)
|
||||
if let animationNode = self.animationNode {
|
||||
|
|
@ -1491,22 +1514,8 @@ public final class SolidRoundedButtonView: UIView {
|
|||
}
|
||||
transition.updateFrame(view: self.titleNode, frame: titleFrame)
|
||||
|
||||
if let badge = self.badge {
|
||||
let badgeNode: BadgeNode
|
||||
if let current = self.badgeNode {
|
||||
badgeNode = current
|
||||
} else {
|
||||
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
|
||||
badgeNode.alpha = self.titleNode.alpha == 0.0 ? 0.0 : 1.0
|
||||
self.badgeNode = badgeNode
|
||||
self.addSubnode(badgeNode)
|
||||
}
|
||||
badgeNode.text = badge
|
||||
let badgeSize = badgeNode.update(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: badgeNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floor((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize))
|
||||
} else if let badgeNode = self.badgeNode {
|
||||
self.badgeNode = nil
|
||||
badgeNode.removeFromSupernode()
|
||||
if let badgeNode = self.badgeNode {
|
||||
transition.updateFrame(node: badgeNode, frame: badgeFrame)
|
||||
}
|
||||
|
||||
if self.subtitle != self.subtitleNode.attributedText?.string {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
|
|||
flags |= (1 << 0)
|
||||
}
|
||||
if showWinners {
|
||||
flags |= (1 << 0)
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
var additionalPeers: [Api.InputPeer] = []
|
||||
if !additionalPeerIds.isEmpty {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,33 @@ public struct PresentationResourcesSettings {
|
|||
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
|
||||
public static let premiumGift = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
|
||||
context.addPath(path.cgPath)
|
||||
context.clip()
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0x3da3f4).cgColor,
|
||||
UIColor(rgb: 0x3da3f4).cgColor,
|
||||
UIColor(rgb: 0x39b3b9).cgColor,
|
||||
UIColor(rgb: 0x35c37c).cgColor,
|
||||
UIColor(rgb: 0x35c37c).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
|
||||
public static let passport = renderIcon(name: "Settings/Menu/Passport")
|
||||
public static let watch = renderIcon(name: "Settings/Menu/Watch")
|
||||
|
|
|
|||
|
|
@ -894,8 +894,12 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = NSAttributedString(string: strings.Notification_YouChangedWallpaper, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
} else {
|
||||
let resultTitleString = strings.Notification_ChangedWallpaper(compactAuthorName)
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
if message.id.peerId.isGroupOrChannel {
|
||||
attributedString = NSAttributedString(string: strings.Notification_ChannelChangedWallpaper, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
let resultTitleString = strings.Notification_ChangedWallpaper(compactAuthorName)
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
}
|
||||
}
|
||||
case .setSameChatWallpaper:
|
||||
if message.author?.id == accountPeerId {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,11 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
if case .user = peer {
|
||||
item.controllerInteraction.openPeer(peer, .info(nil), nil, .default)
|
||||
} else {
|
||||
item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,9 +256,28 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
|
||||
}
|
||||
|
||||
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 2)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor)
|
||||
let badgeText: String
|
||||
if let giveaway {
|
||||
badgeText = "X\(giveaway.quantity)"
|
||||
} else if let giveawayResults {
|
||||
badgeText = "X\(giveawayResults.winnersCount)"
|
||||
} else {
|
||||
badgeText = ""
|
||||
}
|
||||
let badgeString = NSAttributedString(string: badgeText, font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor)
|
||||
|
||||
let prizeTitleString = NSAttributedString(string: giveawayResults != nil ? "Winners Selected!" : item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle, font: titleFont, textColor: textColor)
|
||||
let prizeTitleText: String
|
||||
if let giveawayResults {
|
||||
if giveawayResults.winnersCount > 1 {
|
||||
prizeTitleText = item.presentationData.strings.Chat_Giveaway_Message_WinnersSelectedTitle_Many
|
||||
} else {
|
||||
prizeTitleText = item.presentationData.strings.Chat_Giveaway_Message_WinnersSelectedTitle_One
|
||||
}
|
||||
} else {
|
||||
prizeTitleText = item.presentationData.strings.Chat_Giveaway_Message_PrizeTitle
|
||||
}
|
||||
|
||||
let prizeTitleString = NSAttributedString(string: prizeTitleText, font: titleFont, textColor: textColor)
|
||||
var prizeTextString: NSAttributedString?
|
||||
var additionalPrizeSeparatorString: NSAttributedString?
|
||||
var additionalPrizeTextString: NSAttributedString?
|
||||
|
|
@ -294,7 +317,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
}
|
||||
), textAlignment: .center)
|
||||
} else if let giveawayResults {
|
||||
prizeTextString = parseMarkdownIntoAttributedString("**\(giveawayResults.winnersCount)** winners of the [Giveaway]() were randomly selected by Telegram.", attributes: MarkdownAttributes(
|
||||
prizeTextString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_Giveaway_Message_WinnersSelectedText(giveawayResults.winnersCount), attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
|
||||
|
|
@ -304,7 +327,18 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
), textAlignment: .center)
|
||||
}
|
||||
|
||||
let participantsTitleString = NSAttributedString(string: giveawayResults != nil ? "Winners" : item.presentationData.strings.Chat_Giveaway_Message_ParticipantsTitle, font: titleFont, textColor: textColor)
|
||||
let participantsTitleText: String
|
||||
if let giveawayResults {
|
||||
if giveawayResults.winnersCount > 1 {
|
||||
participantsTitleText = item.presentationData.strings.Chat_Giveaway_Message_WinnersTitle_Many
|
||||
} else {
|
||||
participantsTitleText = item.presentationData.strings.Chat_Giveaway_Message_WinnersTitle_One
|
||||
}
|
||||
} else {
|
||||
participantsTitleText = item.presentationData.strings.Chat_Giveaway_Message_ParticipantsTitle
|
||||
}
|
||||
|
||||
let participantsTitleString = NSAttributedString(string: participantsTitleText, font: titleFont, textColor: textColor)
|
||||
let participantsText: String
|
||||
let countriesText: String
|
||||
|
||||
|
|
@ -360,7 +394,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
if let giveawayResults {
|
||||
if giveawayResults.winnersCount > giveawayResults.winnersPeerIds.count {
|
||||
let moreCount = giveawayResults.winnersCount - Int32(giveawayResults.winnersPeerIds.count)
|
||||
dateTitleText = "and \(moreCount) more!"
|
||||
dateTitleText = item.presentationData.strings.Chat_Giveaway_Message_WinnersMore(moreCount)
|
||||
} else {
|
||||
dateTitleText = ""
|
||||
}
|
||||
|
|
@ -370,8 +404,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
var dateTextString: NSAttributedString?
|
||||
if let giveaway {
|
||||
dateTextString = NSAttributedString(string: stringForFullDate(timestamp: giveaway.untilDate, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), font: textFont, textColor: textColor)
|
||||
} else if let _ = giveawayResults {
|
||||
dateTextString = NSAttributedString(string: "All winners received gift links in private messages.", font: textFont, textColor: textColor)
|
||||
} else if let giveawayResults {
|
||||
dateTextString = NSAttributedString(string: giveawayResults.winnersCount > 1 ? item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Many : item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_One, font: textFont, textColor: textColor)
|
||||
}
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, hidesHeaders: true)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ swift_library(
|
|||
"//submodules/ChatMessageBackground",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import BundleIconComponent
|
|||
import ChatMessageBackground
|
||||
import ContextUI
|
||||
import UndoUI
|
||||
import MergedAvatarsNode
|
||||
|
||||
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? {
|
||||
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false)
|
||||
|
|
@ -776,7 +777,7 @@ private final class ChannelItemComponent: Component {
|
|||
self.mergedAvatarsNode = mergedAvatarsNode
|
||||
}
|
||||
|
||||
mergedAvatarsNode.update(context: component.context, peers: component.peers.map { $0._asPeer() }, synchronousLoad: false, imageSize: 60.0, imageSpacing: 10.0, borderWidth: 2.0)
|
||||
mergedAvatarsNode.update(context: component.context, peers: component.peers.map { $0._asPeer() }, synchronousLoad: false, imageSize: 60.0, imageSpacing: 10.0, borderWidth: 2.0, avatarFontSize: 26.0)
|
||||
let avatarsSize = CGSize(width: avatarSize.width + 20.0, height: avatarSize.height)
|
||||
mergedAvatarsNode.updateLayout(size: avatarsSize)
|
||||
mergedAvatarsNode.frame = CGRect(origin: CGPoint(x: avatarFrame.midX - avatarsSize.width / 2.0, y: avatarFrame.minY), size: avatarsSize)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||
"//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode",
|
||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import ChatMessageDateAndStatusNode
|
|||
import ChatMessageBubbleContentNode
|
||||
import ChatMessageItemCommon
|
||||
import PollBubbleTimerNode
|
||||
import MergedAvatarsNode
|
||||
|
||||
private final class ChatMessagePollOptionRadioNodeParameters: NSObject {
|
||||
let timestamp: Double
|
||||
|
|
@ -1440,10 +1441,10 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
timerTransition.updateAlpha(node: strongSelf.solutionButtonNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - defaultMergedImageSize) / 2.0)), size: CGSize(width: defaultMergedImageSize + defaultMergedImageSpacing * 2.0, height: defaultMergedImageSize))
|
||||
let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - MergedAvatarsNode.defaultMergedImageSize) / 2.0)), size: CGSize(width: MergedAvatarsNode.defaultMergedImageSize + MergedAvatarsNode.defaultMergedImageSpacing * 2.0, height: MergedAvatarsNode.defaultMergedImageSize))
|
||||
strongSelf.avatarsNode.frame = avatarsFrame
|
||||
strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size)
|
||||
strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: defaultMergedImageSize, imageSpacing: defaultMergedImageSpacing, borderWidth: defaultBorderWidth)
|
||||
strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: MergedAvatarsNode.defaultMergedImageSize, imageSpacing: MergedAvatarsNode.defaultMergedImageSpacing, borderWidth: MergedAvatarsNode.defaultBorderWidth)
|
||||
strongSelf.avatarsNode.isHidden = isBotChat
|
||||
let alphaTransition: ContainedViewLayoutTransition
|
||||
if animation.isAnimated {
|
||||
|
|
@ -1706,202 +1707,3 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerAvatarReference: Equatable {
|
||||
case letters(PeerId, PeerNameColor?, [String])
|
||||
case image(PeerReference, TelegramMediaImageRepresentation)
|
||||
|
||||
var peerId: PeerId {
|
||||
switch self {
|
||||
case let .letters(value, _, _):
|
||||
return value
|
||||
case let .image(value, _):
|
||||
return value.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PeerAvatarReference {
|
||||
init(peer: Peer) {
|
||||
if let photo = peer.smallProfileImage, let peerReference = PeerReference(peer) {
|
||||
self = .image(peerReference, photo)
|
||||
} else {
|
||||
self = .letters(peer.id, peer.nameColor, peer.displayLetters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MergedAvatarsNodeArguments: NSObject {
|
||||
let peers: [PeerAvatarReference]
|
||||
let images: [PeerId: UIImage]
|
||||
let imageSize: CGFloat
|
||||
let imageSpacing: CGFloat
|
||||
let borderWidth: CGFloat
|
||||
|
||||
init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) {
|
||||
self.peers = peers
|
||||
self.images = images
|
||||
self.imageSize = imageSize
|
||||
self.imageSpacing = imageSpacing
|
||||
self.borderWidth = borderWidth
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultMergedImageSize: CGFloat = 16.0
|
||||
private let defaultMergedImageSpacing: CGFloat = 15.0
|
||||
private let defaultBorderWidth: CGFloat = 1.0
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 8.0)
|
||||
|
||||
public final class MergedAvatarsNode: ASDisplayNode {
|
||||
private var peers: [PeerAvatarReference] = []
|
||||
private var images: [PeerId: UIImage] = [:]
|
||||
private var disposables: [PeerId: Disposable] = [:]
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private var imageSize: CGFloat = defaultMergedImageSize
|
||||
private var imageSpacing: CGFloat = defaultMergedImageSpacing
|
||||
private var borderWidthValue: CGFloat = defaultBorderWidth
|
||||
|
||||
public var pressed: (() -> Void)?
|
||||
|
||||
override public init() {
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
self.displaysAsynchronously = true
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.addSubnode(self.buttonNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
for (_, disposable) in self.disposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
public func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) {
|
||||
self.imageSize = imageSize
|
||||
self.imageSpacing = imageSpacing
|
||||
self.borderWidthValue = borderWidth
|
||||
var filteredPeers = peers.map(PeerAvatarReference.init)
|
||||
if filteredPeers.count > 3 {
|
||||
filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3)
|
||||
}
|
||||
if filteredPeers != self.peers {
|
||||
self.peers = filteredPeers
|
||||
|
||||
var validImageIds: [PeerId] = []
|
||||
for peer in filteredPeers {
|
||||
if case .image = peer {
|
||||
validImageIds.append(peer.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
var removedImageIds: [PeerId] = []
|
||||
for (id, _) in self.images {
|
||||
if !validImageIds.contains(id) {
|
||||
removedImageIds.append(id)
|
||||
}
|
||||
}
|
||||
var removedDisposableIds: [PeerId] = []
|
||||
for (id, disposable) in self.disposables {
|
||||
if !validImageIds.contains(id) {
|
||||
disposable.dispose()
|
||||
removedDisposableIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removedImageIds {
|
||||
self.images.removeValue(forKey: id)
|
||||
}
|
||||
for id in removedDisposableIds {
|
||||
self.disposables.removeValue(forKey: id)
|
||||
}
|
||||
for peer in filteredPeers {
|
||||
switch peer {
|
||||
case let .image(peerReference, representation):
|
||||
if self.disposables[peer.peerId] == nil {
|
||||
if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: imageSize, height: imageSize), synchronousLoad: synchronousLoad) {
|
||||
let disposable = (signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let image = imageVersions?.0
|
||||
if let image = image {
|
||||
strongSelf.images[peer.peerId] = image
|
||||
strongSelf.setNeedsDisplay()
|
||||
}
|
||||
})
|
||||
self.disposables[peer.peerId] = disposable
|
||||
}
|
||||
}
|
||||
case .letters:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol {
|
||||
return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue)
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
assertNotOnMainThread()
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(bounds)
|
||||
}
|
||||
|
||||
guard let parameters = parameters as? MergedAvatarsNodeArguments else {
|
||||
return
|
||||
}
|
||||
|
||||
let mergedImageSize = parameters.imageSize
|
||||
let mergedImageSpacing = parameters.imageSpacing
|
||||
|
||||
var currentX = mergedImageSize + mergedImageSpacing * CGFloat(parameters.peers.count - 1) - mergedImageSize
|
||||
for i in (0 ..< parameters.peers.count).reversed() {
|
||||
let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: imageRect.insetBy(dx: -parameters.borderWidth, dy: -parameters.borderWidth))
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
context.saveGState()
|
||||
switch parameters.peers[i] {
|
||||
case let .letters(peerId, nameColor, letters):
|
||||
context.translateBy(x: currentX, y: 0.0)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId, nameColor: nameColor)
|
||||
context.translateBy(x: -currentX, y: 0.0)
|
||||
case .image:
|
||||
if let image = parameters.images[parameters.peers[i].peerId] {
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
context.draw(image.cgImage!, in: imageRect)
|
||||
} else {
|
||||
context.setFillColor(UIColor.gray.cgColor)
|
||||
context.fillEllipse(in: imageRect)
|
||||
}
|
||||
}
|
||||
context.restoreGState()
|
||||
currentX -= mergedImageSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -588,6 +588,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
strongSelf.cachedChatMessageText = updatedCachedChatMessageText
|
||||
}
|
||||
|
||||
strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize)
|
||||
|
||||
let cachedLayout = strongSelf.textNode.textNode.cachedLayout
|
||||
|
|
|
|||
|
|
@ -265,6 +265,7 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
|
|||
}
|
||||
|
||||
let fromYou = item.message.author?.id == item.context.account.peerId
|
||||
let isChannel = item.message.id.peerId.isGroupOrChannel
|
||||
|
||||
let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? ""
|
||||
let text: String
|
||||
|
|
@ -281,7 +282,11 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
|
|||
}
|
||||
}
|
||||
} else {
|
||||
text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string
|
||||
if item.message.id.peerId.isGroupOrChannel {
|
||||
text = item.presentationData.strings.Notification_ChannelChangedWallpaper
|
||||
} else {
|
||||
text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string
|
||||
}
|
||||
}
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor)
|
||||
|
|
@ -311,15 +316,15 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
|
|||
if displayTrailingAnimatedDots {
|
||||
textHeight += subtitleLayout.size.height
|
||||
}
|
||||
let backgroundSize = CGSize(width: width, height: textHeight + 140.0 + (fromYou ? 0.0 : 42.0))
|
||||
let backgroundSize = CGSize(width: width, height: textHeight + 140.0 + (fromYou || isChannel ? 0.0 : 42.0))
|
||||
|
||||
return (backgroundSize.width, { boundingWidth in
|
||||
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.buttonNode.isHidden = fromYou
|
||||
strongSelf.buttonTitleNode.isHidden = fromYou
|
||||
strongSelf.buttonNode.isHidden = fromYou || isChannel
|
||||
strongSelf.buttonTitleNode.isHidden = fromYou || isChannel
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: 13.0), size: imageSize)
|
||||
if let media, mediaUpdated {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MergedAvatarsNode",
|
||||
module_name = "MergedAvatarsNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
|
||||
private enum PeerAvatarReference: Equatable {
|
||||
case letters(PeerId, PeerNameColor?, [String])
|
||||
case image(PeerReference, TelegramMediaImageRepresentation)
|
||||
|
||||
var peerId: PeerId {
|
||||
switch self {
|
||||
case let .letters(value, _, _):
|
||||
return value
|
||||
case let .image(value, _):
|
||||
return value.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PeerAvatarReference {
|
||||
init(peer: Peer) {
|
||||
if let photo = peer.smallProfileImage, let peerReference = PeerReference(peer) {
|
||||
self = .image(peerReference, photo)
|
||||
} else {
|
||||
self = .letters(peer.id, peer.nameColor, peer.displayLetters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MergedAvatarsNodeArguments: NSObject {
|
||||
let peers: [PeerAvatarReference]
|
||||
let images: [PeerId: UIImage]
|
||||
let imageSize: CGFloat
|
||||
let imageSpacing: CGFloat
|
||||
let borderWidth: CGFloat
|
||||
let avatarFontSize: CGFloat
|
||||
|
||||
init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat, avatarFontSize: CGFloat) {
|
||||
self.peers = peers
|
||||
self.images = images
|
||||
self.imageSize = imageSize
|
||||
self.imageSpacing = imageSpacing
|
||||
self.borderWidth = borderWidth
|
||||
self.avatarFontSize = avatarFontSize
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultMergedImageSize: CGFloat = 16.0
|
||||
private let defaultMergedImageSpacing: CGFloat = 15.0
|
||||
private let defaultBorderWidth: CGFloat = 1.0
|
||||
|
||||
public final class MergedAvatarsNode: ASDisplayNode {
|
||||
public static let defaultMergedImageSize: CGFloat = 16.0
|
||||
public static let defaultMergedImageSpacing: CGFloat = 15.0
|
||||
public static let defaultBorderWidth: CGFloat = 1.0
|
||||
public static let defaultAvatarFontSize: CGFloat = 8.0
|
||||
|
||||
private var peers: [PeerAvatarReference] = []
|
||||
private var images: [PeerId: UIImage] = [:]
|
||||
private var disposables: [PeerId: Disposable] = [:]
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private var imageSize: CGFloat = defaultMergedImageSize
|
||||
private var imageSpacing: CGFloat = defaultMergedImageSpacing
|
||||
private var borderWidthValue: CGFloat = defaultBorderWidth
|
||||
private var avatarFontSize: CGFloat = defaultAvatarFontSize
|
||||
|
||||
public var pressed: (() -> Void)?
|
||||
|
||||
override public init() {
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
self.displaysAsynchronously = true
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.addSubnode(self.buttonNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
for (_, disposable) in self.disposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
public func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat, avatarFontSize: CGFloat = 8.0) {
|
||||
self.imageSize = imageSize
|
||||
self.imageSpacing = imageSpacing
|
||||
self.borderWidthValue = borderWidth
|
||||
self.avatarFontSize = avatarFontSize
|
||||
|
||||
var filteredPeers = peers.map(PeerAvatarReference.init)
|
||||
if filteredPeers.count > 3 {
|
||||
filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3)
|
||||
}
|
||||
if filteredPeers != self.peers {
|
||||
self.peers = filteredPeers
|
||||
|
||||
var validImageIds: [PeerId] = []
|
||||
for peer in filteredPeers {
|
||||
if case .image = peer {
|
||||
validImageIds.append(peer.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
var removedImageIds: [PeerId] = []
|
||||
for (id, _) in self.images {
|
||||
if !validImageIds.contains(id) {
|
||||
removedImageIds.append(id)
|
||||
}
|
||||
}
|
||||
var removedDisposableIds: [PeerId] = []
|
||||
for (id, disposable) in self.disposables {
|
||||
if !validImageIds.contains(id) {
|
||||
disposable.dispose()
|
||||
removedDisposableIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removedImageIds {
|
||||
self.images.removeValue(forKey: id)
|
||||
}
|
||||
for id in removedDisposableIds {
|
||||
self.disposables.removeValue(forKey: id)
|
||||
}
|
||||
for peer in filteredPeers {
|
||||
switch peer {
|
||||
case let .image(peerReference, representation):
|
||||
if self.disposables[peer.peerId] == nil {
|
||||
if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: imageSize, height: imageSize), synchronousLoad: synchronousLoad) {
|
||||
let disposable = (signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let image = imageVersions?.0
|
||||
if let image = image {
|
||||
strongSelf.images[peer.peerId] = image
|
||||
strongSelf.setNeedsDisplay()
|
||||
}
|
||||
})
|
||||
self.disposables[peer.peerId] = disposable
|
||||
}
|
||||
}
|
||||
case .letters:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol {
|
||||
return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue, avatarFontSize: self.avatarFontSize)
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
assertNotOnMainThread()
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(bounds)
|
||||
}
|
||||
|
||||
guard let parameters = parameters as? MergedAvatarsNodeArguments else {
|
||||
return
|
||||
}
|
||||
|
||||
let mergedImageSize = parameters.imageSize
|
||||
let mergedImageSpacing = parameters.imageSpacing
|
||||
|
||||
var currentX = mergedImageSize + mergedImageSpacing * CGFloat(parameters.peers.count - 1) - mergedImageSize
|
||||
for i in (0 ..< parameters.peers.count).reversed() {
|
||||
let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: imageRect.insetBy(dx: -parameters.borderWidth, dy: -parameters.borderWidth))
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
context.saveGState()
|
||||
switch parameters.peers[i] {
|
||||
case let .letters(peerId, nameColor, letters):
|
||||
context.translateBy(x: currentX, y: 0.0)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarPlaceholderFont(size: parameters.avatarFontSize), letters: letters, peerId: peerId, nameColor: nameColor)
|
||||
context.translateBy(x: -currentX, y: 0.0)
|
||||
case .image:
|
||||
if let image = parameters.images[parameters.peers[i].peerId] {
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
context.draw(image.cgImage!, in: imageRect)
|
||||
} else {
|
||||
context.setFillColor(UIColor.gray.cgColor)
|
||||
context.fillEllipse(in: imageRect)
|
||||
}
|
||||
}
|
||||
context.restoreGState()
|
||||
currentX -= mergedImageSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -230,7 +230,7 @@ final class ContextResultPanelComponent: Component {
|
|||
presence: nil,
|
||||
selectionState: .none,
|
||||
hasNext: index != peers.count - 1,
|
||||
action: { [weak self] peer in
|
||||
action: { [weak self] peer, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
|
@ -299,7 +299,7 @@ final class ContextResultPanelComponent: Component {
|
|||
presence: nil,
|
||||
selectionState: .none,
|
||||
hasNext: true,
|
||||
action: { _ in
|
||||
action: { _, _ in
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/SolidRoundedButtonNode",
|
||||
"//submodules/MediaPickerUI",
|
||||
"//submodules/AttachmentUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ import PeerNameColorScreen
|
|||
import PeerAllowedReactionsScreen
|
||||
import ChatMessageSelectionInputPanelNode
|
||||
import ChatHistorySearchContainerNode
|
||||
import MediaPickerUI
|
||||
import AttachmentUI
|
||||
|
||||
public enum PeerInfoAvatarEditingMode {
|
||||
case generic
|
||||
|
|
@ -483,6 +485,7 @@ private enum PeerInfoSettingsSection {
|
|||
case language
|
||||
case stickers
|
||||
case premium
|
||||
case premiumGift
|
||||
case passport
|
||||
case watch
|
||||
case support
|
||||
|
|
@ -527,6 +530,7 @@ private final class PeerInfoInteraction {
|
|||
let performBotCommand: (PeerInfoBotCommand) -> Void
|
||||
let editingOpenPublicLinkSetup: () -> Void
|
||||
let editingOpenNameColorSetup: () -> Void
|
||||
let editingOpenPeerWallpaperSetup: () -> Void
|
||||
let editingOpenInviteLinksSetup: () -> Void
|
||||
let editingOpenDiscussionGroupSetup: () -> Void
|
||||
let editingToggleMessageSignatures: (Bool) -> Void
|
||||
|
|
@ -582,6 +586,7 @@ private final class PeerInfoInteraction {
|
|||
performBotCommand: @escaping (PeerInfoBotCommand) -> Void,
|
||||
editingOpenPublicLinkSetup: @escaping () -> Void,
|
||||
editingOpenNameColorSetup: @escaping () -> Void,
|
||||
editingOpenPeerWallpaperSetup: @escaping () -> Void,
|
||||
editingOpenInviteLinksSetup: @escaping () -> Void,
|
||||
editingOpenDiscussionGroupSetup: @escaping () -> Void,
|
||||
editingToggleMessageSignatures: @escaping (Bool) -> Void,
|
||||
|
|
@ -636,6 +641,7 @@ private final class PeerInfoInteraction {
|
|||
self.performBotCommand = performBotCommand
|
||||
self.editingOpenPublicLinkSetup = editingOpenPublicLinkSetup
|
||||
self.editingOpenNameColorSetup = editingOpenNameColorSetup
|
||||
self.editingOpenPeerWallpaperSetup = editingOpenPeerWallpaperSetup
|
||||
self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup
|
||||
self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup
|
||||
self.editingToggleMessageSignatures = editingToggleMessageSignatures
|
||||
|
|
@ -910,6 +916,9 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||
items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: presentationData.strings.Settings_Premium, icon: PresentationResourcesSettings.premium, action: {
|
||||
interaction.openSettings(.premium)
|
||||
}))
|
||||
items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 101, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.Settings_PremiumGift, icon: PresentationResourcesSettings.premiumGift, action: {
|
||||
interaction.openSettings(.premiumGift)
|
||||
}))
|
||||
}
|
||||
|
||||
/*items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: "Payment Method", icon: PresentationResourcesSettings.language, action: {
|
||||
|
|
@ -1570,19 +1579,20 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||
switch channel.info {
|
||||
case .broadcast:
|
||||
let ItemUsername = 1
|
||||
let ItemNameColor = 2
|
||||
let ItemInviteLinks = 3
|
||||
let ItemDiscussionGroup = 4
|
||||
let ItemSignMessages = 5
|
||||
let ItemSignMessagesHelp = 6
|
||||
let ItemDeleteChannel = 7
|
||||
let ItemReactions = 8
|
||||
let ItemAdmins = 9
|
||||
let ItemMembers = 10
|
||||
let ItemMemberRequests = 11
|
||||
let ItemStats = 12
|
||||
let ItemBanned = 13
|
||||
let ItemRecentActions = 14
|
||||
let ItemPeerColor = 2
|
||||
let ItemPeerWallpaper = 3
|
||||
let ItemInviteLinks = 4
|
||||
let ItemDiscussionGroup = 5
|
||||
let ItemSignMessages = 6
|
||||
let ItemSignMessagesHelp = 7
|
||||
let ItemDeleteChannel = 8
|
||||
let ItemReactions = 9
|
||||
let ItemAdmins = 10
|
||||
let ItemMembers = 11
|
||||
let ItemMemberRequests = 12
|
||||
let ItemStats = 13
|
||||
let ItemBanned = 14
|
||||
let ItemRecentActions = 15
|
||||
|
||||
let isCreator = channel.flags.contains(.isCreator)
|
||||
|
||||
|
|
@ -1655,10 +1665,22 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||
}
|
||||
|
||||
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
|
||||
let colors = context.peerNameColors.get(data.peer?.nameColor ?? .blue, dark: presentationData.theme.overallDarkAppearance)
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemNameColor, label: .semitransparentBadge(EnginePeer(channel).compactDisplayTitle, colors.main), text: presentationData.strings.Channel_ChannelColor, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
|
||||
var colors: [PeerNameColors.Colors] = []
|
||||
if let nameColor = channel.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) {
|
||||
colors.append(nameColor)
|
||||
}
|
||||
if let profileColor = channel.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) {
|
||||
colors.append(profileColor)
|
||||
}
|
||||
let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors)
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), text: presentationData.strings.Channel_ChannelColor, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
|
||||
interaction.editingOpenNameColorSetup()
|
||||
}))
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerWallpaper, label: .none, text: "Wallpaper", icon: UIImage(bundleImageName: "Settings/Menu/Appearance"), action: {
|
||||
interaction.editingOpenPeerWallpaperSetup()
|
||||
}))
|
||||
}
|
||||
|
||||
if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendSomething)) {
|
||||
|
|
@ -2336,6 +2358,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
editingOpenNameColorSetup: { [weak self] in
|
||||
self?.editingOpenNameColorSetup()
|
||||
},
|
||||
editingOpenPeerWallpaperSetup: { [weak self] in
|
||||
self?.editingOpenPeerWallpaperSetup()
|
||||
},
|
||||
editingOpenInviteLinksSetup: { [weak self] in
|
||||
self?.editingOpenInviteLinksSetup()
|
||||
},
|
||||
|
|
@ -5412,7 +5437,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
if let strongSelf = self {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumGiftScreen(context: strongSelf.context, peerId: strongSelf.peerId, options: cachedData.premiumGiftOptions, source: .profile, pushController: { c in
|
||||
let controller = PremiumGiftScreen(context: strongSelf.context, peerIds: [strongSelf.peerId], options: cachedData.premiumGiftOptions, source: .profile, pushController: { c in
|
||||
pushControllerImpl?(c)
|
||||
}, completion: { [weak self] in
|
||||
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
|
|
@ -7180,6 +7205,79 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
}
|
||||
|
||||
private func editingOpenPeerWallpaperSetup() {
|
||||
// let link = status.url
|
||||
// let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: {
|
||||
// UIPasteboard.general.string = link
|
||||
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
// presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }))
|
||||
// return true
|
||||
// }, openStats: nil, openGift: premiumConfiguration.giveawayGiftsPurchaseAvailable ? {
|
||||
// let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic)
|
||||
// pushImpl?(controller)
|
||||
// } : nil)
|
||||
// pushImpl?(controller)
|
||||
|
||||
let dismissControllers = { [weak self] in
|
||||
if let self, let navigationController = self.controller?.navigationController as? NavigationController {
|
||||
let controllers = navigationController.viewControllers.filter({ controller in
|
||||
if controller is WallpaperGalleryController || controller is AttachmentController || controller is PeerInfoScreenImpl {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
}
|
||||
var openWallpaperPickerImpl: ((Bool) -> Void)?
|
||||
let openWallpaperPicker: (Bool) -> Void = { [weak self] animateAppearance in
|
||||
guard let self, let peer = self.data?.peer else {
|
||||
return
|
||||
}
|
||||
let controller = wallpaperMediaPickerController(
|
||||
context: self.context,
|
||||
updatedPresentationData: self.controller?.updatedPresentationData,
|
||||
peer: EnginePeer(peer),
|
||||
animateAppearance: true,
|
||||
completion: { [weak self] _, result in
|
||||
guard let strongSelf = self, let asset = result as? PHAsset else {
|
||||
return
|
||||
}
|
||||
let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false))
|
||||
controller.navigationPresentation = .modal
|
||||
controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness, forBoth in
|
||||
if let strongSelf = self {
|
||||
uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peer.id, forBoth: forBoth, completion: {
|
||||
Queue.mainQueue().after(0.3, {
|
||||
dismissControllers()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
strongSelf.controller?.push(controller)
|
||||
},
|
||||
openColors: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.push(controller)
|
||||
}
|
||||
}, openGallery: {
|
||||
openWallpaperPickerImpl?(false)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.controller?.push(controller)
|
||||
}
|
||||
)
|
||||
controller.navigationPresentation = .flatModal
|
||||
self.controller?.push(controller)
|
||||
}
|
||||
openWallpaperPickerImpl = openWallpaperPicker
|
||||
openWallpaperPicker(true)
|
||||
}
|
||||
|
||||
private func editingOpenInviteLinksSetup() {
|
||||
self.controller?.push(inviteLinkListController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, admin: nil))
|
||||
}
|
||||
|
|
@ -8626,6 +8724,63 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
push(LocalizationListController(context: self.context))
|
||||
case .premium:
|
||||
self.controller?.push(PremiumIntroScreen(context: self.context, modal: false, source: .settings))
|
||||
case .premiumGift:
|
||||
let controller = self.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: self.context, mode: .premiumGifting, options: [], isPeerEnabled: { peer in
|
||||
if case let .user(user) = peer, user.botInfo == nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}))
|
||||
self.controller?.push(controller)
|
||||
self.activeActionDisposable.set((controller.result
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self, weak controller] result in
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
var peerIds: [PeerId] = []
|
||||
if case let .result(peerIdsValue, _) = result {
|
||||
peerIds = peerIdsValue.compactMap({ peerId in
|
||||
if case let .peer(peerId) = peerId {
|
||||
return peerId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let maxCount = 10
|
||||
if peerIds.count > maxCount {
|
||||
self.hapticFeedback.error()
|
||||
|
||||
controller.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: self.presentationData.strings.Premium_Gift_ContactSelection_MaximumReached("\(maxCount)").string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
return
|
||||
}
|
||||
|
||||
let giftController = PremiumGiftScreen(context: self.context, peerIds: peerIds, options: [], source: .settings, pushController: { [weak self] c in
|
||||
self?.controller?.push(c)
|
||||
}, completion: { [weak self] in
|
||||
if let self, let navigationController = self.controller?.navigationController as? NavigationController, peerIds.count == 1, let peerId = peerIds.first {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is PeerInfoScreen) && !($0 is PremiumGiftScreen) }
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: peerId) = chatController.chatLocation {
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false))
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
controllers.append(chatController)
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
})
|
||||
controller.push(giftController)
|
||||
}))
|
||||
case .stickers:
|
||||
if let settings = self.data?.globalSettings {
|
||||
push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in
|
||||
|
|
|
|||
|
|
@ -1337,7 +1337,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
self.controller?.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
} else {
|
||||
let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false)))
|
||||
let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: false)))
|
||||
self.contactListNode = contactListNode
|
||||
contactListNode.enableUpdates = true
|
||||
contactListNode.selectionStateUpdated = { [weak self] selectionState in
|
||||
|
|
|
|||
|
|
@ -1066,7 +1066,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
rightAccessory: accessory,
|
||||
selectionState: .none,
|
||||
hasNext: i < peers.count - 1,
|
||||
action: { [weak self] peer in
|
||||
action: { [weak self] peer, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
|
@ -1414,7 +1414,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
presence: stateValue.presences[peer.id],
|
||||
selectionState: .editing(isSelected: isSelected, isTinted: false),
|
||||
hasNext: true,
|
||||
action: { [weak self] peer in
|
||||
action: { [weak self] peer, _ in
|
||||
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
|
||||
return
|
||||
}
|
||||
|
|
@ -2143,7 +2143,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
presence: nil,
|
||||
selectionState: .editing(isSelected: false, isTinted: false),
|
||||
hasNext: true,
|
||||
action: { _ in
|
||||
action: { _, _ in
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import PhotoResources
|
|||
private let avatarFont = avatarPlaceholderFont(size: 15.0)
|
||||
private let readIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/MenuReadIcon"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
private let repostIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Stories/HeaderRepost"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
private let forwardIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Stories/HeaderRepost"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
private let forwardIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Stories/HeaderForward"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
private let checkImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
private let disclosureImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ public final class PeerListItemComponent: Component {
|
|||
let selectionPosition: SelectionPosition
|
||||
let isEnabled: Bool
|
||||
let hasNext: Bool
|
||||
let action: (EnginePeer) -> Void
|
||||
let action: (EnginePeer, EngineMessage.Id?) -> Void
|
||||
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
|
||||
let openStories: ((EnginePeer, AvatarNode) -> Void)?
|
||||
let openStory: ((EnginePeer, Int32, UIView) -> Void)?
|
||||
|
|
@ -141,7 +141,7 @@ public final class PeerListItemComponent: Component {
|
|||
selectionPosition: SelectionPosition = .left,
|
||||
isEnabled: Bool = true,
|
||||
hasNext: Bool,
|
||||
action: @escaping (EnginePeer) -> Void,
|
||||
action: @escaping (EnginePeer, EngineMessage.Id?) -> Void,
|
||||
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
|
||||
openStories: ((EnginePeer, AvatarNode) -> Void)? = nil,
|
||||
openStory: ((EnginePeer, Int32, UIView) -> Void)? = nil
|
||||
|
|
@ -353,7 +353,7 @@ public final class PeerListItemComponent: Component {
|
|||
guard let component = self.component, let peer = component.peer else {
|
||||
return
|
||||
}
|
||||
component.action(peer)
|
||||
component.action(peer, component.message?.id)
|
||||
}
|
||||
|
||||
@objc private func avatarButtonPressed() {
|
||||
|
|
|
|||
|
|
@ -1707,6 +1707,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
peer: component.slice.peer,
|
||||
storyItem: item.storyItem,
|
||||
myReaction: item.storyItem.myReaction.flatMap { value -> StoryFooterPanelComponent.MyReaction? in
|
||||
var centerAnimation: TelegramMediaFile?
|
||||
|
|
@ -3327,6 +3328,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
}
|
||||
self.navigateToPeer(peer: peer, chat: false)
|
||||
},
|
||||
openMessage: { [weak self] peer, messageId in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.navigateToPeer(peer: peer, chat: true, subject: .message(id: .id(messageId), highlight: nil, timecode: nil))
|
||||
},
|
||||
peerContextAction: { [weak self] peer, sourceView, gesture in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||
let deleteAction: () -> Void
|
||||
let moreAction: (UIView, ContextGesture?) -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openMessage: (EnginePeer, EngineMessage.Id) -> Void
|
||||
let peerContextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void
|
||||
let openPeerStories: (EnginePeer, AvatarNode) -> Void
|
||||
let openStory: (EnginePeer, Int32, [(EnginePeer, EngineStoryItem)], UIView) -> Void
|
||||
|
|
@ -94,6 +95,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||
deleteAction: @escaping () -> Void,
|
||||
moreAction: @escaping (UIView, ContextGesture?) -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openMessage: @escaping (EnginePeer, EngineMessage.Id) -> Void,
|
||||
peerContextAction: @escaping (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void,
|
||||
openPeerStories: @escaping (EnginePeer, AvatarNode) -> Void,
|
||||
openStory: @escaping (EnginePeer, Int32, [(EnginePeer, EngineStoryItem)], UIView) -> Void,
|
||||
|
|
@ -118,6 +120,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||
self.deleteAction = deleteAction
|
||||
self.moreAction = moreAction
|
||||
self.openPeer = openPeer
|
||||
self.openMessage = openMessage
|
||||
self.peerContextAction = peerContextAction
|
||||
self.openPeerStories = openPeerStories
|
||||
self.openStory = openStory
|
||||
|
|
@ -237,6 +240,17 @@ final class StoryItemSetViewListComponent: Component {
|
|||
case repostsFirst = 0
|
||||
case reactionsFirst = 1
|
||||
case recentFirst = 2
|
||||
|
||||
var sortMode: EngineStoryViewListContext.SortMode {
|
||||
switch self {
|
||||
case .repostsFirst:
|
||||
return .repostsFirst
|
||||
case .reactionsFirst:
|
||||
return .reactionsFirst
|
||||
case .recentFirst:
|
||||
return .recentFirst
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ContentConfigurationKey: Equatable {
|
||||
|
|
@ -527,11 +541,15 @@ final class StoryItemSetViewListComponent: Component {
|
|||
message: item.message,
|
||||
selectionState: .none,
|
||||
hasNext: index != viewListState.totalCount - 1 || itemLayout.premiumFooterSize != nil,
|
||||
action: { [weak self] peer in
|
||||
action: { [weak self] peer, messageId in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.openPeer(peer)
|
||||
if let messageId {
|
||||
component.openMessage(peer, messageId)
|
||||
} else {
|
||||
component.openPeer(peer)
|
||||
}
|
||||
},
|
||||
contextAction: { peer, view, gesture in
|
||||
component.peerContextAction(peer, view, gesture)
|
||||
|
|
@ -727,15 +745,6 @@ final class StoryItemSetViewListComponent: Component {
|
|||
case .contacts:
|
||||
mappedListMode = .contacts
|
||||
}
|
||||
let mappedSortMode: EngineStoryViewListContext.SortMode
|
||||
switch self.configuration.sortMode {
|
||||
case .repostsFirst:
|
||||
mappedSortMode = .repostsFirst
|
||||
case .reactionsFirst:
|
||||
mappedSortMode = .reactionsFirst
|
||||
case .recentFirst:
|
||||
mappedSortMode = .recentFirst
|
||||
}
|
||||
|
||||
var parentSource: EngineStoryViewListContext?
|
||||
if let baseContentView, baseContentView.configuration == self.configuration, baseContentView.query == nil {
|
||||
|
|
@ -745,16 +754,22 @@ final class StoryItemSetViewListComponent: Component {
|
|||
parentSource = nil
|
||||
}
|
||||
|
||||
self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: mappedSortMode, searchQuery: query, parentSource: parentSource)
|
||||
self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: self.configuration.sortMode.sortMode, searchQuery: query, parentSource: parentSource)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.configuration == ContentConfigurationKey(listMode: .everyone, sortMode: .reactionsFirst) {
|
||||
let defaultSortMode: SortMode
|
||||
if component.peerId.isGroupOrChannel {
|
||||
defaultSortMode = .recentFirst
|
||||
} else {
|
||||
defaultSortMode = .reactionsFirst
|
||||
}
|
||||
if self.configuration == ContentConfigurationKey(listMode: .everyone, sortMode: defaultSortMode) {
|
||||
let viewList: EngineStoryViewListContext
|
||||
if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] {
|
||||
viewList = current
|
||||
} else {
|
||||
viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst)
|
||||
viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: .everyone, sortMode: defaultSortMode.sortMode)
|
||||
component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList
|
||||
}
|
||||
self.viewList = viewList
|
||||
|
|
@ -766,16 +781,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||
case .contacts:
|
||||
mappedListMode = .contacts
|
||||
}
|
||||
let mappedSortMode: EngineStoryViewListContext.SortMode
|
||||
switch self.configuration.sortMode {
|
||||
case .repostsFirst:
|
||||
mappedSortMode = .repostsFirst
|
||||
case .reactionsFirst:
|
||||
mappedSortMode = .reactionsFirst
|
||||
case .recentFirst:
|
||||
mappedSortMode = .recentFirst
|
||||
}
|
||||
self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: mappedSortMode, parentSource: component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)])
|
||||
self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: self.configuration.sortMode.sortMode, parentSource: component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -886,7 +892,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||
presence: nil,
|
||||
selectionState: .none,
|
||||
hasNext: true,
|
||||
action: { _ in
|
||||
action: { _, _ in
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -1351,25 +1357,26 @@ final class StoryItemSetViewListComponent: Component {
|
|||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortReactions, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.contextMenu.primaryColor)
|
||||
}, additionalLeftIcon: { theme in
|
||||
if sortMode != .reactionsFirst {
|
||||
return nil
|
||||
}
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.sortMode != .reactionsFirst {
|
||||
self.sortMode = .reactionsFirst
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})))
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortReactions, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.contextMenu.primaryColor)
|
||||
}, additionalLeftIcon: { theme in
|
||||
if sortMode != .reactionsFirst {
|
||||
return nil
|
||||
}
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.sortMode != .reactionsFirst {
|
||||
self.sortMode = .reactionsFirst
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortRecent, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Time"), color: theme.contextMenu.primaryColor)
|
||||
}, additionalLeftIcon: { theme in
|
||||
|
|
@ -1392,7 +1399,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||
items.append(.separator)
|
||||
|
||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ViewList_ContextSortInfo, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: component.peerId.isGroupOrChannel ? component.strings.Story_ViewList_ContextSortChannelInfo : component.strings.Story_ViewList_ContextSortInfo, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||
|
||||
let contextItems = ContextController.Items(content: .list(items))
|
||||
|
||||
|
|
@ -1432,6 +1440,12 @@ final class StoryItemSetViewListComponent: Component {
|
|||
var updateSubState = false
|
||||
|
||||
if self.mainViewList == nil {
|
||||
if component.peerId.isGroupOrChannel {
|
||||
self.sortMode = .recentFirst
|
||||
} else {
|
||||
self.sortMode = .reactionsFirst
|
||||
}
|
||||
|
||||
self.mainViewListDisposable?.dispose()
|
||||
self.mainViewListDisposable = nil
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||
public let context: AccountContext
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let peer: EnginePeer
|
||||
public let storyItem: EngineStoryItem
|
||||
public let myReaction: MyReaction?
|
||||
public let isChannel: Bool
|
||||
|
|
@ -56,6 +57,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer,
|
||||
storyItem: EngineStoryItem,
|
||||
myReaction: MyReaction?,
|
||||
isChannel: Bool,
|
||||
|
|
@ -73,6 +75,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
self.storyItem = storyItem
|
||||
self.myReaction = myReaction
|
||||
self.isChannel = isChannel
|
||||
|
|
@ -98,6 +101,9 @@ public final class StoryFooterPanelComponent: Component {
|
|||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.storyItem != rhs.storyItem {
|
||||
return false
|
||||
}
|
||||
|
|
@ -186,6 +192,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||
self.avatarsView.alpha = 0.7
|
||||
self.viewStatsCountText.alpha = 0.7
|
||||
self.viewStatsLabelText.view?.alpha = 0.7
|
||||
self.viewsIconView.alpha = 0.7
|
||||
self.reactionStatsIcon?.alpha = 0.7
|
||||
self.reactionStatsText?.alpha = 0.7
|
||||
self.repostStatsIcon?.alpha = 0.7
|
||||
|
|
@ -194,6 +201,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||
self.avatarsView.alpha = 1.0
|
||||
self.viewStatsCountText.alpha = 1.0
|
||||
self.viewStatsLabelText.view?.alpha = 1.0
|
||||
self.viewsIconView.alpha = 1.0
|
||||
self.reactionStatsIcon?.alpha = 1.0
|
||||
self.reactionStatsText?.alpha = 1.0
|
||||
self.repostStatsIcon?.alpha = 1.0
|
||||
|
|
@ -202,6 +210,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||
self.avatarsView.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
self.viewStatsCountText.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
self.viewStatsLabelText.view?.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
self.viewsIconView.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
self.reactionStatsIcon?.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
self.reactionStatsText?.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
self.repostStatsIcon?.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||
|
|
@ -383,7 +392,14 @@ public final class StoryFooterPanelComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
self.viewStatsButton.isEnabled = viewCount != 0 && !component.isChannel
|
||||
var displayViewLists = false
|
||||
if case let .channel(channel) = component.peer, channel.flags.contains(.isCreator) || channel.adminRights?.rights.contains(.canPostStories) == true {
|
||||
displayViewLists = reactionCount != 0 || forwardCount != 0
|
||||
} else {
|
||||
displayViewLists = viewCount != 0 && !component.isChannel
|
||||
}
|
||||
|
||||
self.viewStatsButton.isEnabled = displayViewLists
|
||||
|
||||
var rightContentOffset: CGFloat = availableSize.width - 12.0
|
||||
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Stories/HeaderForward.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Stories/HeaderForward.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "arrow.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
351
submodules/TelegramUI/Images.xcassets/Stories/HeaderForward.imageset/arrow.pdf
vendored
Normal file
351
submodules/TelegramUI/Images.xcassets/Stories/HeaderForward.imageset/arrow.pdf
vendored
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 2 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 13.000000 12.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
1.000000 0.000000 -0.000000 1.000000 1.330078 -0.721191 cm
|
||||
5.315626 8.533691 m
|
||||
5.315626 10.192076 l
|
||||
5.315626 10.556320 5.315626 10.738442 5.388546 10.825513 c
|
||||
5.451862 10.901115 5.547148 10.942396 5.645606 10.936879 c
|
||||
5.759001 10.930527 5.891866 10.805965 6.157596 10.556844 c
|
||||
9.926538 7.023460 l
|
||||
10.061858 6.896598 10.129518 6.833168 10.154650 6.759085 c
|
||||
10.176737 6.693978 10.176737 6.623405 10.154650 6.558298 c
|
||||
10.129518 6.484215 10.061858 6.420784 9.926539 6.293923 c
|
||||
6.157597 2.760540 l
|
||||
5.891867 2.511417 5.759001 2.386856 5.645606 2.380504 c
|
||||
5.547148 2.374987 5.451862 2.416267 5.388546 2.491870 c
|
||||
5.315626 2.578940 5.315626 2.761063 5.315626 3.125307 c
|
||||
5.315626 4.783691 l
|
||||
2.581792 4.783691 1.121608 3.510042 0.458909 2.676931 c
|
||||
0.280517 2.452666 0.191321 2.340534 0.141585 2.333771 c
|
||||
0.096309 2.327615 0.057156 2.342980 0.028153 2.378287 c
|
||||
-0.003707 2.417074 0.006321 2.548758 0.026378 2.812124 c
|
||||
0.161226 4.582819 0.951126 8.533691 5.315626 8.533691 c
|
||||
h
|
||||
0.000000 0.000000 0.000000 scn
|
||||
f*
|
||||
n
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
1028
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Length 4 0 R
|
||||
/FunctionType 4
|
||||
/Domain [ 0.000000 1.000000 ]
|
||||
/Range [ 0.000000 1.000000 ]
|
||||
>>
|
||||
stream
|
||||
{ 0 gt { 0 } { 1 } ifelse }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
27
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /ExtGState << /E1 << /SMask << /Type /Mask
|
||||
/G 1 0 R
|
||||
/S /Alpha
|
||||
/TR 3 0 R
|
||||
>>
|
||||
/Type /ExtGState
|
||||
>> >> >>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Length 7 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 1.330078 -0.721191 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
5.315626 8.533691 m
|
||||
5.315626 7.203691 l
|
||||
6.050164 7.203691 6.645626 7.799152 6.645626 8.533691 c
|
||||
5.315626 8.533691 l
|
||||
h
|
||||
5.315626 4.783691 m
|
||||
6.645626 4.783691 l
|
||||
6.645626 5.136429 6.505501 5.474720 6.256078 5.724144 c
|
||||
6.006654 5.973567 5.668364 6.113691 5.315626 6.113691 c
|
||||
5.315626 4.783691 l
|
||||
h
|
||||
0.458909 2.676931 m
|
||||
-0.581952 3.504886 l
|
||||
0.458909 2.676931 l
|
||||
h
|
||||
0.141585 2.333771 m
|
||||
-0.037609 3.651644 l
|
||||
0.141585 2.333771 l
|
||||
h
|
||||
0.028153 2.378287 m
|
||||
1.055869 3.222504 l
|
||||
0.028153 2.378287 l
|
||||
h
|
||||
5.645606 2.380504 m
|
||||
5.571209 3.708421 l
|
||||
5.645606 2.380504 l
|
||||
h
|
||||
5.388546 2.491870 m
|
||||
4.368898 1.637928 l
|
||||
5.388546 2.491870 l
|
||||
h
|
||||
9.926538 7.023460 m
|
||||
9.016897 6.053176 l
|
||||
9.926538 7.023460 l
|
||||
h
|
||||
10.154650 6.759085 m
|
||||
8.895151 6.331811 l
|
||||
10.154650 6.759085 l
|
||||
h
|
||||
9.926539 6.293923 m
|
||||
10.836181 5.323638 l
|
||||
9.926539 6.293923 l
|
||||
h
|
||||
10.154650 6.558298 m
|
||||
11.414148 6.131024 l
|
||||
10.154650 6.558298 l
|
||||
h
|
||||
5.388546 10.825513 m
|
||||
6.408195 9.971571 l
|
||||
5.388546 10.825513 l
|
||||
h
|
||||
5.645606 10.936879 m
|
||||
5.720003 12.264797 l
|
||||
5.645606 10.936879 l
|
||||
h
|
||||
6.645626 8.533691 m
|
||||
6.645626 10.192076 l
|
||||
3.985626 10.192076 l
|
||||
3.985626 8.533691 l
|
||||
6.645626 8.533691 l
|
||||
h
|
||||
5.247954 9.586559 m
|
||||
9.016897 6.053176 l
|
||||
10.836181 7.993745 l
|
||||
7.067238 11.527128 l
|
||||
5.247954 9.586559 l
|
||||
h
|
||||
9.016897 7.264207 m
|
||||
5.247955 3.730824 l
|
||||
7.067239 1.790256 l
|
||||
10.836181 5.323638 l
|
||||
9.016897 7.264207 l
|
||||
h
|
||||
6.645626 3.125307 m
|
||||
6.645626 4.783691 l
|
||||
3.985626 4.783691 l
|
||||
3.985626 3.125307 l
|
||||
6.645626 3.125307 l
|
||||
h
|
||||
5.315626 6.113691 m
|
||||
2.097498 6.113691 0.279446 4.587790 -0.581952 3.504886 c
|
||||
1.499771 1.848978 l
|
||||
1.963770 2.432294 3.066086 3.453691 5.315626 3.453691 c
|
||||
5.315626 6.113691 l
|
||||
h
|
||||
1.352538 2.711129 m
|
||||
1.413560 3.512409 1.620141 4.689168 2.210945 5.629186 c
|
||||
2.747699 6.483207 3.627555 7.203691 5.315626 7.203691 c
|
||||
5.315626 9.863691 l
|
||||
2.639197 9.863691 0.941854 8.608740 -0.041179 7.044650 c
|
||||
-0.970163 5.566558 -1.225956 3.882534 -1.299782 2.913118 c
|
||||
1.352538 2.711129 l
|
||||
h
|
||||
-0.581952 3.504886 m
|
||||
-0.682445 3.378551 -0.720813 3.331185 -0.740086 3.309600 c
|
||||
-0.750286 3.298176 -0.718989 3.335414 -0.659294 3.384659 c
|
||||
-0.627593 3.410811 -0.566207 3.458532 -0.478809 3.506605 c
|
||||
-0.394197 3.553145 -0.242563 3.623776 -0.037609 3.651644 c
|
||||
0.320778 1.015898 l
|
||||
0.717179 1.069798 0.973219 1.283091 1.033420 1.332752 c
|
||||
1.129403 1.411933 1.203473 1.492488 1.244089 1.537976 c
|
||||
1.326445 1.630214 1.421872 1.751047 1.499771 1.848978 c
|
||||
-0.581952 3.504886 l
|
||||
h
|
||||
-1.299782 2.913118 m
|
||||
-1.308334 2.800830 -1.320285 2.652165 -1.322062 2.530396 c
|
||||
-1.322950 2.469575 -1.322850 2.358324 -1.304198 2.231652 c
|
||||
-1.290535 2.138864 -1.241301 1.828356 -0.999563 1.534072 c
|
||||
1.055869 3.222504 l
|
||||
1.177860 3.073997 1.240899 2.929724 1.273622 2.836102 c
|
||||
1.306749 2.741327 1.320904 2.663443 1.327426 2.619150 c
|
||||
1.334278 2.572617 1.336326 2.537593 1.337061 2.520598 c
|
||||
1.337841 2.502546 1.337658 2.491820 1.337655 2.491581 c
|
||||
1.337645 2.490877 1.337704 2.495204 1.338235 2.506417 c
|
||||
1.338756 2.517421 1.339593 2.532197 1.340889 2.552054 c
|
||||
1.343585 2.593356 1.347357 2.643099 1.352538 2.711129 c
|
||||
-1.299782 2.913118 l
|
||||
h
|
||||
-0.037609 3.651644 m
|
||||
0.141479 3.675995 0.351744 3.662107 0.564653 3.578549 c
|
||||
0.777561 3.494993 0.941147 3.362162 1.055869 3.222504 c
|
||||
-0.999563 1.534072 l
|
||||
-0.677662 1.142203 -0.181727 0.947571 0.320778 1.015898 c
|
||||
-0.037609 3.651644 l
|
||||
h
|
||||
5.247955 3.730824 m
|
||||
5.179151 3.666320 5.129662 3.619975 5.087789 3.581986 c
|
||||
5.067758 3.563814 5.052710 3.550512 5.041348 3.540738 c
|
||||
5.029866 3.530859 5.024831 3.526900 5.024859 3.526922 c
|
||||
5.025069 3.527086 5.027243 3.528785 5.031198 3.531670 c
|
||||
5.035140 3.534546 5.041450 3.539039 5.049938 3.544694 c
|
||||
5.066150 3.555496 5.095299 3.573881 5.135852 3.594389 c
|
||||
5.216256 3.635052 5.367123 3.696987 5.571209 3.708421 c
|
||||
5.720003 1.052586 l
|
||||
6.213777 1.080250 6.554987 1.346669 6.664093 1.432044 c
|
||||
6.802816 1.540595 6.953648 1.683764 7.067239 1.790256 c
|
||||
5.247955 3.730824 l
|
||||
h
|
||||
3.985626 3.125307 m
|
||||
3.985626 2.969605 3.984338 2.761649 4.000025 2.586203 c
|
||||
4.012363 2.448214 4.051368 2.017074 4.368898 1.637928 c
|
||||
6.408195 3.345812 l
|
||||
6.539436 3.189104 6.597436 3.036680 6.622764 2.950212 c
|
||||
6.635538 2.906600 6.642061 2.872761 6.645269 2.853546 c
|
||||
6.646949 2.843485 6.647987 2.835810 6.648585 2.830967 c
|
||||
6.649184 2.826108 6.649432 2.823360 6.649456 2.823094 c
|
||||
6.649459 2.823059 6.648904 2.829440 6.648258 2.844573 c
|
||||
6.647618 2.859546 6.647029 2.879622 6.646587 2.906664 c
|
||||
6.645662 2.963193 6.645626 3.030995 6.645626 3.125307 c
|
||||
3.985626 3.125307 l
|
||||
h
|
||||
5.571209 3.708421 m
|
||||
5.891789 3.726381 6.202040 3.591971 6.408195 3.345812 c
|
||||
4.368898 1.637928 l
|
||||
4.701684 1.240564 5.202506 1.023593 5.720003 1.052586 c
|
||||
5.571209 3.708421 l
|
||||
h
|
||||
9.016897 6.053176 m
|
||||
9.052996 6.019333 9.074108 5.999492 9.090629 5.983498 c
|
||||
9.107223 5.967433 9.104925 5.968908 9.094450 5.980518 c
|
||||
9.075613 6.001396 8.965572 6.124225 8.895151 6.331811 c
|
||||
11.414148 7.186358 l
|
||||
11.318595 7.468027 11.162159 7.659614 11.069380 7.762442 c
|
||||
10.986407 7.854402 10.887315 7.945806 10.836181 7.993745 c
|
||||
9.016897 6.053176 l
|
||||
h
|
||||
10.836181 5.323638 m
|
||||
10.887316 5.371577 10.986407 5.462982 11.069380 5.554941 c
|
||||
11.162159 5.657770 11.318595 5.849356 11.414148 6.131024 c
|
||||
8.895151 6.985572 l
|
||||
8.965572 7.193157 9.075612 7.315987 9.094449 7.336864 c
|
||||
9.104925 7.348475 9.107223 7.349950 9.090629 7.333885 c
|
||||
9.074109 7.317891 9.052996 7.298050 9.016897 7.264207 c
|
||||
10.836181 5.323638 l
|
||||
h
|
||||
8.895151 6.331811 m
|
||||
8.823236 6.543798 8.823236 6.773584 8.895151 6.985572 c
|
||||
11.414148 6.131024 l
|
||||
11.530237 6.473225 11.530237 6.844158 11.414148 7.186358 c
|
||||
8.895151 6.331811 l
|
||||
h
|
||||
6.645626 10.192076 m
|
||||
6.645626 10.286387 6.645662 10.354190 6.646587 10.410719 c
|
||||
6.647029 10.437760 6.647618 10.457837 6.648258 10.472810 c
|
||||
6.648904 10.487943 6.649459 10.494324 6.649456 10.494288 c
|
||||
6.649432 10.494022 6.649184 10.491275 6.648585 10.486416 c
|
||||
6.647987 10.481573 6.646949 10.473898 6.645269 10.463837 c
|
||||
6.642061 10.444622 6.635538 10.410783 6.622764 10.367170 c
|
||||
6.597436 10.280703 6.539436 10.128279 6.408195 9.971571 c
|
||||
4.368898 11.679455 l
|
||||
4.051368 11.300309 4.012363 10.869169 4.000025 10.731180 c
|
||||
3.984338 10.555734 3.985626 10.347777 3.985626 10.192076 c
|
||||
6.645626 10.192076 l
|
||||
h
|
||||
7.067238 11.527128 m
|
||||
6.953648 11.633619 6.802816 11.776789 6.664093 11.885339 c
|
||||
6.554986 11.970715 6.213776 12.237133 5.720003 12.264797 c
|
||||
5.571209 9.608962 l
|
||||
5.367124 9.620396 5.216256 9.682331 5.135852 9.722993 c
|
||||
5.095299 9.743502 5.066151 9.761887 5.049939 9.772688 c
|
||||
5.041450 9.778344 5.035140 9.782837 5.031198 9.785712 c
|
||||
5.027243 9.788598 5.025069 9.790297 5.024859 9.790462 c
|
||||
5.024831 9.790483 5.029865 9.786524 5.041348 9.776645 c
|
||||
5.052709 9.766871 5.067758 9.753570 5.087788 9.735397 c
|
||||
5.129661 9.697409 5.179150 9.651064 5.247954 9.586559 c
|
||||
7.067238 11.527128 l
|
||||
h
|
||||
6.408195 9.971571 m
|
||||
6.202040 9.725411 5.891789 9.591002 5.571209 9.608962 c
|
||||
5.720003 12.264797 l
|
||||
5.202506 12.293790 4.701684 12.076818 4.368898 11.679455 c
|
||||
6.408195 9.971571 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
6868
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 13.000000 12.000000 ]
|
||||
/Resources 5 0 R
|
||||
/Contents 6 0 R
|
||||
/Parent 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<< /Kids [ 8 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<< /Pages 9 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000001286 00000 n
|
||||
0000001309 00000 n
|
||||
0000001484 00000 n
|
||||
0000001505 00000 n
|
||||
0000001817 00000 n
|
||||
0000008741 00000 n
|
||||
0000008764 00000 n
|
||||
0000008937 00000 n
|
||||
0000009011 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 10 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
9071
|
||||
%%EOF
|
||||
|
|
@ -7310,8 +7310,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
let chatWallpaper = self.context.account.viewTracker.peerView(peerId)
|
||||
|> take(1)
|
||||
|> map { view -> TelegramWallpaper? in
|
||||
if let cachedUserData = view.cachedData as? CachedUserData {
|
||||
return cachedUserData.wallpaper
|
||||
if let cachedData = view.cachedData as? CachedUserData {
|
||||
return cachedData.wallpaper
|
||||
} else if let cachedData = view.cachedData as? CachedChannelData {
|
||||
return cachedData.wallpaper
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -7463,6 +7465,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
themeEmoticon = cachedData.themeEmoticon
|
||||
} else if let cachedData = cachedData as? CachedChannelData {
|
||||
themeEmoticon = cachedData.themeEmoticon
|
||||
chatWallpaper = cachedData.wallpaper
|
||||
}
|
||||
|
||||
strongSelf.chatThemeEmoticonPromise.set(.single(themeEmoticon))
|
||||
|
|
@ -13274,7 +13277,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case .gift:
|
||||
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
||||
if !premiumGiftOptions.isEmpty {
|
||||
let controller = PremiumGiftAttachmentScreen(context: context, peerId: peer.id, options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in
|
||||
let controller = PremiumGiftAttachmentScreen(context: context, peerIds: [peer.id], options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
strongSelf.push(c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ final class ComposeControllerNode: ASDisplayNode {
|
|||
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: {
|
||||
openCreateNewChannelImpl?()
|
||||
})
|
||||
], includeChatList: false)), displayPermissionPlaceholder: false)
|
||||
], includeChatList: false, topPeers: false)), displayPermissionPlaceholder: false)
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,13 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = true //count != 0 || self.params.alwaysEnabled
|
||||
case .premiumGifting:
|
||||
let maxCount: Int32 = 10
|
||||
var count = 0
|
||||
if case let .contacts(contactsNode) = self.contactsNode.contentNode {
|
||||
count = contactsNode.selectionState?.selectedPeerIndices.count ?? 0
|
||||
}
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(count)/\(maxCount)")
|
||||
case .channelCreation:
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
|
|
@ -310,7 +317,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||
switch strongSelf.mode {
|
||||
case .groupCreation, .peerSelection, .chatSelection:
|
||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || strongSelf.params.alwaysEnabled
|
||||
case .channelCreation:
|
||||
case .channelCreation, .premiumGifting:
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -318,6 +325,9 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||
case .groupCreation:
|
||||
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
|
||||
case .premiumGifting:
|
||||
let maxCount: Int32 = 10
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(updatedCount)/\(maxCount)")
|
||||
case .peerSelection, .channelCreation, .chatSelection:
|
||||
break
|
||||
}
|
||||
|
|
@ -389,13 +399,16 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||
switch strongSelf.mode {
|
||||
case .groupCreation, .peerSelection, .chatSelection:
|
||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || strongSelf.params.alwaysEnabled
|
||||
case .channelCreation:
|
||||
case .channelCreation, .premiumGifting:
|
||||
break
|
||||
}
|
||||
switch strongSelf.mode {
|
||||
case .groupCreation:
|
||||
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
|
||||
case .premiumGifting:
|
||||
let maxCount: Int32 = 10
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(updatedCount)/\(maxCount)")
|
||||
case .peerSelection, .channelCreation, .chatSelection:
|
||||
break
|
||||
}
|
||||
|
|
@ -500,8 +513,14 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
|||
}
|
||||
}
|
||||
self.contactsNode.complete = { [weak self] in
|
||||
if let strongSelf = self, let rightBarButtonItem = strongSelf.navigationItem.rightBarButtonItem, rightBarButtonItem.isEnabled {
|
||||
strongSelf.rightNavigationButtonPressed()
|
||||
if let strongSelf = self {
|
||||
var available = true
|
||||
if let rightBarButtonItem = strongSelf.navigationItem.rightBarButtonItem {
|
||||
available = rightBarButtonItem.isEnabled
|
||||
}
|
||||
if available {
|
||||
strongSelf.rightNavigationButtonPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import ChatListUI
|
|||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EditableTokenListNode
|
||||
import SolidRoundedButtonNode
|
||||
|
||||
private struct SearchResultEntry: Identifiable {
|
||||
let index: Int
|
||||
|
|
@ -72,6 +73,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
private let footerPanelNode: FooterPanelNode?
|
||||
|
||||
private let isPeerEnabled: ((EnginePeer) -> Bool)?
|
||||
|
||||
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer) -> Void)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?) {
|
||||
|
|
@ -85,18 +88,28 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
|
||||
self.isPeerEnabled = isPeerEnabled
|
||||
|
||||
var proceedImpl: (() -> Void)?
|
||||
|
||||
var placeholder: String
|
||||
var includeChatList = false
|
||||
switch mode {
|
||||
case let .peerSelection(_, searchGroups, searchChannels):
|
||||
includeChatList = searchGroups || searchChannels
|
||||
if searchGroups {
|
||||
placeholder = self.presentationData.strings.Contacts_SearchUsersAndGroupsLabel
|
||||
} else {
|
||||
placeholder = self.presentationData.strings.Contacts_SearchLabel
|
||||
}
|
||||
default:
|
||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||
case let .peerSelection(_, searchGroups, searchChannels):
|
||||
includeChatList = searchGroups || searchChannels
|
||||
if searchGroups {
|
||||
placeholder = self.presentationData.strings.Contacts_SearchUsersAndGroupsLabel
|
||||
} else {
|
||||
placeholder = self.presentationData.strings.Contacts_SearchLabel
|
||||
}
|
||||
self.footerPanelNode = nil
|
||||
case .premiumGifting:
|
||||
placeholder = self.presentationData.strings.Premium_Gift_ContactSelection_Placeholder
|
||||
|
||||
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
|
||||
proceedImpl?()
|
||||
})
|
||||
default:
|
||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||
self.footerPanelNode = nil
|
||||
}
|
||||
|
||||
if case let .chatSelection(chatSelection) = mode {
|
||||
|
|
@ -132,7 +145,11 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
}
|
||||
self.contentNode = .chats(chatListNode)
|
||||
} else {
|
||||
self.contentNode = .contacts(ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList)), filters: filters, selectionState: ContactListNodeGroupSelectionState()))
|
||||
var displayTopPeers = false
|
||||
if case .premiumGifting = mode {
|
||||
displayTopPeers = true
|
||||
}
|
||||
self.contentNode = .contacts(ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, selectionState: ContactListNodeGroupSelectionState()))
|
||||
}
|
||||
|
||||
self.tokenListNode = EditableTokenListNode(context: self.context, presentationTheme: self.presentationData.theme, theme: EditableTokenListNodeTheme(backgroundColor: .clear, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, tokenBackgroundColor: self.presentationData.theme.list.itemCheckColors.strokeColor.withAlphaComponent(0.25), selectedTextColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, selectedBackgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.rootController.keyboardColor), placeholder: placeholder)
|
||||
|
|
@ -215,6 +232,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
searchGroups = true
|
||||
searchChannels = true
|
||||
globalSearch = false
|
||||
case .premiumGifting:
|
||||
searchChatList = true
|
||||
}
|
||||
let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups, searchChannels: searchChannels, globalSearch: globalSearch)), filters: filters, isPeerEnabled: strongSelf.isPeerEnabled, selectionState: selectionState, isSearch: true)
|
||||
searchResultsNode.openPeer = { peer, _ in
|
||||
|
|
@ -248,6 +267,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
self.tokenListNode.textReturned = { [weak self] in
|
||||
self?.complete?()
|
||||
}
|
||||
|
||||
if let footerPanelNode = self.footerPanelNode {
|
||||
proceedImpl = { [weak self] in
|
||||
self?.complete?()
|
||||
}
|
||||
self.addSubnode(footerPanelNode)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -284,6 +310,21 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
insets.top += tokenListHeight
|
||||
headerInsets.top += tokenListHeight
|
||||
|
||||
if let footerPanelNode = self.footerPanelNode {
|
||||
var count = 0
|
||||
if case let .contacts(contactListNode) = self.contentNode {
|
||||
count = contactListNode.selectionState?.selectedPeerIndices.count ?? 0
|
||||
}
|
||||
footerPanelNode.count = count
|
||||
let panelHeight = footerPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition)
|
||||
if count == 0 {
|
||||
transition.updateFrame(node: footerPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)))
|
||||
} else {
|
||||
insets.bottom += panelHeight
|
||||
transition.updateFrame(node: footerPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)))
|
||||
}
|
||||
}
|
||||
|
||||
switch self.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition)
|
||||
|
|
@ -318,3 +359,68 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class FooterPanelNode: ASDisplayNode {
|
||||
private let theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let button: SolidRoundedButtonView
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat)?
|
||||
|
||||
var count: Int = 0 {
|
||||
didSet {
|
||||
if self.count != oldValue && self.count > 0 {
|
||||
self.button.title = self.strings.Premium_Gift_ContactSelection_Proceed
|
||||
self.button.badge = "\(self.count)"
|
||||
|
||||
if let (width, sideInset, bottomInset) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||
|
||||
self.button = SolidRoundedButtonView(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 10.0)
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
||||
self.button.pressed = {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.addSubview(self.button)
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = (width, sideInset, bottomInset)
|
||||
let topInset: CGFloat = 9.0
|
||||
var bottomInset = bottomInset
|
||||
bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0)
|
||||
|
||||
let buttonInset: CGFloat = 16.0 + sideInset
|
||||
let buttonWidth = width - buttonInset * 2.0
|
||||
let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition)
|
||||
transition.updateFrame(view: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight))
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
return topInset + buttonHeight + bottomInset
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
|||
self.filters = filters
|
||||
|
||||
var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
|
||||
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false)), filters: filters, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
|
||||
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: false)), filters: filters, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
|
||||
contextActionImpl?(peer, node, gesture, nil)
|
||||
} : nil, multipleSelection: multipleSelection)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue