Various improvements

This commit is contained in:
Ilya Laktyushin
2024-07-16 05:57:44 +04:00
parent b4cc4e0bd0
commit 49e5ac7e59
7 changed files with 570 additions and 273 deletions

View File

@@ -20,6 +20,8 @@ import MediaEditor
import EntityKeyboardGifContent
import CameraButtonComponent
import BundleIconComponent
import LottieComponent
import LottieComponentResourceContent
import UndoUI
import GalleryUI
@@ -532,7 +534,10 @@ public class StickerPickerScreen: ViewController {
self.containerView.addSubview(self.hostView)
if controller.hasInteractiveStickers {
self.storyStickersContentView = StoryStickersContentView(isPremium: context.isPremium)
self.storyStickersContentView = StoryStickersContentView(
context: context,
weather: controller.weather
)
self.storyStickersContentView?.locationAction = { [weak self] in
self?.controller?.presentLocationPicker()
}
@@ -2044,15 +2049,34 @@ public class StickerPickerScreen: ViewController {
return self.displayNode as! Node
}
public enum Weather {
public struct LoadedWeather {
public let emoji: String
public let emojiFile: TelegramMediaFile
public let temperature: Double
public init(emoji: String, emojiFile: TelegramMediaFile, temperature: Double) {
self.emoji = emoji
self.emojiFile = emojiFile
self.temperature = temperature
}
}
case none
case fetching
case loaded(StickerPickerScreen.Weather.LoadedWeather)
}
private let context: AccountContext
private let theme: PresentationTheme
fileprivate let forceDark: Bool
let forceDark: Bool
private let inputData: Signal<StickerPickerInput, NoError>
fileprivate let defaultToEmoji: Bool
let defaultToEmoji: Bool
let isFullscreen: Bool
let hasEmoji: Bool
let hasGifs: Bool
let hasInteractiveStickers: Bool
let weather: Signal<StickerPickerScreen.Weather, NoError>
private var currentLayout: ContainerViewLayout?
@@ -2068,7 +2092,7 @@ public class StickerPickerScreen: ViewController {
public var addLink: () -> Void = { }
public var addWeather: () -> Void = { }
public init(context: AccountContext, inputData: Signal<StickerPickerInput, NoError>, forceDark: Bool = false, expanded: Bool = false, defaultToEmoji: Bool = false, hasEmoji: Bool = true, hasGifs: Bool = false, hasInteractiveStickers: Bool = true) {
public init(context: AccountContext, inputData: Signal<StickerPickerInput, NoError>, forceDark: Bool = false, expanded: Bool = false, defaultToEmoji: Bool = false, hasEmoji: Bool = true, hasGifs: Bool = false, hasInteractiveStickers: Bool = true, weather: Signal<StickerPickerScreen.Weather, NoError> = .single(.none)) {
self.context = context
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme
@@ -2079,6 +2103,7 @@ public class StickerPickerScreen: ViewController {
self.hasEmoji = hasEmoji
self.hasGifs = hasGifs
self.hasInteractiveStickers = hasInteractiveStickers
self.weather = weather
super.init(navigationBarPresentationData: expanded ? NavigationBarPresentationData(presentationData: presentationData) : nil)
@@ -2141,22 +2166,28 @@ public class StickerPickerScreen: ViewController {
}
private final class InteractiveStickerButtonContent: Component {
let context: AccountContext
let theme: PresentationTheme
let title: String
let iconName: String
let title: String?
let iconName: String?
let iconFile: TelegramMediaFile?
let useOpaqueTheme: Bool
weak var tintContainerView: UIView?
public init(
context: AccountContext,
theme: PresentationTheme,
title: String,
iconName: String,
title: String?,
iconName: String?,
iconFile: TelegramMediaFile? = nil,
useOpaqueTheme: Bool,
tintContainerView: UIView
) {
self.context = context
self.theme = theme
self.title = title
self.iconName = iconName
self.iconFile = iconFile
self.useOpaqueTheme = useOpaqueTheme
self.tintContainerView = tintContainerView
}
@@ -2207,68 +2238,80 @@ private final class InteractiveStickerButtonContent: Component {
func update(component: InteractiveStickerButtonContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.backgroundLayer.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11).cgColor
let iconSize: CGSize
if component.iconName == "Sun" {
iconSize = self.icon.update(
let buttonSize: CGSize
if let title = component.title {
if let iconFile = component.iconFile {
iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(
LottieComponent(
content: LottieComponent.ResourceContent(context: component.context, file: iconFile, attemptSynchronously: true, providesPlaceholder: true),
color: nil,
placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.4),
loop: !["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"].contains(component.iconName ?? "")
)
),
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
} else {
iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: component.iconName ?? "",
tintColor: .white,
maxSize: CGSize(width: 20.0, height: 20.0)
)),
environment: {},
containerSize: availableSize
)
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(Text(
text: "☀️",
text: title.uppercased(),
font: Font.with(size: 23.0, design: .camera),
color: .white
)),
environment: {},
containerSize: availableSize
)
let padding: CGFloat = 7.0
let spacing: CGFloat = 4.0
buttonSize = CGSize(width: padding + iconSize.width + spacing + titleSize.width + padding, height: 34.0)
if let view = self.icon.view {
if view.superview == nil {
self.addSubview(view)
}
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((buttonSize.height - iconSize.height) / 2.0)), size: iconSize))
}
if let view = self.title.view {
if view.superview == nil {
self.addSubview(view)
}
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding + iconSize.width + spacing, y: floorToScreenPixels((buttonSize.height - titleSize.height) / 2.0)), size: titleSize))
}
} else {
iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: component.iconName,
tintColor: .white,
maxSize: CGSize(width: 20.0, height: 20.0)
)),
environment: {},
containerSize: availableSize
)
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(Text(
text: component.title.uppercased(),
font: Font.with(size: 23.0, design: .camera),
color: .white
)),
environment: {},
containerSize: availableSize
)
let padding: CGFloat = 7.0
let spacing: CGFloat = 4.0
let buttonSize = CGSize(width: padding + iconSize.width + spacing + titleSize.width + padding, height: 34.0)
if let view = self.icon.view {
if view.superview == nil {
self.addSubview(view)
}
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((buttonSize.height - iconSize.height) / 2.0)), size: iconSize))
}
if let view = self.title.view {
if view.superview == nil {
self.addSubview(view)
}
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding + iconSize.width + spacing, y: floorToScreenPixels((buttonSize.height - titleSize.height) / 2.0)), size: titleSize))
buttonSize = CGSize(width: 87.0, height: 34.0)
}
self.backgroundLayer.cornerRadius = 6.0
self.tintBackgroundLayer.cornerRadius = 6.0
self.backgroundLayer.frame = CGRect(origin: .zero, size: buttonSize)
var transition = transition
if self.backgroundLayer.frame.width.isZero {
transition = .immediate
}
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: buttonSize))
if self.tintBackgroundLayer.superlayer == nil, let tintContainerView = component.tintContainerView {
Queue.mainQueue().justDispatch {
let mappedFrame = self.convert(self.bounds, to: tintContainerView)
self.tintBackgroundLayer.frame = mappedFrame
transition.setFrame(layer: self.tintBackgroundLayer, frame: mappedFrame)
}
}
@@ -2441,12 +2484,14 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
private let padding: CGFloat
private let minSpacing: CGFloat
private let verticalSpacing: CGFloat
private let maxHorizontalItems: Int
init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], padding: CGFloat, minSpacing: CGFloat, verticalSpacing: CGFloat) {
init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], padding: CGFloat, minSpacing: CGFloat, verticalSpacing: CGFloat, maxHorizontalItems: Int) {
self.items = items
self.padding = padding
self.minSpacing = minSpacing
self.verticalSpacing = verticalSpacing
self.maxHorizontalItems = maxHorizontalItems
}
static func ==(lhs: ItemStack<ChildEnvironment>, rhs: ItemStack<ChildEnvironment>) -> Bool {
@@ -2462,6 +2507,9 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
if lhs.verticalSpacing != rhs.verticalSpacing {
return false
}
if lhs.maxHorizontalItems != rhs.maxHorizontalItems {
return false
}
return true
}
@@ -2491,7 +2539,7 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
let remainingWidth = context.availableSize.width - itemsWidth - context.component.padding * 2.0
let spacing = remainingWidth / CGFloat(rowItemsCount - 1)
if spacing < context.component.minSpacing || currentGroup.count == 3 {
if spacing < context.component.minSpacing || currentGroup.count == context.component.maxHorizontalItems {
groups.append(currentGroup)
currentGroup = []
}
@@ -2545,148 +2593,233 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
}
final class StoryStickersContentView: UIView, EmojiCustomContentView {
private let context: AccountContext
private let weatherFormatter: MeasurementFormatter
let tintContainerView = UIView()
private let container = ComponentView<Empty>()
private let isPremium: Bool
private var weatherDisposable: Disposable?
private var weather: StickerPickerScreen.Weather = .none
var locationAction: () -> Void = {}
var audioAction: () -> Void = {}
var reactionAction: () -> Void = {}
var linkAction: () -> Void = {}
var weatherAction: () -> Void = {}
private var params: (theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, availableSize: CGSize)?
init(isPremium: Bool) {
self.isPremium = isPremium
init(context: AccountContext, weather: Signal<StickerPickerScreen.Weather, NoError>) {
self.context = context
self.weatherFormatter = MeasurementFormatter()
self.weatherFormatter.locale = Locale.current
self.weatherFormatter.unitStyle = .short
self.weatherFormatter.numberFormatter.maximumFractionDigits = 0
super.init(frame: .zero)
self.weatherDisposable = (weather
|> deliverOnMainQueue).start(next: { [weak self] weather in
guard let self else {
return
}
self.weather = weather
if let (theme, strings, useOpaqueTheme, availableSize) = self.params {
let _ = self.update(theme: theme, strings: strings, useOpaqueTheme: useOpaqueTheme, availableSize: availableSize, transition: .easeInOut(duration: 0.25))
}
})
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.weatherDisposable?.dispose()
}
func update(theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.params = (theme, strings, useOpaqueTheme, availableSize)
let padding: CGFloat = 22.0
var maxHorizontalItems = 2
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "link",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "content",
component: AnyComponent(
InteractiveStickerButtonContent(
context: self.context,
theme: theme,
title: strings.MediaEditor_AddLink,
iconName: self.context.isPremium ? "Media Editor/Link" : "Media Editor/LinkLocked",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.linkAction()
}
})
)
)
)
items.append(
AnyComponentWithIdentity(
id: "location",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "content",
component: AnyComponent(
InteractiveStickerButtonContent(
context: self.context,
theme: theme,
title: strings.MediaEditor_AddLocationShort,
iconName: "Chat/Attach Menu/Location",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.locationAction()
}
})
)
)
)
if case .none = self.weather {
} else {
maxHorizontalItems = 3
switch self.weather {
case let .loaded(weather):
items.append(
AnyComponentWithIdentity(
id: "weather",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "weather",
component: AnyComponent(
InteractiveStickerButtonContent(
context: self.context,
theme: theme,
title: self.weatherFormatter.string(from: Measurement(value: weather.temperature, unit: UnitTemperature.celsius)),
iconName: weather.emoji,
iconFile: weather.emojiFile,
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.weatherAction()
}
})
)
)
)
case .fetching:
items.append(
AnyComponentWithIdentity(
id: "weather",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "weather",
component: AnyComponent(
InteractiveStickerButtonContent(
context: self.context,
theme: theme,
title: nil,
iconName: nil,
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.weatherAction()
}
})
)
)
)
default:
fatalError()
}
}
items.append(
AnyComponentWithIdentity(
id: "audio",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "audio",
component: AnyComponent(
InteractiveStickerButtonContent(
context: self.context,
theme: theme,
title: strings.MediaEditor_AddAudio,
iconName: "Media Editor/Audio",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.audioAction()
}
})
)
)
)
items.append(
AnyComponentWithIdentity(
id: "reaction",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "reaction",
component: AnyComponent(
InteractiveReactionButtonContent(theme: theme)
)
),
action: { [weak self] in
if let self {
self.reactionAction()
}
})
)
)
)
let size = self.container.update(
transition: transition,
component: AnyComponent(
ItemStack(
[
AnyComponentWithIdentity(
id: "link",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "content",
component: AnyComponent(
InteractiveStickerButtonContent(
theme: theme,
title: strings.MediaEditor_AddLink,
iconName: self.isPremium ? "Media Editor/Link" : "Media Editor/LinkLocked",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.linkAction()
}
})
)
),
AnyComponentWithIdentity(
id: "location",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "content",
component: AnyComponent(
InteractiveStickerButtonContent(
theme: theme,
title: strings.MediaEditor_AddLocationShort,
iconName: "Chat/Attach Menu/Location",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.locationAction()
}
})
)
),
AnyComponentWithIdentity(
id: "weather",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "weather",
component: AnyComponent(
InteractiveStickerButtonContent(
theme: theme,
title: "35°C",
iconName: "Sun",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.weatherAction()
}
})
)
),
AnyComponentWithIdentity(
id: "audio",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "audio",
component: AnyComponent(
InteractiveStickerButtonContent(
theme: theme,
title: strings.MediaEditor_AddAudio,
iconName: "Media Editor/Audio",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)
)
),
action: { [weak self] in
if let self {
self.audioAction()
}
})
)
),
AnyComponentWithIdentity(
id: "reaction",
component: AnyComponent(
CameraButton(
content: AnyComponentWithIdentity(
id: "reaction",
component: AnyComponent(
InteractiveReactionButtonContent(theme: theme)
)
),
action: { [weak self] in
if let self {
self.reactionAction()
}
})
)
)
],
items,
padding: 18.0,
minSpacing: 8.0,
verticalSpacing: 12.0
verticalSpacing: 12.0,
maxHorizontalItems: maxHorizontalItems
)
),
environment: {},