Various improvements

This commit is contained in:
Ilya Laktyushin 2025-03-31 15:24:55 +04:00
parent 7d8920db82
commit 0186f12972
8 changed files with 325 additions and 58 deletions

View File

@ -14107,3 +14107,11 @@ Sorry for the inconvenience.";
"Login.Fee.Support.Text" = "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs."; "Login.Fee.Support.Text" = "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.";
"Login.Fee.SignUp" = "Sign Up for %@"; "Login.Fee.SignUp" = "Sign Up for %@";
"Login.Fee.GetPremiumForAWeek" = "Get Telegram Premium for 1 week"; "Login.Fee.GetPremiumForAWeek" = "Get Telegram Premium for 1 week";
"StoryFeed.AddStory" = "Add Story";
"WebApp.ImportData.Title" = "Import Data";
"WebApp.ImportData.Description" = "**%@** is requesting permission to import data from a previous Telegram account used on this device.";
"WebApp.ImportData.AccountHeader" = "ACCOUNT TO IMPORT DATA FROM";
"WebApp.ImportData.CreatedOn" = "created on %@";
"WebApp.ImportData.Import" = "Import";

View File

@ -7,6 +7,9 @@ import CoreVideo
import Metal import Metal
import Display import Display
import TelegramCore import TelegramCore
import RLottieBinding
import GZip
import AppBundle
let videoMessageDimensions = PixelDimensions(width: 400, height: 400) let videoMessageDimensions = PixelDimensions(width: 400, height: 400)
@ -98,7 +101,17 @@ final class CameraRoundVideoFilter {
private var resizeFilter: CIFilter? private var resizeFilter: CIFilter?
private var overlayFilter: CIFilter? private var overlayFilter: CIFilter?
private var compositeFilter: CIFilter? private var compositeFilter: CIFilter?
private var borderFilter: CIFilter? private var maskFilter: CIFilter?
private var blurFilter: CIFilter?
private var darkenFilter: CIFilter?
private var logoImageFilter: CIFilter?
private var logoImage: CIImage?
private var animationImageFilter: CIFilter?
private var animationImage: CIImage?
private var animation: LottieInstance?
private var animationFrameIndex: Int32 = 0
private var outputColorSpace: CGColorSpace? private var outputColorSpace: CGColorSpace?
private var outputPixelBufferPool: CVPixelBufferPool? private var outputPixelBufferPool: CVPixelBufferPool?
@ -122,21 +135,45 @@ final class CameraRoundVideoFilter {
} }
self.inputFormatDescription = formatDescription self.inputFormatDescription = formatDescription
let circleImage = generateImage(videoMessageDimensions.cgSize, opaque: false, scale: 1.0, rotatedContext: { size, context in if let logoImage = UIImage(bundleImageName: "Components/RoundVideoCorner") {
self.logoImage = CIImage(image: logoImage)
}
if let path = getAppBundle().path(forResource: "PlaneLogoPlain", ofType: "tgs"), var data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
if let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) {
data = unpackedData
self.animation = LottieInstance(data: data, fitzModifier: .none, colorReplacements: [:], cacheKey: "")
}
}
let circleMaskImage = generateImage(videoMessageDimensions.cgSize, opaque: false, scale: 1.0, rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size) let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds) context.clear(bounds)
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.black.cgColor)
context.fill(bounds) context.fill(bounds)
context.setBlendMode(.clear) context.setBlendMode(.normal)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: bounds.insetBy(dx: -2.0, dy: -2.0)) context.fillEllipse(in: bounds.insetBy(dx: -2.0, dy: -2.0))
})! })!
self.resizeFilter = CIFilter(name: "CILanczosScaleTransform") self.resizeFilter = CIFilter(name: "CILanczosScaleTransform")
self.overlayFilter = CIFilter(name: "CIColorMatrix") self.overlayFilter = CIFilter(name: "CIColorMatrix")
self.compositeFilter = CIFilter(name: "CISourceOverCompositing") self.compositeFilter = CIFilter(name: "CISourceOverCompositing")
self.borderFilter = CIFilter(name: "CISourceOverCompositing") self.maskFilter = CIFilter(name: "CIBlendWithMask")
self.borderFilter?.setValue(CIImage(image: circleImage), forKey: kCIInputImageKey) self.maskFilter?.setValue(CIImage(image: circleMaskImage), forKey: kCIInputMaskImageKey)
self.blurFilter = CIFilter(name: "CIGaussianBlur")
self.blurFilter?.setValue(30.0, forKey: kCIInputRadiusKey)
self.darkenFilter = CIFilter(name: "CIColorMatrix")
let darkenVector = CIVector(x: 0.25, y: 0, z: 0, w: 0)
self.darkenFilter?.setValue(darkenVector, forKey: "inputRVector")
self.darkenFilter?.setValue(darkenVector, forKey: "inputGVector")
self.darkenFilter?.setValue(darkenVector, forKey: "inputBVector")
self.logoImageFilter = CIFilter(name: "CISourceOverCompositing")
self.animationImageFilter = CIFilter(name: "CISourceOverCompositing")
self.isPrepared = true self.isPrepared = true
} }
@ -145,7 +182,11 @@ final class CameraRoundVideoFilter {
self.resizeFilter = nil self.resizeFilter = nil
self.overlayFilter = nil self.overlayFilter = nil
self.compositeFilter = nil self.compositeFilter = nil
self.borderFilter = nil self.maskFilter = nil
self.blurFilter = nil
self.darkenFilter = nil
self.logoImageFilter = nil
self.animationImageFilter = nil
self.outputColorSpace = nil self.outputColorSpace = nil
self.outputPixelBufferPool = nil self.outputPixelBufferPool = nil
self.outputFormatDescription = nil self.outputFormatDescription = nil
@ -159,7 +200,15 @@ final class CameraRoundVideoFilter {
private var lastAdditionalSourceImage: CIImage? private var lastAdditionalSourceImage: CIImage?
func render(pixelBuffer: CVPixelBuffer, additional: Bool, captureOrientation: AVCaptureVideoOrientation, transitionFactor: CGFloat) -> CVPixelBuffer? { func render(pixelBuffer: CVPixelBuffer, additional: Bool, captureOrientation: AVCaptureVideoOrientation, transitionFactor: CGFloat) -> CVPixelBuffer? {
guard let resizeFilter = self.resizeFilter, let overlayFilter = self.overlayFilter, let compositeFilter = self.compositeFilter, let borderFilter = self.borderFilter, self.isPrepared else { guard let resizeFilter = self.resizeFilter,
let overlayFilter = self.overlayFilter,
let compositeFilter = self.compositeFilter,
let maskFilter = self.maskFilter,
let blurFilter = self.blurFilter,
let darkenFilter = self.darkenFilter,
let logoImageFilter = self.logoImageFilter,
let animationImageFilter = self.animationImageFilter,
self.isPrepared else {
return nil return nil
} }
@ -230,9 +279,62 @@ final class CameraRoundVideoFilter {
} }
} }
borderFilter.setValue(effectiveSourceImage, forKey: kCIInputBackgroundImageKey) let extendedImage = effectiveSourceImage.clampedToExtent()
blurFilter.setValue(extendedImage, forKey: kCIInputImageKey)
let blurredImage = blurFilter.outputImage ?? effectiveSourceImage
let blurredAndCropped = blurredImage.cropped(to: effectiveSourceImage.extent)
darkenFilter.setValue(blurredAndCropped, forKey: kCIInputImageKey)
let darkenedBlurredBackground = darkenFilter.outputImage ?? blurredAndCropped
maskFilter.setValue(effectiveSourceImage, forKey: kCIInputImageKey)
maskFilter.setValue(darkenedBlurredBackground, forKey: kCIInputBackgroundImageKey)
var finalImage = maskFilter.outputImage
guard let maskedImage = finalImage else {
return nil
}
if let logoImage = self.logoImage {
let overlayWidth: CGFloat = 100.0
let xPosition = maskedImage.extent.width - overlayWidth
let yPosition = 0.0
let transformedOverlay = logoImage.transformed(by: CGAffineTransform(translationX: xPosition, y: yPosition))
logoImageFilter.setValue(transformedOverlay, forKey: kCIInputImageKey)
logoImageFilter.setValue(maskedImage, forKey: kCIInputBackgroundImageKey)
finalImage = logoImageFilter.outputImage ?? maskedImage
} else {
finalImage = maskedImage
}
if let animation = self.animation, let renderContext = DrawingContext(size: CGSize(width: 68.0, height: 68.0), scale: 1.0, clear: true) {
animation.renderFrame(with: self.animationFrameIndex, into: renderContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(renderContext.size.width * renderContext.scale), height: Int32(renderContext.size.height * renderContext.scale), bytesPerRow: Int32(renderContext.bytesPerRow))
self.animationFrameIndex += 2
if self.animationFrameIndex >= animation.frameCount {
self.animationFrameIndex = 0
}
if let image = renderContext.generateImage(), let animationImage = CIImage(image: image) {
let xPosition = 0.0
let yPosition = 0.0
let transformedOverlay = animationImage.transformed(by: CGAffineTransform(translationX: xPosition, y: yPosition))
animationImageFilter.setValue(transformedOverlay, forKey: kCIInputImageKey)
animationImageFilter.setValue(finalImage, forKey: kCIInputBackgroundImageKey)
finalImage = animationImageFilter.outputImage ?? maskedImage
} else {
finalImage = maskedImage
}
} else {
finalImage = maskedImage
}
let finalImage = borderFilter.outputImage
guard let finalImage else { guard let finalImage else {
return nil return nil
} }

View File

@ -2291,7 +2291,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
dateReplies = Int(attribute.count) dateReplies = Int(attribute.count)
} }
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value var messageCount: Int = 1
if case let .group(messages) = item.content {
messageCount = messages.count
}
starsCount = attribute.stars.value * Int64(messageCount)
} }
} }

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "vlog.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -53,6 +53,7 @@ swift_library(
"//submodules/DeviceLocationManager", "//submodules/DeviceLocationManager",
"//submodules/DeviceAccess", "//submodules/DeviceAccess",
"//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage", "//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage",
"//submodules/AvatarNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -2992,39 +2992,46 @@ public final class WebAppController: ViewController, AttachmentContainable {
return return
} }
let transferController = WebAppSecureStorageTransferScreen( let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId))
context: self.context, |> deliverOnMainQueue).start(next: { [weak self] botPeer in
existingKeys: storedKeys, guard let self, let botPeer, let controller = self.controller else {
completion: { [weak self] uuid in return
guard let self else { }
return let transferController = WebAppSecureStorageTransferScreen(
} context: self.context,
guard let uuid else { peer: botPeer,
let data: JSON = [ existingKeys: storedKeys,
"req_id": requestId, completion: { [weak self] uuid in
"error": "RESTORE_CANCELLED"
]
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
return
}
let _ = (WebAppSecureStorage.transferAllValues(context: self.context, fromUuid: uuid, botId: controller.botId)
|> deliverOnMainQueue).start(completed: { [weak self] in
guard let self else { guard let self else {
return return
} }
let _ = (WebAppSecureStorage.getValue(context: self.context, botId: controller.botId, key: key) guard let uuid else {
|> deliverOnMainQueue).start(next: { [weak self] value in
let data: JSON = [ let data: JSON = [
"req_id": requestId, "req_id": requestId,
"value": value ?? NSNull() "error": "RESTORE_CANCELLED"
] ]
self?.webView?.sendEvent(name: "secure_storage_key_restored", data: data.string) self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
return
}
let _ = (WebAppSecureStorage.transferAllValues(context: self.context, fromUuid: uuid, botId: controller.botId)
|> deliverOnMainQueue).start(completed: { [weak self] in
guard let self else {
return
}
let _ = (WebAppSecureStorage.getValue(context: self.context, botId: controller.botId, key: key)
|> deliverOnMainQueue).start(next: { [weak self] value in
let data: JSON = [
"req_id": requestId,
"value": value ?? NSNull()
]
self?.webView?.sendEvent(name: "secure_storage_key_restored", data: data.string)
})
}) })
}) }
} )
) controller.parentController()?.push(transferController)
controller.parentController()?.push(transferController) })
} }
fileprivate func openLocationSettings() { fileprivate func openLocationSettings() {

View File

@ -11,28 +11,31 @@ import TelegramStringFormatting
import ViewControllerComponent import ViewControllerComponent
import SheetComponent import SheetComponent
import BundleIconComponent import BundleIconComponent
import BalancedTextComponent
import MultilineTextComponent import MultilineTextComponent
import ButtonComponent import ButtonComponent
import ListSectionComponent import ListSectionComponent
import ListActionItemComponent import ListActionItemComponent
import AccountContext import AccountContext
import AvatarNode
private final class SheetContent: CombinedComponent { private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let peer: EnginePeer
let existingKeys: [WebAppSecureStorage.ExistingKey] let existingKeys: [WebAppSecureStorage.ExistingKey]
let completion: (String) -> Void let completion: (String) -> Void
let dismiss: () -> Void let dismiss: () -> Void
init( init(
context: AccountContext, context: AccountContext,
peer: EnginePeer,
existingKeys: [WebAppSecureStorage.ExistingKey], existingKeys: [WebAppSecureStorage.ExistingKey],
completion: @escaping (String) -> Void, completion: @escaping (String) -> Void,
dismiss: @escaping () -> Void dismiss: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.peer = peer
self.existingKeys = existingKeys self.existingKeys = existingKeys
self.completion = completion self.completion = completion
self.dismiss = dismiss self.dismiss = dismiss
@ -42,6 +45,9 @@ private final class SheetContent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.peer != rhs.peer {
return false
}
if lhs.existingKeys != rhs.existingKeys { if lhs.existingKeys != rhs.existingKeys {
return false return false
} }
@ -59,8 +65,9 @@ private final class SheetContent: CombinedComponent {
static var body: Body { static var body: Body {
let closeButton = Child(Button.self) let closeButton = Child(Button.self)
let title = Child(BalancedTextComponent.self) let title = Child(MultilineTextComponent.self)
let text = Child(BalancedTextComponent.self) let avatar = Child(AvatarComponent.self)
let text = Child(MultilineTextComponent.self)
let keys = Child(ListSectionComponent.self) let keys = Child(ListSectionComponent.self)
let button = Child(ButtonComponent.self) let button = Child(ButtonComponent.self)
@ -76,11 +83,11 @@ private final class SheetContent: CombinedComponent {
let textSideInset: CGFloat = 32.0 + environment.safeInsets.left let textSideInset: CGFloat = 32.0 + environment.safeInsets.left
let titleFont = Font.semibold(17.0) let titleFont = Font.semibold(17.0)
let subtitleFont = Font.regular(12.0) let textFont = Font.regular(13.0)
let boldTextFont = Font.semibold(13.0)
let textColor = theme.actionSheet.primaryTextColor let textColor = theme.actionSheet.primaryTextColor
let secondaryTextColor = theme.actionSheet.secondaryTextColor
var contentSize = CGSize(width: context.availableSize.width, height: 10.0) var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
let closeButton = closeButton.update( let closeButton = closeButton.update(
component: Button( component: Button(
@ -98,8 +105,8 @@ private final class SheetContent: CombinedComponent {
//TODO:localize //TODO:localize
let title = title.update( let title = title.update(
component: BalancedTextComponent( component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Data Transfer Requested", font: titleFont, textColor: textColor)), text: .plain(NSAttributedString(string: strings.WebApp_ImportData_Title, font: titleFont, textColor: textColor)),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 1, maximumNumberOfLines: 1,
lineSpacing: 0.1 lineSpacing: 0.1
@ -111,12 +118,36 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
) )
contentSize.height += title.size.height contentSize.height += title.size.height
contentSize.height += 24.0
let avatar = avatar.update(
component: AvatarComponent(
context: component.context,
peer: component.peer,
size: CGSize(width: 80.0, height: 80.0)
),
availableSize: CGSize(width: 80.0, height: 80.0),
transition: .immediate
)
context.add(avatar
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + avatar.size.height / 2.0))
)
contentSize.height += avatar.size.height
contentSize.height += 22.0
let text = text.update( let text = text.update(
component: BalancedTextComponent( component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Choose account to transfer data from:", font: subtitleFont, textColor: secondaryTextColor)), text: .markdown(
text: strings.WebApp_ImportData_Description(component.peer.compactDisplayTitle).string,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: textColor),
linkAttribute: { _ in return nil }
)
),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 1, maximumNumberOfLines: 0,
lineSpacing: 0.2 lineSpacing: 0.2
), ),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
@ -126,7 +157,7 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
) )
contentSize.height += text.size.height contentSize.height += text.size.height
contentSize.height += 17.0 contentSize.height += 29.0
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
@ -146,8 +177,8 @@ private final class SheetContent: CombinedComponent {
titleComponents.append( titleComponents.append(
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "Created on \(stringForMediumCompactDate(timestamp: key.timestamp, strings: strings, dateTimeFormat: environment.dateTimeFormat))", string: strings.WebApp_ImportData_CreatedOn(stringForMediumCompactDate(timestamp: key.timestamp, strings: strings, dateTimeFormat: environment.dateTimeFormat)).string,
font: Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize * 14.0 / 17.0)), font: Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize * 15.0 / 17.0)),
textColor: environment.theme.list.itemSecondaryTextColor textColor: environment.theme.list.itemSecondaryTextColor
)), )),
maximumNumberOfLines: 1 maximumNumberOfLines: 1
@ -155,7 +186,8 @@ private final class SheetContent: CombinedComponent {
) )
items.append(AnyComponentWithIdentity(id: key.uuid, component: AnyComponent(ListActionItemComponent( items.append(AnyComponentWithIdentity(id: key.uuid, component: AnyComponent(ListActionItemComponent(
theme: theme, theme: theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 3.0)),
contentInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0),
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(isSelected: key.uuid == state.selectedUuid, isEnabled: true, toggle: nil)), leftIcon: .check(ListActionItemComponent.LeftIcon.Check(isSelected: key.uuid == state.selectedUuid, isEnabled: true, toggle: nil)),
accessory: nil, accessory: nil,
action: { [weak state] _ in action: { [weak state] _ in
@ -170,7 +202,14 @@ private final class SheetContent: CombinedComponent {
let keys = keys.update( let keys = keys.update(
component: ListSectionComponent( component: ListSectionComponent(
theme: environment.theme, theme: environment.theme,
header: nil, header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.WebApp_ImportData_AccountHeader.uppercased(),
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
footer: nil, footer: nil,
items: items items: items
), ),
@ -181,9 +220,8 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + keys.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + keys.size.height / 2.0))
) )
contentSize.height += keys.size.height contentSize.height += keys.size.height
contentSize.height += 17.0 contentSize.height += 24.0
//TODO:localize
let button = button.update( let button = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: ButtonComponent.Background(
@ -192,12 +230,13 @@ private final class SheetContent: CombinedComponent {
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
), ),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable("transfer"), id: AnyHashable("import"),
component: AnyComponent( component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "Transfer", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))) MultilineTextComponent(text: .plain(NSAttributedString(string: strings.WebApp_ImportData_Import, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))
) )
), ),
isEnabled: state.selectedUuid != nil, isEnabled: state.selectedUuid != nil,
allowActionWhenDisabled: true,
displaysProgress: false, displaysProgress: false,
action: { [weak state] in action: { [weak state] in
guard let state else { guard let state else {
@ -231,15 +270,18 @@ private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let peer: EnginePeer
let existingKeys: [WebAppSecureStorage.ExistingKey] let existingKeys: [WebAppSecureStorage.ExistingKey]
let completion: (String) -> Void let completion: (String) -> Void
init( init(
context: AccountContext, context: AccountContext,
peer: EnginePeer,
existingKeys: [WebAppSecureStorage.ExistingKey], existingKeys: [WebAppSecureStorage.ExistingKey],
completion: @escaping (String) -> Void completion: @escaping (String) -> Void
) { ) {
self.context = context self.context = context
self.peer = peer
self.existingKeys = existingKeys self.existingKeys = existingKeys
self.completion = completion self.completion = completion
} }
@ -248,6 +290,9 @@ private final class SheetContainerComponent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.peer != rhs.peer {
return false
}
if lhs.existingKeys != rhs.existingKeys { if lhs.existingKeys != rhs.existingKeys {
return false return false
} }
@ -270,6 +315,7 @@ private final class SheetContainerComponent: CombinedComponent {
component: SheetComponent<EnvironmentType>( component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent( content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context, context: context.component.context,
peer: context.component.peer,
existingKeys: context.component.existingKeys, existingKeys: context.component.existingKeys,
completion: context.component.completion, completion: context.component.completion,
dismiss: { dismiss: {
@ -340,6 +386,7 @@ private final class SheetContainerComponent: CombinedComponent {
final class WebAppSecureStorageTransferScreen: ViewControllerComponentContainer { final class WebAppSecureStorageTransferScreen: ViewControllerComponentContainer {
init( init(
context: AccountContext, context: AccountContext,
peer: EnginePeer,
existingKeys: [WebAppSecureStorage.ExistingKey], existingKeys: [WebAppSecureStorage.ExistingKey],
completion: @escaping (String?) -> Void completion: @escaping (String?) -> Void
) { ) {
@ -347,6 +394,7 @@ final class WebAppSecureStorageTransferScreen: ViewControllerComponentContainer
context: context, context: context,
component: SheetContainerComponent( component: SheetContainerComponent(
context: context, context: context,
peer: peer,
existingKeys: existingKeys, existingKeys: existingKeys,
completion: completion completion: completion
), ),
@ -368,3 +416,79 @@ final class WebAppSecureStorageTransferScreen: ViewControllerComponentContainer
} }
} }
} }
private final class AvatarComponent: Component {
let context: AccountContext
let peer: EnginePeer
let size: CGSize?
init(context: AccountContext, peer: EnginePeer, size: CGSize? = nil) {
self.context = context
self.peer = peer
self.size = size
}
static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.size != rhs.size {
return false
}
return true
}
final class View: UIView {
private var avatarNode: AvatarNode?
private var component: AvatarComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let size = component.size ?? availableSize
let avatarNode: AvatarNode
if let current = self.avatarNode {
avatarNode = current
} else {
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: floor(size.width * 0.5)))
avatarNode.displaysAsynchronously = false
self.avatarNode = avatarNode
self.addSubview(avatarNode.view)
}
avatarNode.frame = CGRect(origin: CGPoint(), size: size)
avatarNode.setPeer(
context: component.context,
theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme,
peer: component.peer,
synchronousLoad: true,
displayDimensions: size
)
avatarNode.updateSize(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)
}
}