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
7d8920db82
commit
0186f12972
@ -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";
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
submodules/TelegramUI/Images.xcassets/Components/RoundVideoCorner.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Components/RoundVideoCorner.imageset/Contents.json
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Components/RoundVideoCorner.imageset/vlog.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Components/RoundVideoCorner.imageset/vlog.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
@ -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",
|
||||||
|
@ -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() {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user