mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
6bf08c72ad
commit
10ac25e1cb
@ -13789,4 +13789,12 @@ Sorry for the inconvenience.";
|
||||
"Notification.StarsGift.SentSomeone" = "Someone sent you a gift";
|
||||
"Notification.StarsGift.UpgradeChannel" = "A gift was turned into a unique collectible";
|
||||
|
||||
"Gift.View.TonGiftInfo" = "This gift is owned by a TON account. [View >]()";
|
||||
"Gift.View.ViewTonAddressUrl" = "https://tonviewer.com/%@";
|
||||
"Gift.View.CopiedAddress" = "TON address copied to clipboard.";
|
||||
|
||||
"NameColor.AddProfileIcons" = "Add Icons To Profile";
|
||||
"NameColor.AddRepliesIcons" = "Add Icons To Replies";
|
||||
"NameColor.GiftTitle" = "USE A GIFT";
|
||||
"NameColor.GiftInfo" = "Apply your collectible's unique look to your profile.";
|
||||
"NameColor.WearCollectible" = "Wear Collectible";
|
||||
|
@ -16,7 +16,7 @@ public protocol ContactSelectionController: ViewController {
|
||||
|
||||
public enum ContactSelectionControllerMode {
|
||||
case generic
|
||||
case starsGifting(birthdays: [EnginePeer.Id: TelegramBirthday]?, hasActions: Bool, showSelf: Bool)
|
||||
case starsGifting(birthdays: [EnginePeer.Id: TelegramBirthday]?, hasActions: Bool, showSelf: Bool, selfSubtitle: String?)
|
||||
}
|
||||
|
||||
public struct ContactListAdditionalOption: Equatable {
|
||||
|
@ -573,7 +573,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
case let .custom(showSelf, sections):
|
||||
case let .custom(showSelf, selfSubtitle, sections):
|
||||
if !topPeers.isEmpty {
|
||||
var index: Int = 0
|
||||
|
||||
@ -637,7 +637,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
if showSelf, let accountPeer {
|
||||
if let peer = topPeers.first(where: { $0.id == accountPeer.id }) {
|
||||
let header = ChatListSearchItemHeader(type: .text(strings.Premium_Gift_ContactSelection_ThisIsYou.uppercased(), AnyHashable(10)), theme: theme, strings: strings)
|
||||
entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, strings.Premium_Gift_ContactSelection_BuySelf))
|
||||
entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, selfSubtitle))
|
||||
existingPeerIds.insert(.peer(peer.id))
|
||||
}
|
||||
}
|
||||
@ -882,7 +882,7 @@ public enum ContactListPresentation {
|
||||
public enum TopPeers {
|
||||
case none
|
||||
case recent
|
||||
case custom(showSelf: Bool, sections: [(title: String, peerIds: [EnginePeer.Id], hasActions: Bool)])
|
||||
case custom(showSelf: Bool, selfSubtitle: String?, sections: [(title: String, peerIds: [EnginePeer.Id], hasActions: Bool)])
|
||||
}
|
||||
|
||||
case orderedByPresence(options: [ContactListAdditionalOption])
|
||||
@ -1728,7 +1728,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
case let .custom(showSelf, sections):
|
||||
case let .custom(showSelf, _, sections):
|
||||
var peerIds: [EnginePeer.Id] = []
|
||||
if showSelf {
|
||||
peerIds.append(context.account.peerId)
|
||||
|
@ -2926,6 +2926,10 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
||||
}
|
||||
|
||||
public func adapterContainerLayoutUpdatedSize(_ size: CGSize, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat, inputHeight: CGFloat, orientation: UIInterfaceOrientation, isRegular: Bool, animated: Bool) {
|
||||
var intrinsicInsets = intrinsicInsets
|
||||
if intrinsicInsets.top.isZero {
|
||||
intrinsicInsets.top = statusBarHeight
|
||||
}
|
||||
let layout = ContainerViewLayout(
|
||||
size: size,
|
||||
metrics: LayoutMetrics(widthClass: isRegular ? .regular : .compact, heightClass: isRegular ? .regular : .compact, orientation: nil),
|
||||
|
@ -569,6 +569,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
|
||||
break;
|
||||
}
|
||||
|
||||
_cancelButton.modernHighlight = false;
|
||||
[UIView animateWithDuration:0.2f animations:^
|
||||
{
|
||||
_portraitToolsWrapperView.alpha = 0.0f;
|
||||
@ -688,6 +689,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
|
||||
[_cropView hideImageForCustomTransition];
|
||||
[_cropView animateTransitionOutSwitching:false];
|
||||
|
||||
_cancelButton.modernHighlight = false;
|
||||
[_cancelButton.layer removeAllAnimations];
|
||||
|
||||
_previewView.hidden = true;
|
||||
[UIView animateWithDuration:0.3f animations:^
|
||||
{
|
||||
|
@ -1507,9 +1507,9 @@
|
||||
doneButtonType = TGPhotoEditorDoneButtonDone;
|
||||
|
||||
if (sideButtonsHiddenInCrop) {
|
||||
[_portraitToolbarView setCancelDoneButtonsHidden:true animated:true];
|
||||
[_portraitToolbarView setCenterButtonsHidden:false animated:true];
|
||||
[_landscapeToolbarView setAllButtonsHidden:false animated:true];
|
||||
[_portraitToolbarView setCancelDoneButtonsHidden:true animated:false];
|
||||
[_portraitToolbarView setCenterButtonsHidden:false animated:false];
|
||||
[_landscapeToolbarView setAllButtonsHidden:false animated:false];
|
||||
} else {
|
||||
[_portraitToolbarView setAllButtonsHidden:false animated:false];
|
||||
[_landscapeToolbarView setAllButtonsHidden:false animated:false];
|
||||
|
@ -514,7 +514,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
||||
}, openWallpaperSettings: {
|
||||
pushControllerImpl?(ThemeGridController(context: context))
|
||||
}, openNameColorSettings: {
|
||||
pushControllerImpl?(PeerNameColorScreen(context: context, subject: .account))
|
||||
pushControllerImpl?(UserAppearanceScreen(context: context))
|
||||
}, selectAccentColor: { accentColor in
|
||||
selectAccentColorImpl?(accentColor)
|
||||
}, openAccentColorPicker: { themeReference, create in
|
||||
|
@ -1124,6 +1124,9 @@ private final class ProfileGiftsContextImpl {
|
||||
}
|
||||
if let index = self.filteredGifts.firstIndex(where: { $0.reference == reference }) {
|
||||
self.filteredGifts[index] = self.filteredGifts[index].withSavedToProfile(added)
|
||||
if !self.filter.contains(.hidden) && !added {
|
||||
self.filteredGifts.remove(at: index)
|
||||
}
|
||||
}
|
||||
self.pushState()
|
||||
}
|
||||
@ -1803,3 +1806,14 @@ func _internal_toggleStarGiftsNotifications(account: Account, peerId: EnginePeer
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
||||
public extension StarGift.UniqueGift {
|
||||
var itemFile: TelegramMediaFile? {
|
||||
for attribute in self.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
18
submodules/TelegramStringFormatting/Sources/Birthday.swift
Normal file
18
submodules/TelegramStringFormatting/Sources/Birthday.swift
Normal file
@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
import TelegramCore
|
||||
|
||||
public func hasBirthdayToday(cachedData: CachedUserData) -> Bool {
|
||||
if let birthday = cachedData.birthday {
|
||||
return hasBirthdayToday(birthday: birthday)
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func hasBirthdayToday(birthday: TelegramBirthday) -> Bool {
|
||||
let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date())
|
||||
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@ -315,7 +315,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
private var displayedGiftTooltip = false
|
||||
private func presentGiftTooltip() {
|
||||
guard let context = self.context, !self.displayedGiftTooltip else {
|
||||
guard let context = self.context, !self.displayedGiftTooltip, let parentController = self.interfaceInteraction?.chatController() else {
|
||||
return
|
||||
}
|
||||
self.displayedGiftTooltip = true
|
||||
@ -332,7 +332,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
|
||||
|
||||
Queue.mainQueue().after(0.4, {
|
||||
let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: nil)
|
||||
let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: parentController.view)
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
@ -134,11 +134,15 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
||||
|
||||
if let peerStatus = title.peerStatus {
|
||||
let statusContent: EmojiStatusComponent.Content
|
||||
var statusParticleColor: UIColor?
|
||||
switch peerStatus {
|
||||
case .premium:
|
||||
statusContent = .premium(color: self.theme.list.itemAccentColor)
|
||||
case let .emoji(emoji):
|
||||
statusContent = .animation(content: .customEmoji(fileId: emoji.fileId), size: CGSize(width: 22.0, height: 22.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
case let .emoji(emojiStatus):
|
||||
statusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 22.0, height: 22.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
if let color = emojiStatus.color {
|
||||
statusParticleColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
}
|
||||
}
|
||||
|
||||
var titleCredibilityIconTransition: ComponentTransition
|
||||
@ -164,6 +168,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
content: statusContent,
|
||||
particleColor: statusParticleColor,
|
||||
isVisibleForAnimations: true,
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self, let titleCredibilityIconView = strongSelf.titleCredibilityIconView else {
|
||||
|
@ -151,7 +151,7 @@ private enum ChatTitleCredibilityIcon: Equatable {
|
||||
case scam
|
||||
case verified
|
||||
case premium
|
||||
case emojiStatus(PeerEmojiStatus, Int32?)
|
||||
case emojiStatus(PeerEmojiStatus)
|
||||
}
|
||||
|
||||
public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
@ -275,7 +275,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
} else if peer.isScam {
|
||||
titleCredibilityIcon = .scam
|
||||
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
|
||||
titleStatusIcon = .emojiStatus(emojiStatus, emojiStatus.color)
|
||||
titleStatusIcon = .emojiStatus(emojiStatus)
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
titleCredibilityIcon = .premium
|
||||
}
|
||||
@ -284,7 +284,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
titleCredibilityIcon = .verified
|
||||
}
|
||||
if let verificationIconFileId = peer.verificationIconFileId {
|
||||
titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil), nil)
|
||||
titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -839,7 +839,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased())
|
||||
case .scam:
|
||||
titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
|
||||
case let .emojiStatus(emojiStatus, _):
|
||||
case let .emojiStatus(emojiStatus):
|
||||
titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
}
|
||||
|
||||
@ -855,16 +855,16 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
titleVerifiedContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased())
|
||||
case .scam:
|
||||
titleVerifiedContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased())
|
||||
case let .emojiStatus(emojiStatus, _):
|
||||
case let .emojiStatus(emojiStatus):
|
||||
titleVerifiedContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
}
|
||||
|
||||
let titleStatusContent: EmojiStatusComponent.Content
|
||||
var titleStatusParticleColor: UIColor?
|
||||
switch self.titleStatusIcon {
|
||||
case let .emojiStatus(emojiStatus, color):
|
||||
case let .emojiStatus(emojiStatus):
|
||||
titleStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2))
|
||||
if let color {
|
||||
if let color = emojiStatus.color {
|
||||
titleStatusParticleColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
}
|
||||
default:
|
||||
|
@ -1149,7 +1149,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
if let previewItem = self.previewItem, let itemFile = previewItem.item.itemFile {
|
||||
if let previewItem = self.previewItem, let itemFile = previewItem.item.displayFile {
|
||||
let previewScreenView: ComponentView<Empty>
|
||||
var previewScreenTransition = transition
|
||||
if let current = self.previewScreenView {
|
||||
@ -1190,58 +1190,66 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
if let result = result, let previewItem = strongSelf.previewItem {
|
||||
var emojiString: String?
|
||||
if let itemFile = previewItem.item.itemFile {
|
||||
attributeLoop: for attribute in itemFile.attributes {
|
||||
switch attribute {
|
||||
case let .CustomEmoji(_, _, alt, _):
|
||||
emojiString = alt
|
||||
break attributeLoop
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let result, let previewItem = strongSelf.previewItem {
|
||||
let expirationDate: Int32? = result.timestamp
|
||||
if let itemGift = previewItem.item.itemGift {
|
||||
let _ = (strongSelf.context.engine.accountData.setStarGiftStatus(starGift: itemGift, expirationDate: expirationDate)
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
if let destinationView = strongSelf.controller?.destinationItemView() {
|
||||
strongSelf.animateOutToStatus(item: previewItem.item, sourceLayer: result.sourceView.layer, customEffectFile: nil, destinationView: destinationView, fromBackground: true)
|
||||
}
|
||||
}
|
||||
|
||||
let context = strongSelf.context
|
||||
let _ = (context.engine.stickers.availableReactions()
|
||||
|> take(1)
|
||||
|> mapToSignal { availableReactions -> Signal<String?, NoError> in
|
||||
guard let emojiString = emojiString, let availableReactions = availableReactions else {
|
||||
return .single(nil)
|
||||
}
|
||||
for reaction in availableReactions.reactions {
|
||||
if case let .builtin(value) = reaction.value, value == emojiString {
|
||||
if let aroundAnimation = reaction.aroundAnimation {
|
||||
return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource)
|
||||
|> take(1)
|
||||
|> map { data -> String? in
|
||||
if data.complete {
|
||||
return data.path
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
} else {
|
||||
var emojiString: String?
|
||||
if let itemFile = previewItem.item.itemFile {
|
||||
attributeLoop: for attribute in itemFile.attributes {
|
||||
switch attribute {
|
||||
case let .CustomEmoji(_, _, alt, _):
|
||||
emojiString = alt
|
||||
break attributeLoop
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { filePath in
|
||||
guard let strongSelf = self, let previewItem = strongSelf.previewItem, let destinationView = strongSelf.controller?.destinationItemView() else {
|
||||
return
|
||||
|
||||
let context = strongSelf.context
|
||||
let _ = (context.engine.stickers.availableReactions()
|
||||
|> take(1)
|
||||
|> mapToSignal { availableReactions -> Signal<String?, NoError> in
|
||||
guard let emojiString = emojiString, let availableReactions = availableReactions else {
|
||||
return .single(nil)
|
||||
}
|
||||
for reaction in availableReactions.reactions {
|
||||
if case let .builtin(value) = reaction.value, value == emojiString {
|
||||
if let aroundAnimation = reaction.aroundAnimation {
|
||||
return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource)
|
||||
|> take(1)
|
||||
|> map { data -> String? in
|
||||
if data.complete {
|
||||
return data.path
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let expirationDate: Int32? = result.timestamp
|
||||
|
||||
let _ = (strongSelf.context.engine.accountData.setEmojiStatus(file: previewItem.item.itemFile, expirationDate: expirationDate)
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
strongSelf.animateOutToStatus(item: previewItem.item, sourceLayer: result.sourceView.layer, customEffectFile: filePath, destinationView: destinationView, fromBackground: true)
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { filePath in
|
||||
guard let strongSelf = self, let previewItem = strongSelf.previewItem, let destinationView = strongSelf.controller?.destinationItemView() else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.engine.accountData.setEmojiStatus(file: previewItem.item.itemFile, expirationDate: expirationDate)
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
strongSelf.animateOutToStatus(item: previewItem.item, sourceLayer: result.sourceView.layer, customEffectFile: filePath, destinationView: destinationView, fromBackground: true)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
strongSelf.dismissedPreviewItem = strongSelf.previewItem
|
||||
strongSelf.previewItem = nil
|
||||
@ -1551,3 +1559,15 @@ private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to tar
|
||||
|
||||
return keyframes
|
||||
}
|
||||
|
||||
extension EmojiPagerContentComponent.Item {
|
||||
var displayFile: TelegramMediaFile? {
|
||||
if let file = self.itemFile {
|
||||
return file
|
||||
} else if let gift = self.itemGift {
|
||||
return gift.itemFile
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -572,14 +572,16 @@ public final class EntityKeyboardComponent: Component {
|
||||
for itemGroup in emojiContent.panelItemGroups {
|
||||
if !itemGroup.items.isEmpty {
|
||||
if let id = itemGroup.groupId.base as? String, id != "peerSpecific" {
|
||||
if id == "recent" || id == "liked" {
|
||||
if id == "recent" || id == "liked" || id == "collectible" {
|
||||
let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [
|
||||
"recent": .recent,
|
||||
"liked": .liked,
|
||||
"collectible": .collectible
|
||||
]
|
||||
let titleMapping: [String: String] = [
|
||||
"recent": component.strings.Stickers_Recent,
|
||||
"liked": "",
|
||||
"collectible": ""
|
||||
]
|
||||
if let icon = iconMapping[id], let title = titleMapping[id] {
|
||||
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
|
@ -277,6 +277,7 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
case saved
|
||||
case premium
|
||||
case liked
|
||||
case collectible
|
||||
}
|
||||
|
||||
let icon: Icon
|
||||
@ -363,6 +364,8 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
image = UIImage(bundleImageName: "Chat/Input/Media/PanelSavedIcon")
|
||||
case .liked:
|
||||
image = UIImage(bundleImageName: "Chat/Input/Media/PanelHeartIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
case .collectible:
|
||||
image = UIImage(bundleImageName: "Chat/Input/Media/PanelCollectibleIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
case .premium:
|
||||
image = generateImage(CGSize(width: 44.0, height: 44.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
@ -87,6 +87,7 @@ public final class GiftItemComponent: Component {
|
||||
case profile
|
||||
case thumbnail
|
||||
case preview
|
||||
case grid
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
@ -99,6 +100,7 @@ public final class GiftItemComponent: Component {
|
||||
let isLoading: Bool
|
||||
let isHidden: Bool
|
||||
let isSoldOut: Bool
|
||||
let isSelected: Bool
|
||||
let mode: Mode
|
||||
|
||||
public init(
|
||||
@ -112,6 +114,7 @@ public final class GiftItemComponent: Component {
|
||||
isLoading: Bool = false,
|
||||
isHidden: Bool = false,
|
||||
isSoldOut: Bool = false,
|
||||
isSelected: Bool = false,
|
||||
mode: Mode = .generic
|
||||
) {
|
||||
self.context = context
|
||||
@ -124,6 +127,7 @@ public final class GiftItemComponent: Component {
|
||||
self.isLoading = isLoading
|
||||
self.isHidden = isHidden
|
||||
self.isSoldOut = isSoldOut
|
||||
self.isSelected = isSelected
|
||||
self.mode = mode
|
||||
}
|
||||
|
||||
@ -158,6 +162,9 @@ public final class GiftItemComponent: Component {
|
||||
if lhs.isSoldOut != rhs.isSoldOut {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
if lhs.mode != rhs.mode {
|
||||
return false
|
||||
}
|
||||
@ -181,6 +188,7 @@ public final class GiftItemComponent: Component {
|
||||
private let ribbonText = ComponentView<Empty>()
|
||||
|
||||
private var animationLayer: InlineStickerItemLayer?
|
||||
private var selectionLayer: SimpleShapeLayer?
|
||||
|
||||
private var disposables = DisposableSet()
|
||||
private var fetchedFiles = Set<Int64>()
|
||||
@ -234,6 +242,10 @@ public final class GiftItemComponent: Component {
|
||||
size = CGSize(width: availableSize.width, height: availableSize.width)
|
||||
iconSize = CGSize(width: floor(size.width * 0.7), height: floor(size.width * 0.7))
|
||||
cornerRadius = floor(availableSize.width * 0.2)
|
||||
case .grid:
|
||||
size = CGSize(width: availableSize.width, height: availableSize.width)
|
||||
iconSize = CGSize(width: floor(size.width * 0.7), height: floor(size.width * 0.7))
|
||||
cornerRadius = 10.0
|
||||
case .preview:
|
||||
size = availableSize
|
||||
iconSize = CGSize(width: floor(size.width * 0.6), height: floor(size.width * 0.6))
|
||||
@ -587,6 +599,43 @@ public final class GiftItemComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if case .grid = component.mode {
|
||||
let lineWidth: CGFloat = 2.0
|
||||
let selectionFrame = CGRect(origin: .zero, size: size).insetBy(dx: 3.0, dy: 3.0)
|
||||
|
||||
if component.isSelected {
|
||||
let selectionLayer: SimpleShapeLayer
|
||||
if let current = self.selectionLayer {
|
||||
selectionLayer = current
|
||||
} else {
|
||||
selectionLayer = SimpleShapeLayer()
|
||||
self.selectionLayer = selectionLayer
|
||||
self.layer.addSublayer(selectionLayer)
|
||||
|
||||
selectionLayer.fillColor = UIColor.clear.cgColor
|
||||
selectionLayer.strokeColor = UIColor.white.cgColor
|
||||
selectionLayer.lineWidth = lineWidth
|
||||
selectionLayer.frame = selectionFrame
|
||||
selectionLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
let initialPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
selectionLayer.animate(from: initialPath, to: selectionLayer.path as AnyObject, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
selectionLayer.animateShapeLineWidth(from: 0.0, to: lineWidth, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
} else if let selectionLayer = self.selectionLayer {
|
||||
self.selectionLayer = nil
|
||||
|
||||
let targetPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
selectionLayer.animate(from: selectionLayer.path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||
selectionLayer.animateShapeLineWidth(from: selectionLayer.lineWidth, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
selectionLayer.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import TelegramStringFormatting
|
||||
import PlainButtonComponent
|
||||
import BlurredBackgroundComponent
|
||||
import PremiumStarComponent
|
||||
import ConfettiEffect
|
||||
import TextFormat
|
||||
import GiftItemComponent
|
||||
import InAppPurchaseManager
|
||||
|
@ -47,6 +47,7 @@ swift_library(
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/ProgressNavigationButtonNode",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||
"//submodules/ConfettiEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -233,7 +233,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
||||
case let .starGift(gift):
|
||||
media = [
|
||||
TelegramMediaAction(
|
||||
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: true, upgradeStars: item.upgradeStars, isRefunded: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil)
|
||||
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: gift.upgradeStars != nil, upgradeStars: item.upgradeStars, isRefunded: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import ProgressNavigationButtonNode
|
||||
import Markdown
|
||||
import GiftViewScreen
|
||||
import UndoUI
|
||||
import ConfettiEffect
|
||||
|
||||
final class GiftSetupScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -377,6 +378,8 @@ final class GiftSetupScreenComponent: Component {
|
||||
action: { _ in return true }
|
||||
)
|
||||
(navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current)
|
||||
|
||||
navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds))
|
||||
}
|
||||
|
||||
if let completion {
|
||||
|
@ -47,6 +47,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let cancel: (Bool) -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openAddress: (String) -> Void
|
||||
let copyAddress: (String) -> Void
|
||||
let updateSavedToProfile: (Bool) -> Void
|
||||
let convertToStars: () -> Void
|
||||
let openStarsIntro: () -> Void
|
||||
@ -66,6 +67,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
cancel: @escaping (Bool) -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openAddress: @escaping (String) -> Void,
|
||||
copyAddress: @escaping (String) -> Void,
|
||||
updateSavedToProfile: @escaping (Bool) -> Void,
|
||||
convertToStars: @escaping () -> Void,
|
||||
openStarsIntro: @escaping () -> Void,
|
||||
@ -84,6 +86,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
self.cancel = cancel
|
||||
self.openPeer = openPeer
|
||||
self.openAddress = openAddress
|
||||
self.copyAddress = copyAddress
|
||||
self.updateSavedToProfile = updateSavedToProfile
|
||||
self.convertToStars = convertToStars
|
||||
self.openStarsIntro = openStarsIntro
|
||||
@ -447,6 +450,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
var soldOut = false
|
||||
var nameHidden = false
|
||||
var upgraded = false
|
||||
var exported = false
|
||||
var canUpgrade = false
|
||||
var upgradeStars: Int64?
|
||||
var uniqueGift: StarGift.UniqueGift?
|
||||
@ -1082,6 +1086,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
descriptionText = descriptionText.replacingOccurrences(of: " >]", with: "\u{00A0}>]")
|
||||
let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
|
||||
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
||||
@ -1286,6 +1292,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
)
|
||||
))
|
||||
case let .address(address):
|
||||
exported = true
|
||||
|
||||
func formatAddress(_ str: String) -> String {
|
||||
var result = str
|
||||
let middleIndex = result.index(result.startIndex, offsetBy: str.count / 2)
|
||||
@ -1302,8 +1310,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: formatAddress(address), font: tableLargeMonospaceFont, textColor: tableLinkColor)), maximumNumberOfLines: 2, lineSpacing: 0.2)
|
||||
),
|
||||
action: {
|
||||
component.openAddress(address)
|
||||
component.cancel(true)
|
||||
component.copyAddress(address)
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -1403,6 +1410,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
var canTransfer = true
|
||||
if let peer = state.peerMap[peerId], case let .channel(channel) = peer, !channel.flags.contains(.isCreator) {
|
||||
canTransfer = false
|
||||
} else if subject.arguments?.transferStars == nil {
|
||||
canTransfer = false
|
||||
}
|
||||
|
||||
let buttonsCount = canTransfer ? 3 : 2
|
||||
@ -1871,13 +1880,17 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
originY += table.size.height + 23.0
|
||||
}
|
||||
|
||||
if incoming && !converted && !upgraded && !showUpgradePreview && !showWearPreview {
|
||||
if ((incoming && !converted && !upgraded) || exported) && (!showUpgradePreview && !showWearPreview) {
|
||||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
|
||||
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
|
||||
}
|
||||
let descriptionText: String
|
||||
if savedToProfile {
|
||||
var addressToOpen: String?
|
||||
var descriptionText: String
|
||||
if let uniqueGift, case let .address(address) = uniqueGift.owner {
|
||||
addressToOpen = address
|
||||
descriptionText = strings.Gift_View_TonGiftInfo
|
||||
} else if savedToProfile {
|
||||
descriptionText = isChannelGift ? strings.Gift_View_DisplayedInfoHide_Channel : strings.Gift_View_DisplayedInfoHide
|
||||
} else if let upgradeStars, upgradeStars > 0 && !upgraded {
|
||||
descriptionText = isChannelGift ? strings.Gift_View_HiddenInfoShow_Channel : strings.Gift_View_HiddenInfoShow
|
||||
@ -1894,6 +1907,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
descriptionText = descriptionText.replacingOccurrences(of: " >]", with: "\u{00A0}>]")
|
||||
let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
|
||||
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
||||
@ -1917,10 +1932,15 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
},
|
||||
tapAction: { attributes, _ in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
component.updateSavedToProfile(!savedToProfile)
|
||||
Queue.mainQueue().after(0.6, {
|
||||
component.cancel(false)
|
||||
})
|
||||
if let addressToOpen {
|
||||
component.openAddress(addressToOpen)
|
||||
component.cancel(true)
|
||||
} else {
|
||||
component.updateSavedToProfile(!savedToProfile)
|
||||
Queue.mainQueue().after(0.6, {
|
||||
component.cancel(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -2201,6 +2221,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
|
||||
let subject: GiftViewScreen.Subject
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openAddress: (String) -> Void
|
||||
let copyAddress: (String) -> Void
|
||||
let updateSavedToProfile: (Bool) -> Void
|
||||
let convertToStars: () -> Void
|
||||
let openStarsIntro: () -> Void
|
||||
@ -2218,6 +2239,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
|
||||
subject: GiftViewScreen.Subject,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openAddress: @escaping (String) -> Void,
|
||||
copyAddress: @escaping (String) -> Void,
|
||||
updateSavedToProfile: @escaping (Bool) -> Void,
|
||||
convertToStars: @escaping () -> Void,
|
||||
openStarsIntro: @escaping () -> Void,
|
||||
@ -2234,6 +2256,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
|
||||
self.subject = subject
|
||||
self.openPeer = openPeer
|
||||
self.openAddress = openAddress
|
||||
self.copyAddress = copyAddress
|
||||
self.updateSavedToProfile = updateSavedToProfile
|
||||
self.convertToStars = convertToStars
|
||||
self.openStarsIntro = openStarsIntro
|
||||
@ -2286,6 +2309,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
|
||||
},
|
||||
openPeer: context.component.openPeer,
|
||||
openAddress: context.component.openAddress,
|
||||
copyAddress: context.component.copyAddress,
|
||||
updateSavedToProfile: context.component.updateSavedToProfile,
|
||||
convertToStars: context.component.convertToStars,
|
||||
openStarsIntro: context.component.openStarsIntro,
|
||||
@ -2447,6 +2471,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
|
||||
var openPeerImpl: ((EnginePeer) -> Void)?
|
||||
var openAddressImpl: ((String) -> Void)?
|
||||
var copyAddressImpl: ((String) -> Void)?
|
||||
var updateSavedToProfileImpl: ((Bool) -> Void)?
|
||||
var convertToStarsImpl: (() -> Void)?
|
||||
var openStarsIntroImpl: (() -> Void)?
|
||||
@ -2470,6 +2495,9 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
openAddress: { address in
|
||||
openAddressImpl?(address)
|
||||
},
|
||||
copyAddress: { address in
|
||||
copyAddressImpl?(address)
|
||||
},
|
||||
updateSavedToProfile: { added in
|
||||
updateSavedToProfileImpl?(added)
|
||||
},
|
||||
@ -2537,6 +2565,18 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
}
|
||||
copyAddressImpl = { [weak self] address in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = address
|
||||
|
||||
self.dismissAllTooltips()
|
||||
|
||||
self.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Gift_View_CopiedAddress), elevatedLayout: false, position: .bottom, action: { _ in return true }), in: .current)
|
||||
|
||||
HapticFeedback().tap()
|
||||
}
|
||||
updateSavedToProfileImpl = { [weak self] added in
|
||||
guard let self, let arguments = self.subject.arguments, let reference = arguments.reference else {
|
||||
return
|
||||
@ -2618,6 +2658,11 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
let configuration = GiftConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
let starsConvertMaxDate = arguments.date + configuration.convertToStarsPeriod
|
||||
|
||||
var isChannelGift = false
|
||||
if case let .peer(peerId, _) = reference, peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
isChannelGift = true
|
||||
}
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if currentTime > starsConvertMaxDate {
|
||||
let days: Int32 = Int32(ceil(Float(configuration.convertToStarsPeriod) / 86400.0))
|
||||
@ -2653,8 +2698,10 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
|
||||
if let navigationController {
|
||||
Queue.mainQueue().after(0.5) {
|
||||
if let starsContext = context.starsContext {
|
||||
navigationController.pushViewController(context.sharedContext.makeStarsTransactionsScreen(context: context, starsContext: starsContext), animated: true)
|
||||
if !isChannelGift {
|
||||
if let starsContext = context.starsContext {
|
||||
navigationController.pushViewController(context.sharedContext.makeStarsTransactionsScreen(context: context, starsContext: starsContext), animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
if let lastController = navigationController.viewControllers.last as? ViewController {
|
||||
|
@ -127,7 +127,7 @@ final class MediaEditorComposer {
|
||||
if values.isSticker {
|
||||
self.maskImage = roundedCornersMaskImage(size: CGSize(width: floor(1080.0 * 0.97), height: floor(1080.0 * 0.97)))
|
||||
} else if values.isAvatar {
|
||||
self.maskImage = rectangleMaskImage(size: CGSize(width: floor(1080.0 * 0.97), height: floor(1080.0 * 0.97)))
|
||||
self.maskImage = rectangleMaskImage(size: CGSize(width: 1080.0, height: 1080.0))
|
||||
}
|
||||
|
||||
if let drawing = values.drawing, let drawingImage = CIImage(image: drawing, options: [.colorSpace: self.colorSpace]) {
|
||||
@ -227,7 +227,7 @@ public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inp
|
||||
if values.isSticker {
|
||||
maskImage = roundedCornersMaskImage(size: CGSize(width: floor(1080.0 * 0.97), height: floor(1080.0 * 0.97)))
|
||||
} else if values.isAvatar {
|
||||
maskImage = rectangleMaskImage(size: CGSize(width: floor(1080.0 * 0.97), height: floor(1080.0 * 0.97)))
|
||||
maskImage = rectangleMaskImage(size: CGSize(width: 1080.0, height: 1080.0))
|
||||
} else if let outputDimensions {
|
||||
maskImage = rectangleMaskImage(size: outputDimensions.aspectFitted(CGSize(width: 1080.0, height: 1080.0)))
|
||||
}
|
||||
@ -299,10 +299,14 @@ private func makeEditorImageFrameComposition(context: CIContext, inputImage: CII
|
||||
}
|
||||
|
||||
resultImage = resultImage.transformed(by: CGAffineTransform(translationX: dimensions.width / 2.0, y: dimensions.height / 2.0))
|
||||
if values.isSticker || values.isAvatar {
|
||||
if values.isSticker {
|
||||
let minSize = min(dimensions.width, dimensions.height)
|
||||
let scaledSize = CGSize(width: floor(minSize * 0.97), height: floor(minSize * 0.97))
|
||||
resultImage = resultImage.transformed(by: CGAffineTransform(translationX: -(dimensions.width - scaledSize.width) / 2.0, y: -(dimensions.height - scaledSize.height) / 2.0)).cropped(to: CGRect(origin: .zero, size: scaledSize))
|
||||
} else if values.isAvatar {
|
||||
let minSize = min(dimensions.width, dimensions.height)
|
||||
let scaledSize = CGSize(width: minSize, height: minSize)
|
||||
resultImage = resultImage.transformed(by: CGAffineTransform(translationX: -(dimensions.width - scaledSize.width) / 2.0, y: -(dimensions.height - scaledSize.height) / 2.0)).cropped(to: CGRect(origin: .zero, size: scaledSize))
|
||||
} else if values.isCover, let outputDimensions {
|
||||
let minSize = min(dimensions.width, dimensions.height)
|
||||
let scaledSize = outputDimensions.aspectFitted(CGSize(width: minSize, height: minSize))
|
||||
|
@ -5950,7 +5950,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
||||
if let stickerBackgroundView = self.stickerBackgroundView, let stickerOverlayLayer = self.stickerOverlayLayer, let stickerFrameLayer = self.stickerFrameLayer {
|
||||
let stickerFrameFraction: CGFloat
|
||||
switch controller.mode {
|
||||
case .avatarEditor, .stickerEditor:
|
||||
case .stickerEditor:
|
||||
stickerFrameFraction = 0.97
|
||||
default:
|
||||
stickerFrameFraction = 1.0
|
||||
|
@ -813,6 +813,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
do {
|
||||
self.currentCredibilityIcon = credibilityIcon
|
||||
|
||||
var emojiStatusSize: CGSize?
|
||||
var currentEmojiStatus: PeerEmojiStatus?
|
||||
let emojiRegularStatusContent: EmojiStatusComponent.Content
|
||||
let emojiExpandedStatusContent: EmojiStatusComponent.Content
|
||||
@ -823,6 +824,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
case .premium:
|
||||
emojiRegularStatusContent = .premium(color: navigationContentsAccentColor)
|
||||
emojiExpandedStatusContent = .premium(color: navigationContentsAccentColor)
|
||||
emojiStatusSize = CGSize(width: 30.0, height: 30.0)
|
||||
case .verified:
|
||||
emojiRegularStatusContent = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .large)
|
||||
emojiExpandedStatusContent = .verified(fillColor: navigationContentsAccentColor, foregroundColor: .clear, sizeType: .large)
|
||||
@ -845,6 +847,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
content: emojiRegularStatusContent,
|
||||
size: emojiStatusSize,
|
||||
isVisibleForAnimations: true,
|
||||
useSharedAnimation: true,
|
||||
action: { [weak self] in
|
||||
@ -866,6 +869,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
content: emojiExpandedStatusContent,
|
||||
size: emojiStatusSize,
|
||||
isVisibleForAnimations: true,
|
||||
useSharedAnimation: true,
|
||||
action: { [weak self] in
|
||||
|
@ -1173,7 +1173,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
tabsAlpha = 1.0 - tabsOffset / tabsHeight
|
||||
}
|
||||
tabsAlpha *= tabsAlpha
|
||||
transition.updateFrame(node: self.tabsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -tabsOffset), size: CGSize(width: size.width, height: tabsHeight)))
|
||||
transition.updateFrame(node: self.tabsContainerNode, frame: CGRect(origin: CGPoint(x: sideInset, y: -tabsOffset), size: CGSize(width: size.width - sideInset * 2.0, height: tabsHeight)))
|
||||
transition.updateAlpha(node: self.tabsContainerNode, alpha: tabsAlpha)
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
@ -1183,7 +1183,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
|
||||
transition.updateFrame(node: self.tabsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
|
||||
self.tabsContainerNode.update(size: CGSize(width: size.width, height: tabsHeight), presentationData: presentationData, paneList: availablePanes.map { key in
|
||||
self.tabsContainerNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: tabsHeight), presentationData: presentationData, paneList: availablePanes.map { key in
|
||||
let title: String
|
||||
var icons: [TelegramMediaFile] = []
|
||||
switch key {
|
||||
|
@ -8906,7 +8906,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
|
||||
private func editingOpenNameColorSetup() {
|
||||
if self.peerId == self.context.account.peerId {
|
||||
let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .account)
|
||||
let controller = UserAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData)
|
||||
self.controller?.push(controller)
|
||||
} else if let peer = self.data?.peer, peer is TelegramChannel {
|
||||
self.controller?.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, boostStatus: self.boostStatus))
|
||||
@ -9815,16 +9815,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
let premiumGiftOptions = self.data?.premiumGiftOptions ?? []
|
||||
let premiumOptions = premiumGiftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
|
||||
|
||||
var hasBirthday = false
|
||||
if let cachedUserData = self.data?.cachedData as? CachedUserData {
|
||||
hasBirthday = hasBirthdayToday(cachedData: cachedUserData)
|
||||
}
|
||||
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(
|
||||
context: self.context,
|
||||
peerId: self.peerId,
|
||||
premiumOptions: premiumOptions,
|
||||
hasBirthday: false,
|
||||
hasBirthday: hasBirthday,
|
||||
completion: { [weak self] in
|
||||
guard let self, let profileGiftsContext = self.data?.profileGiftsContext else {
|
||||
return
|
||||
}
|
||||
profileGiftsContext.reload()
|
||||
Queue.mainQueue().after(0.5) {
|
||||
profileGiftsContext.reload()
|
||||
}
|
||||
}
|
||||
)
|
||||
self.controller?.push(controller)
|
||||
@ -10916,7 +10923,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
guard let data = self.data, let channel = data.peer as? TelegramChannel, channel.hasPermission(.sendSomething), let giftsContext = data.profileGiftsContext else {
|
||||
guard let data = self.data, let channel = data.peer as? TelegramChannel, let giftsContext = data.profileGiftsContext else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -10979,18 +10986,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
toggleFilter(.unique)
|
||||
})))
|
||||
|
||||
items.append(.separator)
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Displayed, icon: { theme in
|
||||
return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, f in
|
||||
toggleFilter(.displayed)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Hidden, icon: { theme in
|
||||
return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, f in
|
||||
toggleFilter(.hidden)
|
||||
})))
|
||||
if channel.hasPermission(.sendSomething) {
|
||||
items.append(.separator)
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Displayed, icon: { theme in
|
||||
return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, f in
|
||||
toggleFilter(.displayed)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Hidden, icon: { theme in
|
||||
return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, f in
|
||||
toggleFilter(.hidden)
|
||||
})))
|
||||
}
|
||||
|
||||
return ContextController.Items(content: .list(items))
|
||||
}
|
||||
@ -12019,7 +12028,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
|
||||
}
|
||||
case .gifts:
|
||||
if let data = self.data, let channel = data.peer as? TelegramChannel, channel.hasPermission(.sendSomething) {
|
||||
if let data = self.data, let channel = data.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .sort, isForExpandedView: true))
|
||||
}
|
||||
default:
|
||||
@ -12294,14 +12303,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
|
||||
self.didPlayBirthdayAnimation = true
|
||||
|
||||
var hasBirthdayToday = false
|
||||
let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date())
|
||||
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
|
||||
hasBirthdayToday = true
|
||||
}
|
||||
|
||||
if hasBirthdayToday {
|
||||
if hasBirthdayToday(cachedData: cachedData) {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
var birthdayItemFrame: CGRect?
|
||||
if let section = self.regularSections[InfoSection.peerInfo] {
|
||||
|
@ -9,6 +9,7 @@ import AccountContext
|
||||
import ContextUI
|
||||
import PhotoResources
|
||||
import TelegramUIPreferences
|
||||
import TelegramStringFormatting
|
||||
import ItemListPeerItem
|
||||
import ItemListPeerActionItem
|
||||
import MergeLists
|
||||
@ -48,6 +49,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
private var panelButton: SolidRoundedButtonNode?
|
||||
private var panelCheck: ComponentView<Empty>?
|
||||
|
||||
private let emptyResultsClippingView = UIView()
|
||||
private let emptyResultsAnimation = ComponentView<Empty>()
|
||||
private let emptyResultsTitle = ComponentView<Empty>()
|
||||
private let emptyResultsAction = ComponentView<Empty>()
|
||||
@ -93,7 +95,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.scrollNode)
|
||||
|
||||
|
||||
self.dataDisposable = (profileGifts.state
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||
guard let self else {
|
||||
@ -125,6 +127,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
self.emptyResultsClippingView.clipsToBounds = true
|
||||
self.scrollNode.view.addSubview(self.emptyResultsClippingView)
|
||||
}
|
||||
|
||||
public func ensureMessageIsVisible(id: MessageId) {
|
||||
@ -145,7 +150,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
let optionSpacing: CGFloat = 10.0
|
||||
let itemsSideInset = params.sideInset + 16.0
|
||||
|
||||
let defaultItemsInRow = 3
|
||||
let defaultItemsInRow = params.size.width > params.size.height ? 5 : 3
|
||||
let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow))
|
||||
let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow)
|
||||
let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
|
||||
@ -489,6 +494,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
let emptyAnimationSpacing: CGFloat = 20.0
|
||||
let emptyTextSpacing: CGFloat = 18.0
|
||||
|
||||
self.emptyResultsClippingView.isHidden = false
|
||||
|
||||
transition.setFrame(view: self.emptyResultsClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: 48.0), size: self.scrollNode.frame.size))
|
||||
transition.setBounds(view: self.emptyResultsClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: 48.0), size: self.scrollNode.frame.size))
|
||||
|
||||
let emptyResultsTitleSize = self.emptyResultsTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
@ -517,7 +527,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
return
|
||||
}
|
||||
self.profileGifts.updateFilter(.All)
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
@ -545,7 +556,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.0
|
||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||
self.scrollNode.view.addSubview(view)
|
||||
self.emptyResultsClippingView.addSubview(view)
|
||||
view.playOnce()
|
||||
}
|
||||
view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size)
|
||||
@ -555,7 +566,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.0
|
||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||
self.scrollNode.view.addSubview(view)
|
||||
self.emptyResultsClippingView.addSubview(view)
|
||||
}
|
||||
view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size)
|
||||
transition.setPosition(view: view, position: emptyResultsTitleFrame.center)
|
||||
@ -564,7 +575,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.0
|
||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||
self.scrollNode.view.addSubview(view)
|
||||
self.emptyResultsClippingView.addSubview(view)
|
||||
}
|
||||
view.bounds = CGRect(origin: .zero, size: emptyResultsActionFrame.size)
|
||||
transition.setPosition(view: view, position: emptyResultsActionFrame.center)
|
||||
@ -572,6 +583,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
} else {
|
||||
if let view = self.emptyResultsAnimation.view {
|
||||
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||
self.emptyResultsClippingView.isHidden = true
|
||||
view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
@ -650,8 +662,21 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
self.chatControllerInteraction.navigationController()?.pushViewController(controller)
|
||||
})
|
||||
} else {
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(context: self.context, peerId: self.peerId, premiumOptions: [], hasBirthday: false, completion: nil)
|
||||
self.chatControllerInteraction.navigationController()?.pushViewController(controller)
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Birthday(id: self.peerId))
|
||||
|> deliverOnMainQueue).start(next: { birthday in
|
||||
var hasBirthday = false
|
||||
if let birthday {
|
||||
hasBirthday = hasBirthdayToday(birthday: birthday)
|
||||
}
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(
|
||||
context: self.context,
|
||||
peerId: self.peerId,
|
||||
premiumOptions: [],
|
||||
hasBirthday: hasBirthday,
|
||||
completion: nil
|
||||
)
|
||||
self.chatControllerInteraction.navigationController()?.pushViewController(controller)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
|
||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1071,7 +1071,8 @@ final class ChannelAppearanceScreenComponent: Component {
|
||||
isGroup ? environment.strings.Conversation_StatusMembers(Int32($0)) : environment.strings.Conversation_StatusSubscribers(Int32($0))
|
||||
},
|
||||
files: self.cachedIconFiles,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
showBackground: false
|
||||
),
|
||||
params: ListViewItemLayoutParams(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
||||
))),
|
||||
|
@ -0,0 +1,198 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import GiftItemComponent
|
||||
import PlainButtonComponent
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
|
||||
final class GiftListItemComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let gifts: [StarGift.UniqueGift]
|
||||
let selectedId: Int64?
|
||||
let selectionUpdated: (StarGift.UniqueGift) -> Void
|
||||
let tag: AnyObject?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
gifts: [StarGift.UniqueGift],
|
||||
selectedId: Int64?,
|
||||
selectionUpdated: @escaping (StarGift.UniqueGift) -> Void,
|
||||
tag: AnyObject?
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.gifts = gifts
|
||||
self.selectedId = selectedId
|
||||
self.selectionUpdated = selectionUpdated
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftListItemComponent, rhs: GiftListItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.gifts != rhs.gifts {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedId != rhs.selectedId {
|
||||
return false
|
||||
}
|
||||
if lhs.tag !== rhs.tag {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, ComponentTaggedView {
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let component = self.component, let componentTag = component.tag {
|
||||
let tag = tag as AnyObject
|
||||
if componentTag === tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private var giftItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
private var component: GiftListItemComponent?
|
||||
private var state: EmptyComponentState?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private var visibleBounds: CGRect?
|
||||
func updateVisibleBounds(_ bounds: CGRect) {
|
||||
self.visibleBounds = bounds
|
||||
self.state?.updated()
|
||||
}
|
||||
|
||||
func update(component: GiftListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let topInset: CGFloat = 13.0
|
||||
let spacing: CGFloat = 10.0
|
||||
let itemsInRow = 3
|
||||
let rowsCount = Int(ceil(CGFloat(component.gifts.count) / CGFloat(itemsInRow)))
|
||||
|
||||
let itemWidth = floorToScreenPixels((availableSize.width - sideInset * 2.0 - spacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow))
|
||||
var validIds: [AnyHashable] = []
|
||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: itemWidth, height: itemWidth))
|
||||
|
||||
let contentHeight = topInset * 2.0 + itemWidth * CGFloat(rowsCount) + spacing * CGFloat(rowsCount - 1)
|
||||
|
||||
var index: Int32 = 0
|
||||
for gift in component.gifts {
|
||||
var isVisible = false
|
||||
if let visibleBounds = self.visibleBounds, visibleBounds.intersects(itemFrame) {
|
||||
isVisible = true
|
||||
}
|
||||
if isVisible {
|
||||
let id = gift.id
|
||||
let itemId = AnyHashable(id)
|
||||
validIds.append(itemId)
|
||||
|
||||
var itemTransition = transition
|
||||
let visibleItem: ComponentView<Empty>
|
||||
if let current = self.giftItems[itemId] {
|
||||
visibleItem = current
|
||||
} else {
|
||||
visibleItem = ComponentView()
|
||||
self.giftItems[itemId] = visibleItem
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
let _ = visibleItem.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift),
|
||||
ribbon: nil,
|
||||
isHidden: false,
|
||||
isSelected: gift.id == component.selectedId,
|
||||
mode: .grid
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.selectionUpdated(gift)
|
||||
},
|
||||
animateAlpha: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: itemFrame.size
|
||||
)
|
||||
if let itemView = visibleItem.view {
|
||||
if itemView.superview == nil {
|
||||
self.addSubview(itemView)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
itemFrame.origin.x += itemFrame.width + spacing
|
||||
if itemFrame.maxX > availableSize.width {
|
||||
itemFrame.origin.x = sideInset
|
||||
itemFrame.origin.y += itemFrame.height + spacing
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, item) in self.giftItems {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
if let itemView = item.view {
|
||||
if !transition.animation.isImmediate {
|
||||
itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
itemView.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
itemView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.giftItems.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -308,7 +308,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size)
|
||||
strongSelf.view.addSubview(snapshot)
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.0, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
@ -29,8 +29,9 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListIte
|
||||
let subtitleString: String?
|
||||
let files: [Int64: TelegramMediaFile]
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let showBackground: Bool
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, topInset: CGFloat, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder) {
|
||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, topInset: CGFloat, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder, showBackground: Bool) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.componentTheme = componentTheme
|
||||
@ -41,6 +42,7 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListIte
|
||||
self.subtitleString = subtitleString
|
||||
self.files = files
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.showBackground = showBackground
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
@ -102,7 +104,9 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListIte
|
||||
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.showBackground != rhs.showBackground {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -114,6 +118,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private var icon: ComponentView<Empty>?
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
@ -121,6 +126,9 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
private var item: PeerNameColorProfilePreviewItem?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
@ -137,10 +145,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
self.clipsToBounds = true
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
|
||||
func asyncLayout() -> (_ item: PeerNameColorProfilePreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
||||
return { [weak self] item, params, neighbors in
|
||||
let separatorHeight = UIScreenPixel
|
||||
@ -158,15 +163,19 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let previousItem = self.item, (previousItem.peer?.profileColor != item.peer?.profileColor) || (previousItem.peer?.profileBackgroundEmojiId != item.peer?.profileBackgroundEmojiId) {
|
||||
if let previousItem = self.item, (previousItem.peer?.nameColor != item.peer?.nameColor) || (previousItem.peer?.profileColor != item.peer?.profileColor) || (previousItem.peer?.profileBackgroundEmojiId != item.peer?.profileBackgroundEmojiId) {
|
||||
UIView.transition(with: self.view, duration: 0.2, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
|
||||
})
|
||||
}
|
||||
self.item = item
|
||||
|
||||
self.backgroundNode.backgroundColor = item.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
self.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
self.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if self.backgroundNode.supernode == nil {
|
||||
self.addSubnode(self.backgroundNode)
|
||||
}
|
||||
if self.topStripeNode.supernode == nil {
|
||||
self.addSubnode(self.topStripeNode)
|
||||
}
|
||||
@ -178,10 +187,19 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
if params.isStandalone {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: item.showBackground ? 1.0 : 0.0)
|
||||
transition.updateAlpha(node: self.bottomStripeNode, alpha: item.showBackground ? 1.0 : 0.0)
|
||||
|
||||
self.backgroundNode.isHidden = false
|
||||
self.topStripeNode.isHidden = true
|
||||
self.bottomStripeNode.isHidden = true
|
||||
self.bottomStripeNode.isHidden = false
|
||||
self.maskNode.isHidden = true
|
||||
|
||||
self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
} else {
|
||||
self.backgroundNode.isHidden = true
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
@ -219,11 +237,19 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
let avatarSize: CGFloat = 104.0
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floor((coverFrame.width - avatarSize) * 0.5), y: coverFrame.minY + item.topInset + 24.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
|
||||
let subject: PeerInfoCoverComponent.Subject?
|
||||
if let status = item.peer?.emojiStatus, case .starGift = status.content {
|
||||
subject = .status(status)
|
||||
} else if let peer = item.peer {
|
||||
subject = .peer(peer)
|
||||
} else {
|
||||
subject = nil
|
||||
}
|
||||
let _ = self.background.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PeerInfoCoverComponent(
|
||||
context: item.context,
|
||||
subject: item.peer.flatMap { .peer($0) },
|
||||
subject: subject,
|
||||
files: item.files,
|
||||
isDark: item.theme.overallDarkAppearance,
|
||||
avatarCenter: avatarFrame.center,
|
||||
@ -238,7 +264,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
if let backgroundView = self.background.view {
|
||||
if backgroundView.superview == nil {
|
||||
backgroundView.clipsToBounds = true
|
||||
self.view.insertSubview(backgroundView, at: 0)
|
||||
self.view.insertSubview(backgroundView, at: 1)
|
||||
}
|
||||
backgroundView.frame = coverFrame
|
||||
}
|
||||
@ -296,7 +322,9 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
let statusColor: UIColor
|
||||
if let peer = item.peer, peer.profileColor != nil {
|
||||
if let status = item.peer?.emojiStatus, case .starGift = status.content {
|
||||
statusColor = .white
|
||||
} else if let peer = item.peer, peer.profileColor != nil {
|
||||
statusColor = .white
|
||||
} else {
|
||||
statusColor = item.theme.list.itemCheckColors.fillColor
|
||||
@ -321,7 +349,13 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
let backgroundColor: UIColor
|
||||
let titleColor: UIColor
|
||||
let subtitleColor: UIColor
|
||||
if let peer = item.peer, let profileColor = peer.profileColor {
|
||||
var particleColor: UIColor?
|
||||
if let status = item.peer?.emojiStatus, case let .starGift(_, _, _, _, _, _, outerColor, _, _) = status.content {
|
||||
titleColor = .white
|
||||
backgroundColor = UIColor(rgb: UInt32(bitPattern: outerColor))
|
||||
subtitleColor = UIColor(white: 1.0, alpha: 0.6).blitOver(backgroundColor.withMultiplied(hue: 1.0, saturation: 2.2, brightness: 1.5), alpha: 1.0)
|
||||
particleColor = .white
|
||||
} else if let peer = item.peer, let profileColor = peer.profileColor {
|
||||
titleColor = .white
|
||||
backgroundColor = item.context.peerNameColors.getProfile(profileColor).main
|
||||
subtitleColor = UIColor(white: 1.0, alpha: 0.6).blitOver(backgroundColor.withMultiplied(hue: 1.0, saturation: 2.2, brightness: 1.5), alpha: 1.0)
|
||||
@ -384,6 +418,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
animationCache: item.context.animationCache,
|
||||
animationRenderer: item.context.animationRenderer,
|
||||
content: emojiStatusContent,
|
||||
particleColor: particleColor,
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
@ -424,6 +459,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
self.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
self.backgroundNode.frame = backgroundFrame
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,936 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import UndoUI
|
||||
import EntityKeyboard
|
||||
import PremiumUI
|
||||
import PeerNameColorItem
|
||||
|
||||
private final class PeerNameColorScreenArguments {
|
||||
let context: AccountContext
|
||||
let updateNameColor: (PeerNameColor?) -> Void
|
||||
let updateBackgroundEmojiId: (Int64?, TelegramMediaFile?) -> Void
|
||||
let resetColor: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
updateNameColor: @escaping (PeerNameColor?) -> Void,
|
||||
updateBackgroundEmojiId: @escaping (Int64?, TelegramMediaFile?) -> Void,
|
||||
resetColor: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateNameColor = updateNameColor
|
||||
self.updateBackgroundEmojiId = updateBackgroundEmojiId
|
||||
self.resetColor = resetColor
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerNameColorScreenSection: Int32 {
|
||||
case nameColor
|
||||
case backgroundEmoji
|
||||
}
|
||||
|
||||
private enum PeerNameColorScreenEntry: ItemListNodeEntry {
|
||||
enum StableId: Hashable {
|
||||
case colorHeader
|
||||
case colorMessage
|
||||
case colorProfile
|
||||
case colorPicker
|
||||
case removeColor
|
||||
case colorDescription
|
||||
case backgroundEmojiHeader
|
||||
case backgroundEmoji
|
||||
}
|
||||
|
||||
case colorHeader(String)
|
||||
case colorMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, items: [PeerNameColorChatPreviewItem.MessageItem])
|
||||
case colorProfile(peer: EnginePeer?, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder)
|
||||
case colorPicker(colors: PeerNameColors, currentColor: PeerNameColor?, isProfile: Bool)
|
||||
case removeColor
|
||||
case colorDescription(String)
|
||||
case backgroundEmojiHeader(String, String?)
|
||||
case backgroundEmoji(EmojiPagerContentComponent, UIColor, Bool, Bool)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .colorHeader, .colorMessage, .colorProfile, .colorPicker, .removeColor, .colorDescription:
|
||||
return PeerNameColorScreenSection.nameColor.rawValue
|
||||
case .backgroundEmojiHeader, .backgroundEmoji:
|
||||
return PeerNameColorScreenSection.backgroundEmoji.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: StableId {
|
||||
switch self {
|
||||
case .colorHeader:
|
||||
return .colorHeader
|
||||
case .colorMessage:
|
||||
return .colorMessage
|
||||
case .colorProfile:
|
||||
return .colorProfile
|
||||
case .colorPicker:
|
||||
return .colorPicker
|
||||
case .removeColor:
|
||||
return.removeColor
|
||||
case .colorDescription:
|
||||
return .colorDescription
|
||||
case .backgroundEmojiHeader:
|
||||
return .backgroundEmojiHeader
|
||||
case .backgroundEmoji:
|
||||
return .backgroundEmoji
|
||||
}
|
||||
}
|
||||
|
||||
var sortId: Int {
|
||||
switch self {
|
||||
case .colorHeader:
|
||||
return 0
|
||||
case .colorMessage:
|
||||
return 1
|
||||
case .colorProfile:
|
||||
return 2
|
||||
case .colorPicker:
|
||||
return 3
|
||||
case .removeColor:
|
||||
return 4
|
||||
case .colorDescription:
|
||||
return 5
|
||||
case .backgroundEmojiHeader:
|
||||
return 6
|
||||
case .backgroundEmoji:
|
||||
return 7
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerNameColorScreenEntry, rhs: PeerNameColorScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .colorHeader(text):
|
||||
if case .colorHeader(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorMessage(lhsWallpaper, lhsFontSize, lhsBubbleCorners, lhsDateTimeFormat, lhsNameDisplayOrder, lhsItems):
|
||||
if case let .colorMessage(rhsWallpaper, rhsFontSize, rhsBubbleCorners, rhsDateTimeFormat, rhsNameDisplayOrder, rhsItems) = rhs, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsBubbleCorners == rhsBubbleCorners, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorProfile(lhsPeer, lhsFiles, lhsNameDisplayOrder):
|
||||
if case let .colorProfile(rhsPeer, rhsFiles, rhsNameDisplayOrder) = rhs {
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsFiles != rhsFiles {
|
||||
return false
|
||||
}
|
||||
if lhsNameDisplayOrder != rhsNameDisplayOrder {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorPicker(lhsColors, lhsCurrentColor, lhsIsProfile):
|
||||
if case let .colorPicker(rhsColors, rhsCurrentColor, rhsIsProfile) = rhs, lhsColors == rhsColors, lhsCurrentColor == rhsCurrentColor, lhsIsProfile == rhsIsProfile {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .removeColor:
|
||||
if case .removeColor = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .colorDescription(text):
|
||||
if case .colorDescription(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .backgroundEmojiHeader(text, action):
|
||||
if case .backgroundEmojiHeader(text, action) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .backgroundEmoji(lhsEmojiContent, lhsBackgroundIconColor, lhsIsProfile, lhsHasRemoveButton):
|
||||
if case let .backgroundEmoji(rhsEmojiContent, rhsBackgroundIconColor, rhsIsProfile, rhsHasRemoveButton) = rhs, lhsEmojiContent == rhsEmojiContent, lhsBackgroundIconColor == rhsBackgroundIconColor, lhsIsProfile == rhsIsProfile, lhsHasRemoveButton == rhsHasRemoveButton {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PeerNameColorScreenEntry, rhs: PeerNameColorScreenEntry) -> Bool {
|
||||
return lhs.sortId < rhs.sortId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! PeerNameColorScreenArguments
|
||||
switch self {
|
||||
case let .colorHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .colorMessage(wallpaper, fontSize, chatBubbleCorners, dateTimeFormat, nameDisplayOrder, items):
|
||||
return PeerNameColorChatPreviewItem(
|
||||
context: arguments.context,
|
||||
theme: presentationData.theme,
|
||||
componentTheme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
sectionId: self.section,
|
||||
fontSize: fontSize,
|
||||
chatBubbleCorners: chatBubbleCorners,
|
||||
wallpaper: wallpaper,
|
||||
dateTimeFormat: dateTimeFormat,
|
||||
nameDisplayOrder: nameDisplayOrder,
|
||||
messageItems: items
|
||||
)
|
||||
case let .colorProfile(peer, files, nameDisplayOrder):
|
||||
return PeerNameColorProfilePreviewItem(
|
||||
context: arguments.context,
|
||||
theme: presentationData.theme,
|
||||
componentTheme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
topInset: 0.0,
|
||||
sectionId: self.section,
|
||||
peer: peer,
|
||||
files: files,
|
||||
nameDisplayOrder: nameDisplayOrder
|
||||
)
|
||||
case let .colorPicker(colors, currentColor, isProfile):
|
||||
return PeerNameColorItem(
|
||||
theme: presentationData.theme,
|
||||
colors: colors,
|
||||
mode: isProfile ? .profile : .name,
|
||||
currentColor: currentColor,
|
||||
updated: { color in
|
||||
if let color {
|
||||
arguments.updateNameColor(color)
|
||||
}
|
||||
},
|
||||
sectionId: self.section
|
||||
)
|
||||
case .removeColor:
|
||||
return ItemListActionItem(presentationData: presentationData, title: presentationData.strings.ProfileColorSetup_ResetAction, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.resetColor()
|
||||
})
|
||||
case let .colorDescription(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .backgroundEmojiHeader(text, action):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, actionText: action, action: action != nil ? {
|
||||
arguments.updateBackgroundEmojiId(0, nil)
|
||||
} : nil, sectionId: self.section)
|
||||
case let .backgroundEmoji(emojiContent, backgroundIconColor, isProfileColor, hasRemoveButton):
|
||||
return EmojiPickerItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, emojiContent: emojiContent, backgroundIconColor: backgroundIconColor, isProfileColor: isProfileColor, hasRemoveButton: hasRemoveButton, sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeerNameColorScreenState: Equatable {
|
||||
var updatedNameColor: PeerNameColor?
|
||||
var updatedBackgroundEmojiId: Int64?
|
||||
var inProgress: Bool = false
|
||||
var needsBoosts: Bool = false
|
||||
|
||||
var updatedProfileColor: PeerNameColor?
|
||||
var hasUpdatedProfileColor: Bool = false
|
||||
var updatedProfileBackgroundEmojiId: Int64?
|
||||
var hasUpdatedProfileBackgroundEmojiId: Bool = false
|
||||
|
||||
var selectedTabIndex: Int = 0
|
||||
|
||||
var files: [Int64: TelegramMediaFile] = [:]
|
||||
}
|
||||
|
||||
private func peerNameColorScreenEntries(
|
||||
nameColors: PeerNameColors,
|
||||
presentationData: PresentationData,
|
||||
state: PeerNameColorScreenState,
|
||||
peer: EnginePeer?,
|
||||
isPremium: Bool,
|
||||
emojiContent: EmojiPagerContentComponent?
|
||||
) -> [PeerNameColorScreenEntry] {
|
||||
var entries: [PeerNameColorScreenEntry] = []
|
||||
|
||||
if let peer {
|
||||
let nameColor: PeerNameColor
|
||||
if let updatedNameColor = state.updatedNameColor {
|
||||
nameColor = updatedNameColor
|
||||
} else if let peerNameColor = peer.nameColor {
|
||||
nameColor = peerNameColor
|
||||
} else {
|
||||
nameColor = .blue
|
||||
}
|
||||
|
||||
let colors = nameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance)
|
||||
|
||||
let backgroundEmojiId: Int64?
|
||||
if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId {
|
||||
if updatedBackgroundEmojiId == 0 {
|
||||
backgroundEmojiId = nil
|
||||
} else {
|
||||
backgroundEmojiId = updatedBackgroundEmojiId
|
||||
}
|
||||
} else if let emojiId = peer.backgroundEmojiId {
|
||||
backgroundEmojiId = emojiId
|
||||
} else {
|
||||
backgroundEmojiId = nil
|
||||
}
|
||||
|
||||
let profileColor: PeerNameColor?
|
||||
if state.hasUpdatedProfileColor {
|
||||
profileColor = state.updatedProfileColor
|
||||
} else {
|
||||
profileColor = peer.profileColor
|
||||
}
|
||||
var selectedProfileEmojiId: Int64?
|
||||
if state.hasUpdatedProfileBackgroundEmojiId {
|
||||
selectedProfileEmojiId = state.updatedProfileBackgroundEmojiId
|
||||
} else {
|
||||
selectedProfileEmojiId = peer.profileBackgroundEmojiId
|
||||
}
|
||||
let profileColors = profileColor.flatMap { profileColor in nameColors.getProfile(profileColor, dark: presentationData.theme.overallDarkAppearance) }
|
||||
|
||||
let replyText: String
|
||||
let messageText: String
|
||||
if case .channel = peer {
|
||||
replyText = presentationData.strings.NameColor_ChatPreview_ReplyText_Channel
|
||||
messageText = presentationData.strings.NameColor_ChatPreview_MessageText_Channel
|
||||
} else {
|
||||
replyText = presentationData.strings.NameColor_ChatPreview_ReplyText_Account
|
||||
messageText = presentationData.strings.NameColor_ChatPreview_MessageText_Account
|
||||
}
|
||||
let messageItem = PeerNameColorChatPreviewItem.MessageItem(
|
||||
outgoing: false,
|
||||
peerId: PeerId(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)),
|
||||
author: peer.compactDisplayTitle,
|
||||
photo: peer.profileImageRepresentations,
|
||||
nameColor: nameColor,
|
||||
backgroundEmojiId: backgroundEmojiId,
|
||||
reply: (peer.compactDisplayTitle, replyText, nameColor),
|
||||
linkPreview: (presentationData.strings.NameColor_ChatPreview_LinkSite, presentationData.strings.NameColor_ChatPreview_LinkTitle, presentationData.strings.NameColor_ChatPreview_LinkText),
|
||||
text: messageText
|
||||
)
|
||||
if state.selectedTabIndex == 0 {
|
||||
entries.append(.colorMessage(
|
||||
wallpaper: presentationData.chatWallpaper,
|
||||
fontSize: presentationData.chatFontSize,
|
||||
bubbleCorners: presentationData.chatBubbleCorners,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
items: [messageItem]
|
||||
))
|
||||
} else {
|
||||
var updatedPeer = peer
|
||||
switch updatedPeer {
|
||||
case let .user(user):
|
||||
updatedPeer = .user(user.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId).withUpdatedProfileColor(profileColor).withUpdatedProfileBackgroundEmojiId(selectedProfileEmojiId))
|
||||
case let .channel(channel):
|
||||
updatedPeer = .channel(channel.withUpdatedNameColor(nameColor).withUpdatedBackgroundEmojiId(backgroundEmojiId).withUpdatedProfileColor(profileColor).withUpdatedProfileBackgroundEmojiId(selectedProfileEmojiId))
|
||||
default:
|
||||
break
|
||||
}
|
||||
var files: [Int64: TelegramMediaFile] = [:]
|
||||
if let fileId = updatedPeer.profileBackgroundEmojiId, let file = state.files[fileId] {
|
||||
files[fileId] = file
|
||||
}
|
||||
entries.append(.colorProfile(
|
||||
peer: updatedPeer,
|
||||
files: files,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder
|
||||
))
|
||||
}
|
||||
if state.selectedTabIndex == 0 {
|
||||
entries.append(.colorPicker(
|
||||
colors: nameColors,
|
||||
currentColor: nameColor,
|
||||
isProfile: false
|
||||
))
|
||||
} else {
|
||||
entries.append(.colorPicker(
|
||||
colors: nameColors,
|
||||
currentColor: profileColor,
|
||||
isProfile: true
|
||||
))
|
||||
}
|
||||
if state.selectedTabIndex == 1 && profileColor != nil {
|
||||
entries.append(.removeColor)
|
||||
}
|
||||
|
||||
if state.selectedTabIndex == 0 {
|
||||
if case .channel = peer {
|
||||
entries.append(.colorDescription(presentationData.strings.NameColor_ChatPreview_Description_Channel))
|
||||
} else {
|
||||
entries.append(.colorDescription(presentationData.strings.NameColor_ChatPreview_Description_Account))
|
||||
}
|
||||
|
||||
if let emojiContent {
|
||||
var selectedItems = Set<MediaId>()
|
||||
if let backgroundEmojiId {
|
||||
selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId))
|
||||
}
|
||||
let emojiContent = emojiContent.withSelectedItems(selectedItems).withCustomTintColor(colors.main)
|
||||
|
||||
entries.append(.backgroundEmojiHeader(presentationData.strings.NameColor_BackgroundEmoji_Title, (backgroundEmojiId != nil && backgroundEmojiId != 0) ? presentationData.strings.NameColor_BackgroundEmoji_Remove : nil))
|
||||
entries.append(.backgroundEmoji(emojiContent, colors.main, false, false))
|
||||
}
|
||||
} else {
|
||||
if let emojiContent {
|
||||
var selectedItems = Set<MediaId>()
|
||||
if let selectedProfileEmojiId {
|
||||
selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: selectedProfileEmojiId))
|
||||
}
|
||||
let emojiContent = emojiContent.withSelectedItems(selectedItems).withCustomTintColor(profileColors?.main ?? presentationData.theme.list.itemSecondaryTextColor)
|
||||
|
||||
entries.append(.backgroundEmojiHeader(presentationData.strings.ProfileColorSetup_IconSectionTitle, (selectedProfileEmojiId != nil && selectedProfileEmojiId != 0) ? presentationData.strings.NameColor_BackgroundEmoji_Remove : nil))
|
||||
entries.append(.backgroundEmoji(emojiContent, profileColors?.main ?? presentationData.theme.list.itemSecondaryTextColor, true, profileColor != nil))
|
||||
} else {
|
||||
if case .channel = peer {
|
||||
entries.append(.colorDescription(presentationData.strings.ProfileColorSetup_ChannelColorInfoLabel))
|
||||
} else {
|
||||
entries.append(.colorDescription(presentationData.strings.ProfileColorSetup_AccountColorInfoLabel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public enum PeerNameColorScreenSubject {
|
||||
case account
|
||||
case channel(EnginePeer.Id)
|
||||
}
|
||||
|
||||
public func PeerNameColorScreen(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
subject: PeerNameColorScreenSubject
|
||||
) -> ViewController {
|
||||
let statePromise = ValuePromise(PeerNameColorScreenState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: PeerNameColorScreenState())
|
||||
let updateState: ((PeerNameColorScreenState) -> PeerNameColorScreenState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
|
||||
var presentImpl: ((ViewController) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var attemptNavigationImpl: ((@escaping () -> Void) -> Bool)?
|
||||
var applyChangesImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let arguments = PeerNameColorScreenArguments(
|
||||
context: context,
|
||||
updateNameColor: { color in
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
|
||||
if state.selectedTabIndex == 0 {
|
||||
if let color {
|
||||
updatedState.updatedNameColor = color
|
||||
}
|
||||
} else {
|
||||
updatedState.updatedProfileColor = color
|
||||
updatedState.hasUpdatedProfileColor = true
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
},
|
||||
updateBackgroundEmojiId: { emojiId, file in
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
if state.selectedTabIndex == 0 {
|
||||
updatedState.updatedBackgroundEmojiId = emojiId
|
||||
} else {
|
||||
updatedState.hasUpdatedProfileBackgroundEmojiId = true
|
||||
updatedState.updatedProfileBackgroundEmojiId = emojiId
|
||||
}
|
||||
if let file {
|
||||
updatedState.files[file.fileId.id] = file
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
},
|
||||
resetColor: {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
|
||||
if state.selectedTabIndex == 1 {
|
||||
updatedState.updatedProfileColor = nil
|
||||
updatedState.hasUpdatedProfileColor = true
|
||||
updatedState.updatedProfileBackgroundEmojiId = nil
|
||||
updatedState.hasUpdatedProfileBackgroundEmojiId = true
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let peerId: EnginePeer.Id
|
||||
switch subject {
|
||||
case .account:
|
||||
peerId = context.account.peerId
|
||||
case let .channel(channelId):
|
||||
peerId = channelId
|
||||
}
|
||||
|
||||
let emojiContent = EmojiPagerContentComponent.emojiInputData(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
isStandalone: false,
|
||||
subject: .backgroundIcon,
|
||||
hasTrending: false,
|
||||
topReactionItems: [],
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: context.account.peerId,
|
||||
selectedItems: Set(),
|
||||
backgroundIconColor: nil
|
||||
)
|
||||
/*let emojiContent: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
|
||||
context.sharedContext.presentationData
|
||||
)
|
||||
|> mapToSignal { presentationData, state, peer -> Signal<(EmojiPagerContentComponent, EmojiPagerContentComponent), NoError> in
|
||||
var selectedEmojiId: Int64?
|
||||
if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId {
|
||||
selectedEmojiId = updatedBackgroundEmojiId
|
||||
} else {
|
||||
selectedEmojiId = peer?.backgroundEmojiId
|
||||
}
|
||||
let nameColor: PeerNameColor
|
||||
if let updatedNameColor = state.updatedNameColor {
|
||||
nameColor = updatedNameColor
|
||||
} else {
|
||||
nameColor = (peer?.nameColor ?? .blue)
|
||||
}
|
||||
|
||||
var selectedProfileEmojiId: Int64?
|
||||
if state.hasUpdatedProfileBackgroundEmojiId {
|
||||
selectedProfileEmojiId = state.updatedProfileBackgroundEmojiId
|
||||
} else {
|
||||
selectedProfileEmojiId = peer?.profileBackgroundEmojiId
|
||||
}
|
||||
let profileColor: PeerNameColor?
|
||||
if state.hasUpdatedProfileColor {
|
||||
profileColor = state.updatedProfileColor
|
||||
} else {
|
||||
profileColor = peer?.profileColor
|
||||
}
|
||||
|
||||
let color = context.peerNameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance)
|
||||
let profileColorValue: UIColor? = profileColor.flatMap { profileColor in context.peerNameColors.getProfile(profileColor, dark: presentationData.theme.overallDarkAppearance).main }
|
||||
|
||||
let selectedItems: [EngineMedia.Id]
|
||||
if let selectedEmojiId, selectedEmojiId != 0 {
|
||||
selectedItems = [EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: selectedEmojiId)]
|
||||
} else {
|
||||
selectedItems = []
|
||||
}
|
||||
|
||||
let selectedProfileItems: [EngineMedia.Id]
|
||||
if let selectedProfileEmojiId, selectedProfileEmojiId != 0 {
|
||||
selectedProfileItems = [EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: selectedProfileEmojiId)]
|
||||
} else {
|
||||
selectedProfileItems = []
|
||||
}
|
||||
}*/
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
presentationData,
|
||||
statePromise.get(),
|
||||
context.engine.stickers.availableReactions(),
|
||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
|
||||
emojiContent
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, availableReactions, peer, emojiContent -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let isPremium = peer?.isPremium ?? false
|
||||
let buttonTitle: String
|
||||
let isLocked: Bool
|
||||
switch subject {
|
||||
case .account:
|
||||
isLocked = !isPremium
|
||||
case .channel:
|
||||
isLocked = false
|
||||
}
|
||||
let _ = isLocked
|
||||
|
||||
let backgroundEmojiId: Int64
|
||||
if let updatedBackgroundEmojiId = state.updatedBackgroundEmojiId {
|
||||
backgroundEmojiId = updatedBackgroundEmojiId
|
||||
} else if let emojiId = peer?.backgroundEmojiId {
|
||||
backgroundEmojiId = emojiId
|
||||
} else {
|
||||
backgroundEmojiId = 0
|
||||
}
|
||||
if backgroundEmojiId != 0 {
|
||||
buttonTitle = presentationData.strings.NameColor_ApplyColorAndBackgroundEmoji
|
||||
} else {
|
||||
buttonTitle = presentationData.strings.NameColor_ApplyColor
|
||||
}
|
||||
let _ = buttonTitle
|
||||
|
||||
/*let footerItem = ApplyColorFooterItem(
|
||||
theme: presentationData.theme,
|
||||
title: buttonTitle,
|
||||
locked: isLocked,
|
||||
inProgress: state.inProgress,
|
||||
action: {
|
||||
if !isLocked {
|
||||
applyChangesImpl?()
|
||||
} else {
|
||||
HapticFeedback().impact(.light)
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .premiumPaywall(
|
||||
title: nil,
|
||||
text: presentationData.strings.NameColor_TooltipPremium_Account,
|
||||
customUndoText: nil,
|
||||
timeout: nil,
|
||||
linkAction: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { action in
|
||||
if case .info = action {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .nameColor, forceDark: false, dismissed: nil)
|
||||
pushImpl?(controller)
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
presentImpl?(controller)
|
||||
}
|
||||
}
|
||||
)*/
|
||||
|
||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { _, item, _, _, _, _ in
|
||||
var selectedFileId: Int64?
|
||||
var selectedFile: TelegramMediaFile?
|
||||
if let fileId = item.itemFile?.fileId.id {
|
||||
selectedFileId = fileId
|
||||
selectedFile = item.itemFile
|
||||
} else {
|
||||
selectedFileId = 0
|
||||
}
|
||||
arguments.updateBackgroundEmojiId(selectedFileId, selectedFile)
|
||||
},
|
||||
deleteBackwards: {
|
||||
},
|
||||
openStickerSettings: {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
openSearch: {
|
||||
},
|
||||
addGroupAction: { groupId, isPremiumLocked, _ in
|
||||
guard let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
}
|
||||
|
||||
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
|
||||
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { views in
|
||||
guard let view = views.views[viewKey] as? OrderedItemListView else {
|
||||
return
|
||||
}
|
||||
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
||||
if featuredEmojiPack.info.id == collectionId {
|
||||
let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
clearGroup: { _ in
|
||||
},
|
||||
editAction: { _ in
|
||||
},
|
||||
pushController: { c in
|
||||
},
|
||||
presentController: { c in
|
||||
},
|
||||
presentGlobalOverlayController: { c in
|
||||
},
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
requestUpdate: { _ in
|
||||
},
|
||||
updateSearchQuery: { _ in
|
||||
},
|
||||
updateScrollingToItemGroup: {
|
||||
},
|
||||
onScroll: {},
|
||||
chatPeerId: nil,
|
||||
peekBehavior: nil,
|
||||
customLayout: nil,
|
||||
externalBackground: nil,
|
||||
externalExpansionView: nil,
|
||||
customContentView: nil,
|
||||
useOpaqueTheme: true,
|
||||
hideBackground: false,
|
||||
stateContext: nil,
|
||||
addImage: nil
|
||||
)
|
||||
|
||||
let entries = peerNameColorScreenEntries(
|
||||
nameColors: context.peerNameColors,
|
||||
presentationData: presentationData,
|
||||
state: state,
|
||||
peer: peer,
|
||||
isPremium: isPremium,
|
||||
emojiContent: emojiContent
|
||||
)
|
||||
|
||||
let title: ItemListControllerTitle = .sectionControl([presentationData.strings.ProfileColorSetup_TitleName, presentationData.strings.ProfileColorSetup_TitleProfile], state.selectedTabIndex)
|
||||
|
||||
let controllerState = ItemListControllerState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
title: title,
|
||||
leftNavigationButton: nil,
|
||||
rightNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
||||
if !isLocked {
|
||||
applyChangesImpl?()
|
||||
} else {
|
||||
HapticFeedback().impact(.light)
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .premiumPaywall(
|
||||
title: nil,
|
||||
text: presentationData.strings.NameColor_TooltipPremium_Account,
|
||||
customUndoText: nil,
|
||||
timeout: nil,
|
||||
linkAction: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { action in
|
||||
if case .info = action {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .colors, forceDark: false, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
|
||||
replaceImpl?(controller)
|
||||
}, dismissed: nil)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
pushImpl?(controller)
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
presentImpl?(controller)
|
||||
}
|
||||
}),
|
||||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||
animateChanges: false
|
||||
)
|
||||
let listState = ItemListNodeState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
entries: entries,
|
||||
style: .blocks,
|
||||
footerItem: nil,
|
||||
animateChanges: false
|
||||
)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
controller.titleControlValueChanged = { value in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.selectedTabIndex = value
|
||||
return state
|
||||
}
|
||||
}
|
||||
presentImpl = { [weak controller] c in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
if c is UndoOverlayController {
|
||||
controller.present(c, in: .current)
|
||||
} else {
|
||||
controller.present(c, in: .window(.root))
|
||||
}
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
controller.attemptNavigation = { f in
|
||||
return attemptNavigationImpl?(f) ?? true
|
||||
}
|
||||
attemptNavigationImpl = { f in
|
||||
if case .account = subject, !context.isPremium {
|
||||
return true
|
||||
}
|
||||
let state = stateValue.with({ $0 })
|
||||
if case .channel = subject, state.needsBoosts {
|
||||
return true
|
||||
}
|
||||
var hasChanges = false
|
||||
if state.updatedNameColor != nil || state.updatedBackgroundEmojiId != nil {
|
||||
hasChanges = true
|
||||
}
|
||||
if hasChanges {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.NameColor_UnsavedChanges_Title, text: presentationData.strings.NameColor_UnsavedChanges_Text, actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.NameColor_UnsavedChanges_Discard, action: {
|
||||
f()
|
||||
dismissImpl?()
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.NameColor_UnsavedChanges_Apply, action: {
|
||||
applyChangesImpl?()
|
||||
})
|
||||
]))
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
applyChangesImpl = { [weak controller] in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
let state = stateValue.with { $0 }
|
||||
if state.updatedNameColor == nil && state.updatedBackgroundEmojiId == nil && !state.hasUpdatedProfileColor && !state.hasUpdatedProfileBackgroundEmojiId {
|
||||
dismissImpl?()
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let nameColor = state.updatedNameColor ?? peer.nameColor
|
||||
let backgroundEmojiId = state.updatedBackgroundEmojiId ?? peer.backgroundEmojiId
|
||||
let colors = context.peerNameColors.get(nameColor ?? .blue, dark: presentationData.theme.overallDarkAppearance)
|
||||
|
||||
let profileColor = state.hasUpdatedProfileColor ? state.updatedProfileColor : peer.profileColor
|
||||
let profileBackgroundEmojiId = state.hasUpdatedProfileBackgroundEmojiId ? state.updatedProfileBackgroundEmojiId : peer.profileBackgroundEmojiId
|
||||
|
||||
switch subject {
|
||||
case .account:
|
||||
let _ = context.engine.accountData.updateNameColorAndEmoji(nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId ?? 0).startStandalone()
|
||||
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
Queue.mainQueue().after(0.25) {
|
||||
if let lastController = navigationController.viewControllers.last as? ViewController {
|
||||
var colorList: [PeerNameColors.Colors] = []
|
||||
if let nameColor {
|
||||
colorList.append(context.peerNameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance))
|
||||
}
|
||||
if let profileColor {
|
||||
colorList.append(context.peerNameColors.getProfile(profileColor, dark: presentationData.theme.overallDarkAppearance, subject: .palette))
|
||||
}
|
||||
|
||||
let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colorList)
|
||||
|
||||
let tipController = UndoOverlayController(presentationData: presentationData, content: .image(image: colorImage, title: nil, text: presentationData.strings.ProfileColorSetup_ToastAccountColorUpdated, round: false, undoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false })
|
||||
lastController.present(tipController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dismissImpl?()
|
||||
case let .channel(peerId):
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.inProgress = true
|
||||
return updatedState
|
||||
}
|
||||
let _ = (context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId ?? 0)
|
||||
|> deliverOnMainQueue).startStandalone(next: {
|
||||
}, error: { error in
|
||||
if case .channelBoostRequired = error {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.needsBoosts = true
|
||||
return updatedState
|
||||
}
|
||||
|
||||
let _ = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
|
||||
context.engine.peers.getChannelBoostStatus(peerId: peerId)
|
||||
).startStandalone(next: { peer, status in
|
||||
guard let peer, let status else {
|
||||
return
|
||||
}
|
||||
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.inProgress = false
|
||||
return updatedState
|
||||
}
|
||||
|
||||
let link = status.url
|
||||
let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors(colors: .blue), 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(title: nil, 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)
|
||||
|
||||
HapticFeedback().impact(.light)
|
||||
})
|
||||
} else {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.inProgress = false
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
}, completed: {
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
Queue.mainQueue().after(0.25) {
|
||||
if let lastController = navigationController.viewControllers.last as? ViewController {
|
||||
let tipController = UndoOverlayController(presentationData: presentationData, content: .image(image: generatePeerNameColorImage(nameColor: colors, isDark: presentationData.theme.overallDarkAppearance, bounds: CGSize(width: 32.0, height: 32.0), size: CGSize(width: 22.0, height: 22.0))!, title: nil, text: presentationData.strings.NameColor_ChannelColorUpdated, round: false, undoText: nil), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false })
|
||||
lastController.present(tipController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dismissImpl?()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return controller
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "diamond.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelCollectibleIcon.imageset/diamond.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelCollectibleIcon.imageset/diamond.pdf
vendored
Normal file
Binary file not shown.
@ -4339,7 +4339,22 @@ extension ChatControllerImpl {
|
||||
let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone()
|
||||
}
|
||||
} else {
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(context: self.context, peerId: peerId, premiumOptions: [], hasBirthday: false, completion: nil)
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(context: self.context, peerId: peerId, premiumOptions: [], hasBirthday: false, completion: { [weak self] in
|
||||
guard let self, let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
if let controller = self.context.sharedContext.makePeerInfoController(
|
||||
context: self.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer,
|
||||
mode: .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
self.push(controller)
|
||||
}
|
||||
})
|
||||
self.push(controller)
|
||||
}
|
||||
}, openPremiumRequiredForMessaging: { [weak self] in
|
||||
|
@ -4577,7 +4577,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
let premiumOptions = giftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: false, completion: nil)
|
||||
|
||||
var hasBirthday = false
|
||||
if let cachedUserData = self.peerView?.cachedData as? CachedUserData {
|
||||
hasBirthday = hasBirthdayToday(cachedData: cachedUserData)
|
||||
}
|
||||
let controller = self.context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: hasBirthday, completion: nil)
|
||||
self.push(controller)
|
||||
})
|
||||
}, requestMessageUpdate: { [weak self] id, scroll in
|
||||
|
@ -226,7 +226,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
sections.append((presentationData.strings.Premium_Gift_ContactSelection_BirthdayTomorrow, tomorrowPeers, hasActions))
|
||||
}
|
||||
|
||||
displayTopPeers = .custom(showSelf: false, sections: sections)
|
||||
displayTopPeers = .custom(showSelf: false, selfSubtitle: nil, sections: sections)
|
||||
} else {
|
||||
displayTopPeers = .recent
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
var excludeSelf = true
|
||||
|
||||
let displayTopPeers: ContactListPresentation.TopPeers
|
||||
if case let .starsGifting(birthdays, hasActions, showSelf) = mode {
|
||||
if case let .starsGifting(birthdays, hasActions, showSelf, selfSubtitle) = mode {
|
||||
if showSelf {
|
||||
excludeSelf = false
|
||||
}
|
||||
@ -98,7 +98,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
sections.append((presentationData.strings.Premium_Gift_ContactSelection_BirthdayTomorrow, tomorrowPeers, hasActions))
|
||||
}
|
||||
|
||||
displayTopPeers = .custom(showSelf: showSelf, sections: sections)
|
||||
displayTopPeers = .custom(showSelf: showSelf, selfSubtitle: selfSubtitle, sections: sections)
|
||||
} else {
|
||||
displayTopPeers = .recent
|
||||
}
|
||||
|
@ -1220,7 +1220,14 @@ func openResolvedUrlImpl(
|
||||
updateExternalController(nil)
|
||||
}
|
||||
}
|
||||
let controller = context.sharedContext.makeGiftViewScreen(context: context, gift: gift, shareStory: nil, dismissed: {
|
||||
let controller = context.sharedContext.makeGiftViewScreen(context: context, gift: gift, shareStory: { [weak navigationController] uniqueGift in
|
||||
Queue.mainQueue().after(0.15) {
|
||||
if let lastController = navigationController?.viewControllers.last as? ViewController {
|
||||
let controller = context.sharedContext.makeStorySharingScreen(context: context, subject: .gift(gift), parentController: lastController)
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
}, dismissed: {
|
||||
dismissedImpl?()
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
|
@ -2352,7 +2352,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var presentBirthdayPickerImpl: (() -> Void)?
|
||||
let starsMode: ContactSelectionControllerMode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: false)
|
||||
let starsMode: ContactSelectionControllerMode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: false, selfSubtitle: nil)
|
||||
|
||||
let contactOptions: Signal<[ContactListAdditionalOption], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId))
|
||||
|> map { birthday in
|
||||
@ -2428,22 +2428,26 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
var currentBirthdays: [EnginePeer.Id: TelegramBirthday]?
|
||||
|
||||
if case let .starGiftTransfer(birthdays, _, _, _, _, showSelf) = source {
|
||||
mode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: showSelf)
|
||||
mode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: showSelf, selfSubtitle: presentationData.strings.Premium_Gift_ContactSelection_TransferSelf)
|
||||
currentBirthdays = birthdays
|
||||
} else if case let .chatList(birthdays) = source {
|
||||
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true)
|
||||
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true, selfSubtitle: presentationData.strings.Premium_Gift_ContactSelection_BuySelf)
|
||||
currentBirthdays = birthdays
|
||||
} else if case let .settings(birthdays) = source {
|
||||
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true)
|
||||
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true, selfSubtitle: presentationData.strings.Premium_Gift_ContactSelection_BuySelf)
|
||||
currentBirthdays = birthdays
|
||||
} else {
|
||||
mode = .starsGifting(birthdays: nil, hasActions: true, showSelf: false)
|
||||
mode = .starsGifting(birthdays: nil, hasActions: true, showSelf: false, selfSubtitle: nil)
|
||||
}
|
||||
|
||||
var allowChannelsInSearch = false
|
||||
var isChannelGift = false
|
||||
let contactOptions: Signal<[ContactListAdditionalOption], NoError>
|
||||
if case let .starGiftTransfer(_, _, _, _, canExportDate, _) = source {
|
||||
if case let .starGiftTransfer(_, reference, _, _, canExportDate, _) = source {
|
||||
allowChannelsInSearch = true
|
||||
if case let .peer(peerId, _) = reference, peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
isChannelGift = true
|
||||
}
|
||||
var subtitle: String?
|
||||
if let canExportDate {
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
@ -2605,9 +2609,16 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
case .requestPassword:
|
||||
let alertController = confirmGiftWithdrawalController(context: context, reference: reference, present: { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}, completion: { url in
|
||||
}, completion: { [weak controller] url in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
||||
|
||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is ContactSelectionController) }
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
})
|
||||
controller?.present(alertController, in: .window(.root))
|
||||
default:
|
||||
@ -2646,20 +2657,23 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is ContactSelectionController) }
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation {
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
|
||||
if !isChannelGift {
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation {
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
controllers.append(chatController)
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
controllers.append(chatController)
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
let tooltipController = UndoOverlayController(
|
||||
|
Loading…
x
Reference in New Issue
Block a user