mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '0448473ab6'
This commit is contained in:
commit
37ffcf76e9
26 changed files with 590 additions and 254 deletions
|
|
@ -15676,9 +15676,9 @@ Error: %8$@";
|
|||
|
||||
"Gift.Craft.Title" = "Craft Gift";
|
||||
"Gift.Craft.Description" = "Add up to **4 gifts** to craft new\n**$ %@**.\n\nIf crafting fails, all used gifts\nwill be lost.";
|
||||
"Gift.Craft.ViewVariants" = "View all new variants";
|
||||
"Gift.Craft.BackdropTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** backdrop.";
|
||||
"Gift.Craft.SymbolTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** symbol.";
|
||||
"Gift.Craft.ViewVariants" = "View all craftable models";
|
||||
"Gift.Craft.BackdropTooltip" = "**%1$@%** chance the crafted gift will have **%2$@** backdrop.";
|
||||
"Gift.Craft.SymbolTooltip" = "**%1$@%** chance the crafted gift will have **%2$@** symbol.";
|
||||
"Gift.Craft.SuccessChanceSuffix" = "% Success Chance";
|
||||
"Gift.Craft.Craft" = "Craft %@";
|
||||
"Gift.Craft.Crafting.Title" = "Crafting";
|
||||
|
|
@ -15722,5 +15722,35 @@ Error: %8$@";
|
|||
|
||||
"Gift.View.OnSale" = "On sale for %@";
|
||||
|
||||
"Gift.Store.Balance.MyStars" = "My Stars";
|
||||
"Gift.Store.Balance.MyTon" = "My TON";
|
||||
|
||||
"Resolve.GiftErrorNotFound" = "Sorry, this gift doesn't seem to exist.";
|
||||
"Resolve.GiftErrorBurned" = "Sorry, this gift has already been burned.";
|
||||
|
||||
"Notification.StarGift.Burned" = "burned";
|
||||
|
||||
"AuthConfirmation.Title" = "Log in to **%@**";
|
||||
"AuthConfirmation.Description" = "This site will receive your **name**,\n**username** and **profile photo**.";
|
||||
"AuthConfirmation.Device" = "Device";
|
||||
"AuthConfirmation.IpAddress" ="IP Address";
|
||||
"AuthConfirmation.Info" ="This login attempt came from the device above.";
|
||||
"AuthConfirmation.AllowMessages" = "Allow Messages";
|
||||
"AuthConfirmation.AllowMessagesInfo" = "This will allow %@ to message you.";
|
||||
"AuthConfirmation.Cancel" = "Cancel";
|
||||
"AuthConfirmation.LogIn" = "Log In";
|
||||
|
||||
"AuthConfirmation.PhoneNumberConfirmation.Title" = "Phone Number";
|
||||
"AuthConfirmation.PhoneNumberConfirmation.Text" = "**%1$@** wants to access your phone number **%2$@**.\n\nAllow access?";
|
||||
"AuthConfirmation.PhoneNumberConfirmation.Deny" = "Deny";
|
||||
"AuthConfirmation.PhoneNumberConfirmation.Allow" = "Allow";
|
||||
|
||||
"AuthConfirmation.LoginSuccess.Title" = "Login Successful";
|
||||
"AuthConfirmation.LoginSuccess.Text" = "You are now logged in to [%@]().";
|
||||
"AuthConfirmation.LoginSuccess.TextNoNumber" = "You are logged in to [%@](), but you didn't grant access to your phone number.";
|
||||
|
||||
"AuthConfirmation.LoginFail.Title" = "Login Failed";
|
||||
"AuthConfirmation.LoginFail.Text" = "Please try logging in to [%@]() again.";
|
||||
|
||||
"Notification.GroupCreatorChangePending" = "%2$@ will become the new main admin in 7 days if %1$@ does not return.";
|
||||
"Notification.GroupCreatorChangeApplied" = "%1$@ has transferred ownership of the group to %2$@.";
|
||||
|
|
|
|||
|
|
@ -316,7 +316,6 @@ public enum ResolvedUrl {
|
|||
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
|
||||
case premiumGiftCode(slug: String)
|
||||
case premiumMultiGift(reference: String?)
|
||||
case collectible(gift: StarGift.UniqueGift?)
|
||||
case auction(auction: GiftAuctionContext?)
|
||||
case messageLink(link: TelegramResolvedMessageLink?)
|
||||
case stars
|
||||
|
|
@ -328,6 +327,13 @@ public enum ResolvedUrl {
|
|||
case unknownDeepLink(path: String)
|
||||
case oauth(url: String)
|
||||
|
||||
public enum ResolvedCollectible {
|
||||
case gift(StarGift.UniqueGift)
|
||||
case invalidSlug
|
||||
case alreadyBurned
|
||||
}
|
||||
case collectible(ResolvedCollectible)
|
||||
|
||||
public enum ChatsSection {
|
||||
case search
|
||||
case edit
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
|
|||
case .unmutedHeader:
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnmutedChatsHeader, sectionId: self.section)
|
||||
case let .unmutedValue(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateUnmuted(value)
|
||||
})
|
||||
case .unmutedFooter:
|
||||
|
|
@ -97,7 +97,7 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
|
|||
case .foldersHeader:
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_FolderChatsHeader, sectionId: self.section)
|
||||
case let .foldersValue(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateFolders(value)
|
||||
})
|
||||
case .foldersFooter:
|
||||
|
|
@ -105,7 +105,7 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
|
|||
case .unknownHeader:
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnknownChatsHeader, sectionId: self.section)
|
||||
case let .unknownValue(isOn, isLocked):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_AutomaticallyArchive, value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ArchiveSettings_AutomaticallyArchive, value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateUnknown(value)
|
||||
}, activatedWhileDisabled: {
|
||||
arguments.updateUnknown(nil)
|
||||
|
|
|
|||
|
|
@ -302,7 +302,6 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case starGiftPurchaseOffer(gift: StarGift, amount: CurrencyAmount, expireDate: Int32, isAccepted: Bool, isDeclined: Bool)
|
||||
case starGiftPurchaseOfferDeclined(gift: StarGift, amount: CurrencyAmount, hasExpired: Bool)
|
||||
case groupCreatorChange(GroupCreatorChange)
|
||||
case starGiftCraftFail
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
|
||||
|
|
@ -474,8 +473,6 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
self = .starGiftPurchaseOffer(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount") ?? CurrencyAmount(amount: .zero, currency: .stars), expireDate: decoder.decodeInt32ForKey("expireDate", orElse: 0), isAccepted: decoder.decodeBoolForKey("isAccepted", orElse: false), isDeclined: decoder.decodeBoolForKey("isDeclined", orElse: false))
|
||||
case 57:
|
||||
self = .starGiftPurchaseOfferDeclined(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount")!, hasExpired: decoder.decodeBoolForKey("hasExpired", orElse: false))
|
||||
case 58:
|
||||
self = .starGiftCraftFail
|
||||
case 59:
|
||||
self = .groupCreatorChange(decoder.decodeCodable(GroupCreatorChange.self, forKey: "d") ?? GroupCreatorChange(kind: .pending, targetPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))))
|
||||
default:
|
||||
|
|
@ -969,8 +966,6 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
encoder.encodeObject(gift, forKey: "gift")
|
||||
encoder.encodeCodable(amount, forKey: "amount")
|
||||
encoder.encodeBool(hasExpired, forKey: "hasExpired")
|
||||
case .starGiftCraftFail:
|
||||
encoder.encodeInt32(58, forKey: "_rawValue")
|
||||
case let .groupCreatorChange(groupCreatorChange):
|
||||
encoder.encodeInt32(59, forKey: "_rawValue")
|
||||
encoder.encodeCodable(groupCreatorChange, forKey: "d")
|
||||
|
|
|
|||
|
|
@ -199,6 +199,11 @@ public enum MessageActionUrlAuthResult {
|
|||
case request(domain: String, bot: Peer, clientData: ClientData?, flags: Flags)
|
||||
}
|
||||
|
||||
public enum MessageActionUrlAuthError {
|
||||
case generic
|
||||
case urlExpired
|
||||
}
|
||||
|
||||
public enum MessageActionUrlSubject {
|
||||
case message(id: MessageId, buttonId: Int32)
|
||||
case url(String)
|
||||
|
|
@ -259,7 +264,7 @@ func _internal_requestMessageActionUrlAuth(account: Account, subject: MessageAct
|
|||
}
|
||||
}
|
||||
|
||||
func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError> {
|
||||
var flags: Int32 = 0
|
||||
if allowWriteAccess {
|
||||
flags |= Int32(1 << 0)
|
||||
|
|
@ -292,13 +297,10 @@ func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActi
|
|||
|
||||
|
||||
return request
|
||||
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
|
||||
return .single(nil)
|
||||
|> mapError { _ -> MessageActionUrlAuthError in
|
||||
return .generic
|
||||
}
|
||||
|> map { result -> MessageActionUrlAuthResult in
|
||||
guard let result = result else {
|
||||
return .default
|
||||
}
|
||||
switch result {
|
||||
case let .urlAuthResultAccepted(urlAuthResultAcceptedData):
|
||||
let url = urlAuthResultAcceptedData.url
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ public extension TelegramEngine {
|
|||
return _internal_requestMessageActionUrlAuth(account: self.account, subject: subject)
|
||||
}
|
||||
|
||||
public func acceptMessageActionUrlAuth(subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
public func acceptMessageActionUrlAuth(subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError> {
|
||||
return _internal_acceptMessageActionUrlAuth(account: self.account, subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3592,33 +3592,36 @@ extension StarGift.UniqueGift.Attribute {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public enum GetUniqueStarGiftError {
|
||||
case generic
|
||||
case burned
|
||||
case invalidSlug
|
||||
case alreadyBurned
|
||||
}
|
||||
|
||||
func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarGift.UniqueGift?, GetUniqueStarGiftError> {
|
||||
func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> {
|
||||
return account.network.request(Api.functions.payments.getUniqueStarGift(slug: slug))
|
||||
|> mapError { error -> GetUniqueStarGiftError in
|
||||
if error.errorDescription == "STARGIFT_ALREADY_BURNED" {
|
||||
return .burned
|
||||
return .alreadyBurned
|
||||
} else if error.errorDescription == "STARGIFT_SLUG_INVALID" {
|
||||
return .invalidSlug
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<StarGift.UniqueGift?, GetUniqueStarGiftError> in
|
||||
|> mapToSignal { result -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> in
|
||||
switch result {
|
||||
case let .uniqueStarGift(uniqueStarGiftData):
|
||||
let (gift, chats, users) = (uniqueStarGiftData.gift, uniqueStarGiftData.chats, uniqueStarGiftData.users)
|
||||
return account.postbox.transaction { transaction in
|
||||
return account.postbox.transaction { transaction -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> in
|
||||
let parsedPeers = AccumulatedPeers(chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
|
||||
guard case let .unique(uniqueGift) = StarGift(apiStarGift: gift) else {
|
||||
return nil
|
||||
return .fail(.invalidSlug)
|
||||
}
|
||||
return uniqueGift
|
||||
return .single(uniqueGift)
|
||||
}
|
||||
|> castError(GetUniqueStarGiftError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ public extension TelegramEngine {
|
|||
return _internal_checkCanSendStarGift(account: self.account, giftId: giftId)
|
||||
}
|
||||
|
||||
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift?, GetUniqueStarGiftError> {
|
||||
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> {
|
||||
return _internal_getUniqueStarGift(account: self.account, slug: slug)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1743,9 +1743,6 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGiftOffer_Rejected(peerName, giftTitle, priceString)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
}
|
||||
case .starGiftCraftFail:
|
||||
//TODO:localize
|
||||
attributedString = NSAttributedString(string: "Gift crafting failed", font: titleFont, textColor: primaryTextColor)
|
||||
case let .groupCreatorChange(groupCreatorChange):
|
||||
var targetName = ""
|
||||
if let peer = message.peers[groupCreatorChange.targetPeerId] {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AvatarComponent",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/PhoneNumberFormat",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/AccountUtils",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import GlassBackgroundComponent
|
||||
import AvatarComponent
|
||||
import BundleIconComponent
|
||||
import AccountContext
|
||||
|
||||
final class AccountSwitchComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
let canSwitch: Bool
|
||||
let action: ((GlassContextExtractableContainer) -> Void)
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
peer: EnginePeer,
|
||||
canSwitch: Bool,
|
||||
action: @escaping ((GlassContextExtractableContainer) -> Void)
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
self.canSwitch = canSwitch
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: AccountSwitchComponent, rhs: AccountSwitchComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.canSwitch != rhs.canSwitch {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let backgroundView = GlassContextExtractableContainer()
|
||||
private let avatar = ComponentView<Empty>()
|
||||
private let arrow = ComponentView<Empty>()
|
||||
private let button = HighlightTrackingButton()
|
||||
|
||||
private var component: AccountSwitchComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
if let component = self.component {
|
||||
component.action(self.backgroundView)
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: AccountSwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let size = CGSize(width: component.canSwitch ? 76.0 : 44.0, height: 44.0)
|
||||
|
||||
let avatarSize = self.avatar.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
AvatarComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
peer: component.peer,
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 36.0, height: 36.0)
|
||||
)
|
||||
if let avatarView = self.avatar.view {
|
||||
if avatarView.superview == nil {
|
||||
avatarView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(avatarView)
|
||||
}
|
||||
avatarView.frame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: avatarSize)
|
||||
}
|
||||
|
||||
let arrowSize = self.arrow.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(name: "Navigation/Disclosure", tintColor: component.theme.rootController.navigationBar.secondaryTextColor)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let arrowView = self.arrow.view {
|
||||
if arrowView.superview == nil {
|
||||
arrowView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(arrowView)
|
||||
self.backgroundView.contentView.addSubview(self.button)
|
||||
}
|
||||
arrowView.frame = CGRect(origin: CGPoint(x: size.width - arrowSize.width - 12.0, y: floorToScreenPixels((size.height - arrowSize.height) / 2.0)), size: arrowSize)
|
||||
transition.setAlpha(view: arrowView, alpha: component.canSwitch ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: component.canSwitch, transition: transition)
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
|
||||
transition.setFrame(view: self.button, frame: CGRect(origin: .zero, size: size))
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,10 @@ import ListActionItemComponent
|
|||
import AvatarComponent
|
||||
import Markdown
|
||||
import PhoneNumberFormat
|
||||
import ContextUI
|
||||
import AccountUtils
|
||||
import GlassBackgroundComponent
|
||||
import AccountPeerContextItem
|
||||
|
||||
private final class AuthConfirmationSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -52,6 +56,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
private let subject: MessageActionUrlAuthResult
|
||||
|
||||
var peer: EnginePeer?
|
||||
var forcedAccount: (AccountContext, EnginePeer)?
|
||||
|
||||
fileprivate var inProgress = false
|
||||
var allowWrite = true
|
||||
|
|
@ -77,24 +82,24 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
guard case let .request(domain, _, _, _) = self.subject else {
|
||||
return
|
||||
}
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, case let .user(user) = peer, let phone = user.phone else {
|
||||
return
|
||||
}
|
||||
let phoneNumber = formatPhoneNumber(context: self.context, number: phone)
|
||||
let phoneNumber = formatPhoneNumber(context: self.context, number: phone).replacingOccurrences(of: " ", with: "\u{00A0}")
|
||||
|
||||
//TODO:localize
|
||||
let alertController = textAlertController(
|
||||
context: self.context,
|
||||
title: "Phone Number",
|
||||
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**.\n\nAllow access?",
|
||||
title: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Title,
|
||||
text: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Text(domain, phoneNumber).string,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: "Deny", action: {
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Deny, action: {
|
||||
commit(false)
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: "Allow", action: {
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Allow, action: {
|
||||
commit(true)
|
||||
})
|
||||
]
|
||||
|
|
@ -102,6 +107,51 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
self.controller?.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
func presentAccountSwitchMenu(sourceView: GlassContextExtractableContainer) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let items: Signal<[ContextMenuItem], NoError> = activeAccountsAndPeers(context: self.context, includePrimary: true)
|
||||
|> take(1)
|
||||
|> map { primary, other -> [ContextMenuItem] in
|
||||
var items: [ContextMenuItem] = []
|
||||
var existingIds = Set<EnginePeer.Id>()
|
||||
if let (_, peer) = primary {
|
||||
items.append(.custom(AccountPeerContextItem(context: context, account: context.account, peer: peer, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.forcedAccount = nil
|
||||
self.updated()
|
||||
}), true))
|
||||
existingIds.insert(peer.id)
|
||||
}
|
||||
|
||||
for (accountContext, peer, _) in other {
|
||||
guard !existingIds.contains(peer.id) else {
|
||||
continue
|
||||
}
|
||||
items.append(.custom(AccountPeerContextItem(context: accountContext, account: accountContext.account, peer: peer, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.forcedAccount = (accountContext, peer)
|
||||
self.updated()
|
||||
}), true))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: presentationData, source: .reference(AuthConfirmationReferenceContentSource(controller: controller, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: nil)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
|
|
@ -110,7 +160,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let peerButton = Child(AvatarComponent.self)
|
||||
let accountButton = Child(AccountSwitchComponent.self)
|
||||
let avatar = Child(AvatarComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
|
@ -162,17 +212,21 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
)
|
||||
|
||||
if let peer = state.peer {
|
||||
let peerButton = peerButton.update(
|
||||
component: AvatarComponent(
|
||||
context: component.context,
|
||||
let accountButton = accountButton.update(
|
||||
component: AccountSwitchComponent(
|
||||
context: state.forcedAccount?.0 ?? component.context,
|
||||
theme: environment.theme,
|
||||
peer: peer
|
||||
peer: state.forcedAccount?.1 ?? peer,
|
||||
canSwitch: true,
|
||||
action: { [weak state] sourceView in
|
||||
state?.presentAccountSwitchMenu(sourceView: sourceView)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(peerButton
|
||||
.position(CGPoint(x: context.availableSize.width - 16.0 - peerButton.size.width / 2.0, y: 16.0 + peerButton.size.height / 2.0))
|
||||
context.add(accountButton
|
||||
.position(CGPoint(x: context.availableSize.width - 16.0 - accountButton.size.width / 2.0, y: 16.0 + accountButton.size.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -192,11 +246,11 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
)
|
||||
contentHeight += avatar.size.height
|
||||
contentHeight += 18.0
|
||||
|
||||
|
||||
let titleFont = Font.bold(24.0)
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: "Log in to **\(domain)**", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
|
||||
text: .markdown(text: strings.AuthConfirmation_Title(domain).string, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 2
|
||||
),
|
||||
|
|
@ -213,7 +267,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
let boldTextFont = Font.semibold(15.0)
|
||||
let description = description.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: "This site will receive your **name**,\n**username** and **profile photo**.", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
|
||||
text: .markdown(text: strings.AuthConfirmation_Description, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 3,
|
||||
lineSpacing: 0.2
|
||||
|
|
@ -235,7 +289,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
style: .glass,
|
||||
title: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Device",
|
||||
string: strings.AuthConfirmation_Device,
|
||||
font: Font.regular(17.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
|
|
@ -283,7 +337,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
style: .glass,
|
||||
title: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "IP Address",
|
||||
string: strings.AuthConfirmation_IpAddress,
|
||||
font: Font.regular(17.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
|
|
@ -331,7 +385,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
header: nil,
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "This login attempt came from the device above.",
|
||||
string: strings.AuthConfirmation_Info,
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
|
|
@ -357,7 +411,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Allow Messages",
|
||||
string: strings.AuthConfirmation_AllowMessages,
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
|
|
@ -380,7 +434,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
header: nil,
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "This will allow \(EnginePeer(bot).compactDisplayTitle) to message you.",
|
||||
string: strings.AuthConfirmation_AllowMessagesInfo(EnginePeer(bot).compactDisplayTitle).string,
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
|
|
@ -412,7 +466,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Cancel", font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center))))
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.AuthConfirmation_Cancel, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center))))
|
||||
),
|
||||
action: {
|
||||
component.cancel(true)
|
||||
|
|
@ -436,7 +490,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Log In", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.AuthConfirmation_LogIn, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
),
|
||||
displaysProgress: state.inProgress,
|
||||
action: { [weak state] in
|
||||
|
|
@ -610,3 +664,19 @@ public class AuthConfirmationScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class AuthConfirmationReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceView: UIView
|
||||
|
||||
let forceDisplayBelowKeyboard = true
|
||||
|
||||
init(controller: ViewController, sourceView: UIView) {
|
||||
self.controller = controller
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -725,6 +725,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
} else {
|
||||
ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
|
||||
}
|
||||
|
||||
if uniqueGift.flags.contains(.isBurned) {
|
||||
ribbonTitle = item.presentationData.strings.Notification_StarGift_Burned
|
||||
}
|
||||
|
||||
buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View
|
||||
modelTitle = item.presentationData.strings.Notification_StarGift_Model
|
||||
backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ final class CraftTableComponent: Component {
|
|||
diameter: 84.0,
|
||||
lineWidth: 5.0,
|
||||
fontSize: 18.0,
|
||||
progress: CGFloat(permilleValue / 10 / 100),
|
||||
progress: CGFloat(permilleValue) / 10.0 / 100.0,
|
||||
value: permilleValue / 10,
|
||||
suffix: "%",
|
||||
isVisible: !component.isCrafting
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ private final class CraftGiftPageContent: Component {
|
|||
},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
craftContentHeight += 276.0
|
||||
craftContentHeight += 291.0
|
||||
craftContentHeight += descriptionTextSize.height
|
||||
craftContentHeight += 25.0
|
||||
|
||||
|
|
@ -566,7 +566,7 @@ private final class CraftGiftPageContent: Component {
|
|||
diameter: 48.0,
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
progress: CGFloat(backdropPermille / 10 / 100),
|
||||
progress: CGFloat(backdropPermille) / 10.0 / 100.0,
|
||||
value: backdropPermille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
|
|
@ -615,7 +615,7 @@ private final class CraftGiftPageContent: Component {
|
|||
contentSize: CGSize(width: 28.0, height: 28.0),
|
||||
lineWidth: 4.0,
|
||||
fontSize: 10.0,
|
||||
progress: CGFloat(symbolPermille / 10 / 100),
|
||||
progress: CGFloat(symbolPermille) / 10.0 / 100.0,
|
||||
value: symbolPermille / 10,
|
||||
suffix: "%"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1220,7 +1220,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
}
|
||||
let tonBalance = tonContext.currentState?.balance.value ?? 0
|
||||
if tonBalance == 0 {
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
|
||||
controller.push(controller)
|
||||
return
|
||||
}
|
||||
|
|
@ -1242,7 +1242,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: "My Stars",
|
||||
text: presentationData.strings.Gift_Store_Balance_MyStars,
|
||||
textLayout: .secondLineWithValue(formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)),
|
||||
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stars"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] _, f in
|
||||
|
|
@ -1256,7 +1256,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
)))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: "My TON",
|
||||
text: presentationData.strings.Gift_Store_Balance_MyTon,
|
||||
textLayout: .secondLineWithValue(formatTonAmountText(tonBalance, dateTimeFormat: presentationData.dateTimeFormat)),
|
||||
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Ton"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] _, f in
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
.single(gift.themePeerId)
|
||||
|> then(
|
||||
context.engine.payments.getUniqueStarGift(slug: gift.slug)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<StarGift.UniqueGift?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AccountPeerContextItem",
|
||||
module_name = "AccountPeerContextItem",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import PresentationDataUtils
|
||||
import ContextUI
|
||||
import AvatarNode
|
||||
import EmojiStatusComponent
|
||||
import AccountContext
|
||||
|
||||
public final class AccountPeerContextItem: ContextMenuCustomItem {
|
||||
let context: AccountContext
|
||||
let account: Account
|
||||
let peer: EnginePeer
|
||||
let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||
|
||||
public init(context: AccountContext, account: Account, peer: EnginePeer, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||
self.context = context
|
||||
self.account = account
|
||||
self.peer = peer
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return AccountPeerContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustomNode {
|
||||
private let item: AccountPeerContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let emojiStatusView: ComponentView<Empty>
|
||||
|
||||
init(presentationData: PresentationData, item: AccountPeerContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0)
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.isAccessibilityElement = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
let peerTitle = item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
self.textNode.attributedText = NSAttributedString(string: peerTitle, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
|
||||
|
||||
self.emojiStatusView = ComponentView<Empty>()
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
self.buttonNode.isAccessibilityElement = true
|
||||
self.buttonNode.accessibilityLabel = peerTitle
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 18.0
|
||||
let iconSideInset: CGFloat = 20.0
|
||||
let verticalInset: CGFloat = 11.0
|
||||
|
||||
let iconSize = CGSize(width: 28.0, height: 28.0)
|
||||
|
||||
let standardIconWidth: CGFloat = 32.0
|
||||
var rightTextInset: CGFloat = sideInset
|
||||
if !iconSize.width.isZero {
|
||||
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 12.0
|
||||
}
|
||||
|
||||
self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer)
|
||||
|
||||
if self.item.peer.emojiStatus != nil {
|
||||
rightTextInset += 32.0
|
||||
}
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
|
||||
|
||||
return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in
|
||||
let verticalOrigin = floor((size.height - textSize.height) / 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: iconSideInset + 40.0, y: verticalOrigin), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
var iconContent: EmojiStatusComponent.Content?
|
||||
if case let .user(user) = self.item.peer {
|
||||
if let emojiStatus = user.emojiStatus {
|
||||
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
|
||||
} else if user.isPremium {
|
||||
iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
} else if case let .channel(channel) = self.item.peer {
|
||||
if let emojiStatus = channel.emojiStatus {
|
||||
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
|
||||
}
|
||||
}
|
||||
if let iconContent {
|
||||
let emojiStatusSize = self.emojiStatusView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: self.item.context,
|
||||
animationCache: self.item.context.animationCache,
|
||||
animationRenderer: self.item.context.animationRenderer,
|
||||
content: iconContent,
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 24.0, height: 24.0)
|
||||
)
|
||||
if let view = self.emojiStatusView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 2.0, y: textFrame.minY + floor((textFrame.height - emojiStatusSize.height) / 2.0)), size: emojiStatusSize))
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
if let attributedText = self.textNode.attributedText {
|
||||
let updatedAttributedText = NSMutableAttributedString(attributedString: attributedText)
|
||||
updatedAttributedText.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.primaryColor.cgColor, range: NSRange(location: 0, length: updatedAttributedText.length))
|
||||
self.textNode.attributedText = updatedAttributedText
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.performAction()
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
self.setIsHighlighted(isHighlighted)
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
self.item.action(controller, { [weak self] result in
|
||||
self?.actionSelected(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -173,6 +173,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AvatarComponent",
|
||||
"//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent",
|
||||
"//submodules/TelegramUI/Components/HorizontalTabsComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ import GiftViewScreen
|
|||
import PeerMessagesMediaPlaylist
|
||||
import EdgeEffect
|
||||
import Pasteboard
|
||||
import AccountPeerContextItem
|
||||
|
||||
public enum PeerInfoAvatarEditingMode {
|
||||
case generic
|
||||
|
|
|
|||
|
|
@ -89,167 +89,6 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
}
|
||||
|
||||
final class AccountPeerContextItem: ContextMenuCustomItem {
|
||||
let context: AccountContext
|
||||
let account: Account
|
||||
let peer: EnginePeer
|
||||
let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||
|
||||
init(context: AccountContext, account: Account, peer: EnginePeer, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||
self.context = context
|
||||
self.account = account
|
||||
self.peer = peer
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return AccountPeerContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustomNode {
|
||||
private let item: AccountPeerContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let emojiStatusView: ComponentView<Empty>
|
||||
|
||||
init(presentationData: PresentationData, item: AccountPeerContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0)
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.isAccessibilityElement = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
let peerTitle = item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
self.textNode.attributedText = NSAttributedString(string: peerTitle, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
|
||||
|
||||
self.emojiStatusView = ComponentView<Empty>()
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
self.buttonNode.isAccessibilityElement = true
|
||||
self.buttonNode.accessibilityLabel = peerTitle
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 18.0
|
||||
let iconSideInset: CGFloat = 20.0
|
||||
let verticalInset: CGFloat = 11.0
|
||||
|
||||
let iconSize = CGSize(width: 28.0, height: 28.0)
|
||||
|
||||
let standardIconWidth: CGFloat = 32.0
|
||||
var rightTextInset: CGFloat = sideInset
|
||||
if !iconSize.width.isZero {
|
||||
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 12.0
|
||||
}
|
||||
|
||||
self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer)
|
||||
|
||||
if self.item.peer.emojiStatus != nil {
|
||||
rightTextInset += 32.0
|
||||
}
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
|
||||
|
||||
return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in
|
||||
let verticalOrigin = floor((size.height - textSize.height) / 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: iconSideInset + 40.0, y: verticalOrigin), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
var iconContent: EmojiStatusComponent.Content?
|
||||
if case let .user(user) = self.item.peer {
|
||||
if let emojiStatus = user.emojiStatus {
|
||||
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
|
||||
} else if user.isPremium {
|
||||
iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
} else if case let .channel(channel) = self.item.peer {
|
||||
if let emojiStatus = channel.emojiStatus {
|
||||
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
|
||||
}
|
||||
}
|
||||
if let iconContent {
|
||||
let emojiStatusSize = self.emojiStatusView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: self.item.context,
|
||||
animationCache: self.item.context.animationCache,
|
||||
animationRenderer: self.item.context.animationRenderer,
|
||||
content: iconContent,
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 24.0, height: 24.0)
|
||||
)
|
||||
if let view = self.emojiStatusView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 2.0, y: textFrame.minY + floor((textFrame.height - emojiStatusSize.height) / 2.0)), size: emojiStatusSize))
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
if let attributedText = self.textNode.attributedText {
|
||||
let updatedAttributedText = NSMutableAttributedString(attributedString: attributedText)
|
||||
updatedAttributedText.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.primaryColor.cgColor, range: NSRange(location: 0, length: updatedAttributedText.length))
|
||||
self.textNode.attributedText = updatedAttributedText
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.performAction()
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
self.setIsHighlighted(isHighlighted)
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
self.item.action(controller, { [weak self] result in
|
||||
self?.actionSelected(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerInfoControllerContextReferenceContentSource: ContextReferenceContentSource {
|
||||
let controller: ViewController
|
||||
let sourceView: UIView
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Navigation/Disclosure.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Navigation/Disclosure.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "openicon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Navigation/Disclosure.imageset/openicon.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Navigation/Disclosure.imageset/openicon.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -1632,33 +1632,36 @@ func openResolvedUrlImpl(
|
|||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
})
|
||||
case let .collectible(gift):
|
||||
if let gift {
|
||||
var dismissedImpl: (() -> Void)?
|
||||
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
|
||||
let updateExternalController = storyProgressPauseContext.update
|
||||
dismissedImpl = {
|
||||
updateExternalController(nil)
|
||||
}
|
||||
case let .collectible(result):
|
||||
switch result {
|
||||
case let .gift(gift):
|
||||
var dismissedImpl: (() -> Void)?
|
||||
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
|
||||
let updateExternalController = storyProgressPauseContext.update
|
||||
dismissedImpl = {
|
||||
updateExternalController(nil)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}, openChatTheme: nil, dismissed: {
|
||||
dismissedImpl?()
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
|
||||
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
|
||||
storyProgressPauseContext.update(controller)
|
||||
}
|
||||
} else {
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}, openChatTheme: nil, dismissed: {
|
||||
dismissedImpl?()
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
|
||||
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
|
||||
storyProgressPauseContext.update(controller)
|
||||
}
|
||||
case .alreadyBurned:
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_GiftErrorBurned, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
case .invalidSlug:
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_GiftErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
case let .auction(auctionContext):
|
||||
if let auctionContext, case let .generic(gift) = auctionContext.gift {
|
||||
if !auctionContext.isFinished, let currentBidPeerId = auctionContext.currentBidPeerId {
|
||||
|
|
@ -1818,8 +1821,27 @@ func openResolvedUrlImpl(
|
|||
if case .request = result {
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = AuthConfirmationScreen(context: context, subject: result, completion: { allowWriteAccess, sharePhoneNumber in
|
||||
let _ = context.engine.messages.acceptMessageActionUrlAuth(subject: .url(url), allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber).start(next: { _ in
|
||||
let _ = (context.engine.messages.acceptMessageActionUrlAuth(subject: .url(url), allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
dismissImpl?()
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
let text: String
|
||||
if case let .request(domain, _, _, flags) = result {
|
||||
if flags.contains(.requestPhoneNumber) && !sharePhoneNumber {
|
||||
text = presentationData.strings.AuthConfirmation_LoginSuccess_TextNoNumber(domain).string
|
||||
} else {
|
||||
text = presentationData.strings.AuthConfirmation_LoginSuccess_Text(domain).string
|
||||
}
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginSuccess_Title, text: text, cancel: nil, destructive: false), action: { _ in return true })
|
||||
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}, error: { _ in
|
||||
if case let .request(domain, _, _, _) = result {
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, cancel: nil, destructive: false), action: { _ in return true })
|
||||
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
|
|
|
|||
|
|
@ -846,7 +846,14 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||
if let peer {
|
||||
return .single(peer)
|
||||
} else {
|
||||
return context.engine.peers.findChannelById(channelId: monoforumId.id._internalGetInt64Value())
|
||||
return context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id)
|
||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||
if result {
|
||||
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: monoforumId))
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> map { peer -> ResolveInternalUrlResult in
|
||||
|
|
@ -1208,12 +1215,17 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||
return .single(.result(.premiumGiftCode(slug: slug)))
|
||||
case let .collectible(slug):
|
||||
return .single(.progress) |> then(context.engine.payments.getUniqueStarGift(slug: slug)
|
||||
|> `catch` { _ -> Signal<StarGift.UniqueGift?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { gift -> ResolveInternalUrlResult in
|
||||
return .result(.collectible(gift: gift))
|
||||
return .result(.collectible(.gift(gift)))
|
||||
})
|
||||
|> `catch` { error -> Signal<ResolveInternalUrlResult, NoError> in
|
||||
switch error {
|
||||
case .alreadyBurned:
|
||||
return .single(.result(.collectible(.alreadyBurned)))
|
||||
default:
|
||||
return .single(.result(.collectible(.invalidSlug)))
|
||||
}
|
||||
}
|
||||
case let .auction(slug):
|
||||
if let giftAuctionsManager = context.giftAuctionsManager {
|
||||
return .single(.progress) |> then(giftAuctionsManager.auctionContext(for: .slug(slug))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue