UI optimizations

This commit is contained in:
Ali 2023-02-07 23:23:06 +04:00
parent 5c71c08b6e
commit 5f3de7a40b
17 changed files with 158 additions and 74 deletions

View File

@ -5,7 +5,7 @@ import Lottie
import AppBundle
import Display
public final class AnimationNode : ASDisplayNode {
public final class AnimationNode: ASDisplayNode {
private let scale: CGFloat
public var speed: CGFloat = 1.0 {
didSet {
@ -15,8 +15,6 @@ public final class AnimationNode : ASDisplayNode {
}
}
//private var colorCallbacks: [LOTColorValueCallback] = []
public var didPlay = false
public var completion: (() -> Void)?
private var internalCompletion: (() -> Void)?
@ -43,9 +41,6 @@ public final class AnimationNode : ASDisplayNode {
if let colors = colors {
for (key, value) in colors {
view.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
/*let colorCallback = LOTColorValueCallback(color: value.cgColor)
self.colorCallbacks.append(colorCallback)
view.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))*/
}
if let value = colors["__allcolors__"] {
@ -77,9 +72,6 @@ public final class AnimationNode : ASDisplayNode {
if let colors = colors {
for (key, value) in colors {
view.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
/*let colorCallback = LOTColorValueCallback(color: value.cgColor)
self.colorCallbacks.append(colorCallback)
view.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))*/
}
if let value = colors["__allcolors__"] {
@ -121,9 +113,6 @@ public final class AnimationNode : ASDisplayNode {
if let colors = colors {
for (key, value) in colors {
self.animationView()?.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
/*let colorCallback = LOTColorValueCallback(color: value.cgColor)
self.colorCallbacks.append(colorCallback)
self.animationView()?.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))*/
}
}
}

View File

@ -75,8 +75,6 @@ func localizedCountryNamesAndCodes(strings: PresentationStrings) -> [((String, S
}
}
result.append(((englishCountryName, countryName), country.id, codes))
} else {
assertionFailure()
}
}
return result

View File

@ -6,6 +6,22 @@ public enum DeviceType {
}
public enum DeviceMetrics: CaseIterable, Equatable {
public struct Performance {
public let isGraphicallyCapable: Bool
init() {
var length: Int = 4
var cpuCount: UInt32 = 0
sysctlbyname("hw.ncpu", &cpuCount, &length, nil, 0)
#if DEBUG
cpuCount = 2
#endif
self.isGraphicallyCapable = cpuCount >= 6
}
}
case iPhone4
case iPhone5
case iPhone6
@ -33,6 +49,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
case iPadPro3rdGen
case iPadMini6thGen
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?)
public static let performance = Performance()
public static var allCases: [DeviceMetrics] {
return [

View File

@ -290,7 +290,7 @@ private final class ChatListViewSpaceState {
postboxLog("existingEntityIds not unique: \(allEntityIds)")
postboxLog("allIndices: \(allIndices)")
assert(false)
//assert(false)
//preconditionFailure()
}

View File

@ -327,8 +327,6 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe
let animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0)
self.animationNode = animationNode
animationNode.animationView()?.logHierarchyKeypaths()
let animationBackgroundNode = ASDisplayNode()
animationBackgroundNode.cornerRadius = 20.0
animationBackgroundNode.backgroundColor = backgroundColor

View File

@ -1,5 +1,6 @@
import Foundation
import UIKit
import Display
import TelegramCore
import TelegramUIPreferences
import Postbox
@ -28,6 +29,10 @@ public func selectReactionFillStaticColor(theme: PresentationTheme, wallpaper: T
}
public func dateFillNeedsBlur(theme: PresentationTheme, wallpaper: TelegramWallpaper) -> Bool {
if !DeviceMetrics.performance.isGraphicallyCapable {
return false
}
if case .builtin = wallpaper {
return false
} else if case .color = wallpaper {

View File

@ -353,6 +353,7 @@ swift_library(
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/TelegramUI/Components/StorageUsageScreen",
"//submodules/TelegramUI/Components/AvatarEditorScreen",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/MediaPasteboardUI:MediaPasteboardUI",
"//submodules/DrawingUI:DrawingUI",
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",

View File

@ -39,10 +39,10 @@ private final class LottieDirectContent: LottieComponent.Content {
return true
}
override func load(_ f: @escaping (Data) -> Void) -> Disposable {
override func load(_ f: @escaping (Data, String?) -> Void) -> Disposable {
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.path)) {
let result = TGGUnzipData(data, 2 * 1024 * 1024) ?? data
f(result)
f(result, nil)
}
return EmptyDisposable

View File

@ -15,6 +15,7 @@ swift_library(
"//submodules/Components/HierarchyTrackingLayer",
"//submodules/rlottie:RLottieBinding",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AppBundle",
],
visibility = [
"//visibility:public",

View File

@ -5,7 +5,7 @@ import ComponentFlow
import HierarchyTrackingLayer
import RLottieBinding
import SwiftSignalKit
import Accelerate
import AppBundle
public final class LottieComponent: Component {
public typealias EnvironmentType = Empty
@ -25,10 +25,36 @@ public final class LottieComponent: Component {
preconditionFailure()
}
open func load(_ f: @escaping (Data) -> Void) -> Disposable {
open func load(_ f: @escaping (Data, String?) -> Void) -> Disposable {
preconditionFailure()
}
}
public final class AppBundleContent: Content {
public let name: String
public init(name: String) {
self.name = name
}
override public func isEqual(to other: Content) -> Bool {
guard let other = other as? AppBundleContent else {
return false
}
if self.name != other.name {
return false
}
return true
}
override public func load(_ f: @escaping (Data, String?) -> Void) -> Disposable {
if let url = getAppBundle().url(forResource: self.name, withExtension: "json"), let data = try? Data(contentsOf: url) {
f(data, url.path)
}
return EmptyDisposable
}
}
public let content: Content
public let color: UIColor
@ -63,6 +89,9 @@ public final class LottieComponent: Component {
private var currentFrame: Int = 0
private var currentFrameStartTime: Double?
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
private var isVisible: Bool = false
private var displayLink: SharedDisplayLinkDriver.Link?
private var currentTemplateFrameImage: UIImage?
@ -76,11 +105,30 @@ public final class LottieComponent: Component {
}
override init(frame: CGRect) {
//self.hierarchyTrackingLayer = HierarchyTrackingLayer()
super.init(frame: frame)
//self.layer.addSublayer(self.hierarchyTrackingLayer)
let hierarchyTrackingLayer = HierarchyTrackingLayer()
self.hierarchyTrackingLayer = hierarchyTrackingLayer
self.layer.addSublayer(hierarchyTrackingLayer)
hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let self else {
return
}
if !self.isVisible {
self.isVisible = true
self.visibilityUpdated()
}
}
hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
guard let self else {
return
}
if self.isVisible {
self.isVisible = false
self.visibilityUpdated()
}
}
}
required init?(coder: NSCoder) {
@ -91,11 +139,22 @@ public final class LottieComponent: Component {
self.currentContentDisposable?.dispose()
}
private func visibilityUpdated() {
if self.isVisible {
if self.scheduledPlayOnce {
self.playOnce()
}
}
}
public func playOnce(delay: Double = 0.0) {
guard let _ = self.animationInstance else {
self.scheduledPlayOnce = true
return
}
if !self.isVisible {
return
}
self.scheduledPlayOnce = false
@ -135,8 +194,8 @@ public final class LottieComponent: Component {
}
}
private func loadAnimation(data: Data) {
self.animationInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: "")
private func loadAnimation(data: Data, cacheKey: String?) {
self.animationInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: cacheKey ?? "")
if self.scheduledPlayOnce {
self.scheduledPlayOnce = false
self.playOnce()
@ -184,12 +243,6 @@ public final class LottieComponent: Component {
return
}
var destinationBuffer = vImage_Buffer()
destinationBuffer.width = UInt(context.scaledSize.width)
destinationBuffer.height = UInt(context.scaledSize.height)
destinationBuffer.data = context.bytes
destinationBuffer.rowBytes = context.bytesPerRow
animationInstance.renderFrame(with: Int32(self.currentFrame % Int(animationInstance.frameCount)), into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(currentDisplaySize.width), height: Int32(currentDisplaySize.height), bytesPerRow: Int32(context.bytesPerRow))
self.currentTemplateFrameImage = context.generateImage()?.withRenderingMode(.alwaysTemplate)
self.image = self.currentTemplateFrameImage
@ -216,12 +269,12 @@ public final class LottieComponent: Component {
if previousComponent?.content != component.content {
self.currentContentDisposable?.dispose()
let content = component.content
self.currentContentDisposable = component.content.load { [weak self, weak content] data in
self.currentContentDisposable = component.content.load { [weak self, weak content] data, cacheKey in
Queue.mainQueue().async {
guard let self, self.component?.content == content else {
return
}
self.loadAnimation(data: data)
self.loadAnimation(data: data, cacheKey: cacheKey)
}
}
} else if redrawImage {

View File

@ -30,7 +30,7 @@ public extension LottieComponent {
return true
}
override public func load(_ f: @escaping (Data) -> Void) -> Disposable {
override public func load(_ f: @escaping (Data, String?) -> Void) -> Disposable {
let fileId = self.fileId
let mediaBox = self.context.account.postbox.mediaBox
return (self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
@ -60,7 +60,7 @@ public extension LottieComponent {
guard let data else {
return
}
f(data)
f(data, nil)
})
}
}

View File

@ -491,7 +491,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if (strongSelf.context.sharedContext.currentPresentationData.with({ $0 })).reduceMotion {
return
}
strongSelf.backgroundNode.animateEvent(transition: transition, extendAnimation: false)
if DeviceMetrics.performance.isGraphicallyCapable {
strongSelf.backgroundNode.animateEvent(transition: transition, extendAnimation: false)
}
}
getMessageTransitionNode = { [weak self] in
@ -2214,7 +2216,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if (self.context.sharedContext.currentPresentationData.with({ $0 })).reduceMotion {
return
}
self.backgroundNode.animateEvent(transition: transition, extendAnimation: false)
if DeviceMetrics.performance.isGraphicallyCapable {
self.backgroundNode.animateEvent(transition: transition, extendAnimation: false)
}
}
//self.historyNode.didScrollWithOffset?(listBottomInset - previousListBottomInset, transition, nil)
}

View File

@ -184,10 +184,16 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundColorNode)
self.containerNode.addSubnode(self.effectNode)
if DeviceMetrics.performance.isGraphicallyCapable {
self.containerNode.addSubnode(self.effectNode)
}
self.addSubnode(self.borderNode)
self.borderNode.addSubnode(self.borderEffectNode)
if DeviceMetrics.performance.isGraphicallyCapable {
self.borderNode.addSubnode(self.borderEffectNode)
}
}
override func didLoad() {
@ -196,7 +202,9 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode {
self.containerNode.view.mask = self.maskNode.view
self.borderNode.view.mask = self.borderMaskNode.view
self.backgroundNode?.updateIsLooping(true)
if DeviceMetrics.performance.isGraphicallyCapable {
self.backgroundNode?.updateIsLooping(true)
}
}
private var bottomInset: (Int, CGFloat)?

View File

@ -12,6 +12,7 @@ import AudioBlob
import ChatPresentationInterfaceState
import ComponentFlow
import LottieAnimationComponent
import LottieComponent
private let offsetThreshold: CGFloat = 10.0
private let dismissOffsetThreshold: CGFloat = 70.0
@ -289,11 +290,17 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
}
}
private lazy var micLock: (UIView & TGModernConversationInputMicButtonLock) = {
let lockView = LockView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 60.0)), theme: self.theme, strings: self.strings)
lockView.addTarget(self, action: #selector(handleStopTap), for: .touchUpInside)
return lockView
}()
private var micLockValue: (UIView & TGModernConversationInputMicButtonLock)?
private var micLock: UIView & TGModernConversationInputMicButtonLock {
if let current = self.micLockValue {
return current
} else {
let lockView = LockView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 60.0)), theme: self.theme, strings: self.strings)
lockView.addTarget(self, action: #selector(handleStopTap), for: .touchUpInside)
self.micLockValue = lockView
return lockView
}
}
init(theme: PresentationTheme, strings: PresentationStrings, presentController: @escaping (ViewController) -> Void) {
self.theme = theme
@ -363,37 +370,34 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
animationName = "anim_micToVideo"
}
var animationMode: LottieAnimationComponent.AnimationItem.Mode = .still(position: .end)
if previousMode != mode {
animationMode = .animating(loop: false)
}
//var animationMode: LottieAnimationComponent.AnimationItem.Mode = .still(position: .end)
let colorKeys = ["__allcolors__"]
/*let colorKeys = ["__allcolors__"]
var colors: [String: UIColor] = [:]
for colorKey in colorKeys {
colors[colorKey] = self.theme.chat.inputPanel.panelControlColor.blitOver(self.theme.chat.inputPanel.inputBackgroundColor, alpha: 1.0)
}
}*/
let _ = animationView.update(
transition: .immediate,
component: AnyComponent(LottieAnimationComponent(
animation: LottieAnimationComponent.AnimationItem(
name: animationName,
mode: animationMode
),
colors: colors,
size: animationFrame.size
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: animationName),
color: self.theme.chat.inputPanel.panelControlColor.blitOver(self.theme.chat.inputPanel.inputBackgroundColor, alpha: 1.0)
)),
environment: {},
containerSize: animationFrame.size
)
if let view = animationView.view {
if let view = animationView.view as? LottieComponent.View {
view.isUserInteractionEnabled = false
if view.superview == nil {
self.insertSubview(view, at: 0)
}
view.frame = animationFrame
if previousMode != mode {
view.playOnce()
}
}
}
@ -404,7 +408,7 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
self.pallete = legacyInputMicPalette(from: theme)
self.micDecorationValue?.setColor(self.theme.chat.inputPanel.actionControlFillColor)
(self.micLock as? LockView)?.updateTheme(theme)
(self.micLockValue as? LockView)?.updateTheme(theme)
}
deinit {

View File

@ -33,6 +33,7 @@ import ChatControllerInteraction
import UndoUI
import PremiumUI
import StickerPeekUI
import LottieComponent
private let accessoryButtonFont = Font.medium(14.0)
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
@ -184,11 +185,11 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
if let animationView = self.animationView {
let width = AccessoryItemIconButtonNode.calculateWidth(item: item, image: image, text: "", strings: self.strings)
let iconSize = CGSize(width: width, height: width)
//let iconSize = CGSize(width: width, height: width)
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - width) / 2.0), y: floor((size.height - width) / 2.0) - bottomInset), size: CGSize(width: width, height: width))
let colorKeys: [String] = ["__allcolors__"]
//let colorKeys: [String] = ["__allcolors__"]
let animationName: String
var animationMode: LottieAnimationComponent.AnimationItem.Mode = .still(position: .end)
@ -286,30 +287,30 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
}
}
var colors: [String: UIColor] = [:]
/*var colors: [String: UIColor] = [:]
for colorKey in colorKeys {
colors[colorKey] = self.theme.chat.inputPanel.inputControlColor.blitOver(self.theme.chat.inputPanel.inputBackgroundColor, alpha: 1.0)
}
}*/
let animationSize = animationView.update(
transition: .immediate,
component: AnyComponent(LottieAnimationComponent(
animation: LottieAnimationComponent.AnimationItem(
name: animationName,
mode: animationMode
),
colors: colors,
size: iconSize
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: animationName),
color: self.theme.chat.inputPanel.inputControlColor.blitOver(self.theme.chat.inputPanel.inputBackgroundColor, alpha: 1.0)
)),
environment: {},
containerSize: animationFrame.size
)
if let view = animationView.view {
if let view = animationView.view as? LottieComponent.View {
view.isUserInteractionEnabled = false
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = CGRect(origin: CGPoint(x: animationFrame.minX + floor((animationFrame.width - animationSize.width) / 2.0), y: animationFrame.minY + floor((animationFrame.height - animationSize.height) / 2.0)), size: animationSize)
if case .animating = animationMode {
view.playOnce()
}
}
}
}

View File

@ -43,7 +43,7 @@
}
}
_animation = rlottie::Animation::loadFromData(std::string(reinterpret_cast<const char *>(data.bytes), data.length), std::string([cacheKey UTF8String]), "", false, colorsVector, modifier);
_animation = rlottie::Animation::loadFromData(std::string(reinterpret_cast<const char *>(data.bytes), data.length), std::string([cacheKey UTF8String]), "", cacheKey.length != 0, colorsVector, modifier);
if (_animation == nullptr) {
return nil;
}

View File

@ -22,4 +22,8 @@
#define LOTTIE_IMAGE_MODULE_DISABLED
#endif
#ifndef LOTTIE_CACHE_SUPPORT
#define LOTTIE_CACHE_SUPPORT
#endif
#endif // CONFIG_H