mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Story weather display
This commit is contained in:
parent
a6c7e92d6f
commit
f4cc9d1aad
@ -8,6 +8,7 @@ import AnimatedStickerNode
|
|||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import StickerResources
|
import StickerResources
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
private func generateIcon(style: DrawingWeatherEntity.Style) -> UIImage? {
|
private func generateIcon(style: DrawingWeatherEntity.Style) -> UIImage? {
|
||||||
guard let image = UIImage(bundleImageName: "Chat/Attach Menu/Location") else {
|
guard let image = UIImage(bundleImageName: "Chat/Attach Menu/Location") else {
|
||||||
@ -50,7 +51,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
let backgroundView: UIView
|
let backgroundView: UIView
|
||||||
let blurredBackgroundView: BlurredBackgroundView
|
|
||||||
|
|
||||||
let textView: DrawingTextView
|
let textView: DrawingTextView
|
||||||
let iconView: UIImageView
|
let iconView: UIImageView
|
||||||
@ -61,13 +61,14 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
private let stickerFetchedDisposable = MetaDisposable()
|
private let stickerFetchedDisposable = MetaDisposable()
|
||||||
private let cachedDisposable = MetaDisposable()
|
private let cachedDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
let temperature: String
|
||||||
|
|
||||||
init(context: AccountContext, entity: DrawingWeatherEntity) {
|
init(context: AccountContext, entity: DrawingWeatherEntity) {
|
||||||
|
self.temperature = stringForTemperature(entity.temperature)
|
||||||
|
|
||||||
self.backgroundView = UIView()
|
self.backgroundView = UIView()
|
||||||
self.backgroundView.clipsToBounds = true
|
self.backgroundView.clipsToBounds = true
|
||||||
|
|
||||||
self.blurredBackgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.25), enableBlur: true)
|
|
||||||
self.blurredBackgroundView.clipsToBounds = true
|
|
||||||
|
|
||||||
self.textView = DrawingTextView(frame: .zero)
|
self.textView = DrawingTextView(frame: .zero)
|
||||||
self.textView.clipsToBounds = false
|
self.textView.clipsToBounds = false
|
||||||
|
|
||||||
@ -95,7 +96,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
|
|
||||||
self.textView.delegate = self
|
self.textView.delegate = self
|
||||||
self.addSubview(self.backgroundView)
|
self.addSubview(self.backgroundView)
|
||||||
self.addSubview(self.blurredBackgroundView)
|
|
||||||
self.addSubview(self.textView)
|
self.addSubview(self.textView)
|
||||||
self.addSubview(self.iconView)
|
self.addSubview(self.iconView)
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
self.imageNode.frame = self.iconView.frame.offsetBy(dx: 0.0, dy: 2.0)
|
self.imageNode.frame = self.iconView.frame.offsetBy(dx: 0.0, dy: 2.0)
|
||||||
|
|
||||||
if let animationNode = self.animationNode {
|
if let animationNode = self.animationNode {
|
||||||
animationNode.frame = self.iconView.frame.offsetBy(dx: 0.0, dy: 2.0)
|
animationNode.frame = self.iconView.frame
|
||||||
animationNode.updateLayout(size: self.iconView.frame.size)
|
animationNode.updateLayout(size: self.iconView.frame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +147,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
|
|
||||||
self.textView.frame = CGRect(origin: CGPoint(x: self.bounds.width - self.textSize.width - 6.0, y: floorToScreenPixels((self.bounds.height - self.textSize.height) / 2.0)), size: self.textSize)
|
self.textView.frame = CGRect(origin: CGPoint(x: self.bounds.width - self.textSize.width - 6.0, y: floorToScreenPixels((self.bounds.height - self.textSize.height) / 2.0)), size: self.textSize)
|
||||||
self.backgroundView.frame = self.bounds
|
self.backgroundView.frame = self.bounds
|
||||||
self.blurredBackgroundView.frame = self.bounds
|
|
||||||
self.blurredBackgroundView.update(size: self.bounds.size, transition: .immediate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func selectedTapAction() -> Bool {
|
override func selectedTapAction() -> Bool {
|
||||||
@ -161,16 +159,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
case .white:
|
case .white:
|
||||||
updatedStyle = .black
|
updatedStyle = .black
|
||||||
case .black:
|
case .black:
|
||||||
updatedStyle = .transparent
|
|
||||||
case .transparent:
|
|
||||||
if self.weatherEntity.hasCustomColor {
|
|
||||||
updatedStyle = .custom
|
|
||||||
} else {
|
|
||||||
updatedStyle = .white
|
|
||||||
}
|
|
||||||
case .custom:
|
|
||||||
updatedStyle = .white
|
|
||||||
case .blur:
|
|
||||||
updatedStyle = .white
|
updatedStyle = .white
|
||||||
}
|
}
|
||||||
self.weatherEntity.style = updatedStyle
|
self.weatherEntity.style = updatedStyle
|
||||||
@ -182,7 +170,7 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
|
|
||||||
private var displayFontSize: CGFloat {
|
private var displayFontSize: CGFloat {
|
||||||
var textFontSize: CGFloat = 0.07
|
var textFontSize: CGFloat = 0.07
|
||||||
let textLength = self.weatherEntity.temperature.count
|
let textLength = self.temperature.count
|
||||||
if textLength > 10 {
|
if textLength > 10 {
|
||||||
textFontSize = max(0.01, 0.07 - CGFloat(textLength - 10) / 100.0)
|
textFontSize = max(0.01, 0.07 - CGFloat(textLength - 10) / 100.0)
|
||||||
}
|
}
|
||||||
@ -194,7 +182,7 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateText() {
|
private func updateText() {
|
||||||
let text = NSMutableAttributedString(string: self.weatherEntity.temperature.uppercased())
|
let text = NSMutableAttributedString(string: self.temperature.uppercased())
|
||||||
let range = NSMakeRange(0, text.length)
|
let range = NSMakeRange(0, text.length)
|
||||||
let fontSize = self.displayFontSize
|
let fontSize = self.displayFontSize
|
||||||
|
|
||||||
@ -213,15 +201,8 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
switch self.weatherEntity.style {
|
switch self.weatherEntity.style {
|
||||||
case .white:
|
case .white:
|
||||||
textColor = .black
|
textColor = .black
|
||||||
case .black, .transparent, .blur:
|
case .black:
|
||||||
textColor = .white
|
textColor = .white
|
||||||
case .custom:
|
|
||||||
let color = self.weatherEntity.color.toUIColor()
|
|
||||||
if color.lightness > 0.705 {
|
|
||||||
textColor = .black
|
|
||||||
} else {
|
|
||||||
textColor = .white
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text.addAttribute(.foregroundColor, value: textColor, range: range)
|
text.addAttribute(.foregroundColor, value: textColor, range: range)
|
||||||
@ -241,34 +222,10 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
self.textView.textColor = .black
|
self.textView.textColor = .black
|
||||||
self.backgroundView.backgroundColor = .white
|
self.backgroundView.backgroundColor = .white
|
||||||
self.backgroundView.isHidden = false
|
self.backgroundView.isHidden = false
|
||||||
self.blurredBackgroundView.isHidden = true
|
|
||||||
case .black:
|
case .black:
|
||||||
self.textView.textColor = .white
|
self.textView.textColor = .white
|
||||||
self.backgroundView.backgroundColor = .black
|
self.backgroundView.backgroundColor = .black
|
||||||
self.backgroundView.isHidden = false
|
self.backgroundView.isHidden = false
|
||||||
self.blurredBackgroundView.isHidden = true
|
|
||||||
case .transparent:
|
|
||||||
self.textView.textColor = .white
|
|
||||||
self.backgroundView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.2)
|
|
||||||
self.backgroundView.isHidden = false
|
|
||||||
self.blurredBackgroundView.isHidden = true
|
|
||||||
case .custom:
|
|
||||||
let color = self.weatherEntity.color.toUIColor()
|
|
||||||
let textColor: UIColor
|
|
||||||
if color.lightness > 0.705 {
|
|
||||||
textColor = .black
|
|
||||||
} else {
|
|
||||||
textColor = .white
|
|
||||||
}
|
|
||||||
self.textView.textColor = textColor
|
|
||||||
self.backgroundView.backgroundColor = color
|
|
||||||
self.backgroundView.isHidden = false
|
|
||||||
self.blurredBackgroundView.isHidden = true
|
|
||||||
case .blur:
|
|
||||||
self.textView.textColor = .white
|
|
||||||
self.backgroundView.isHidden = true
|
|
||||||
self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff)
|
|
||||||
self.blurredBackgroundView.isHidden = false
|
|
||||||
}
|
}
|
||||||
self.textView.textAlignment = .left
|
self.textView.textAlignment = .left
|
||||||
|
|
||||||
@ -282,10 +239,8 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundView.layer.cornerRadius = self.textSize.height * 0.2
|
self.backgroundView.layer.cornerRadius = self.textSize.height * 0.2
|
||||||
self.blurredBackgroundView.layer.cornerRadius = self.backgroundView.layer.cornerRadius
|
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
self.backgroundView.layer.cornerCurve = .continuous
|
self.backgroundView.layer.cornerCurve = .continuous
|
||||||
self.blurredBackgroundView.layer.cornerCurve = .continuous
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.update(animated: animated)
|
super.update(animated: animated)
|
||||||
|
@ -249,7 +249,7 @@ public enum MediaArea: Codable, Equatable {
|
|||||||
try container.encode(coordinates, forKey: .coordinates)
|
try container.encode(coordinates, forKey: .coordinates)
|
||||||
try container.encode(url, forKey: .value)
|
try container.encode(url, forKey: .value)
|
||||||
case let .weather(coordinates, emoji, temperature, flags):
|
case let .weather(coordinates, emoji, temperature, flags):
|
||||||
try container.encode(MediaAreaType.link.rawValue, forKey: .type)
|
try container.encode(MediaAreaType.weather.rawValue, forKey: .type)
|
||||||
try container.encode(coordinates, forKey: .coordinates)
|
try container.encode(coordinates, forKey: .coordinates)
|
||||||
try container.encode(emoji, forKey: .value)
|
try container.encode(emoji, forKey: .value)
|
||||||
try container.encode(temperature, forKey: .temperature)
|
try container.encode(temperature, forKey: .temperature)
|
||||||
|
@ -120,7 +120,7 @@ public enum CodableDrawingEntity: Equatable {
|
|||||||
rotation = entity.rotation
|
rotation = entity.rotation
|
||||||
scale = entity.scale
|
scale = entity.scale
|
||||||
if let size {
|
if let size {
|
||||||
cornerRadius = 10.0 / (size.width * entity.scale)
|
cornerRadius = (size.height * 0.17) / size.width
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
@ -191,6 +191,17 @@ public enum CodableDrawingEntity: Equatable {
|
|||||||
coordinates: coordinates,
|
coordinates: coordinates,
|
||||||
url: url
|
url: url
|
||||||
)
|
)
|
||||||
|
case let .weather(entity):
|
||||||
|
var flags: MediaArea.WeatherFlags = []
|
||||||
|
if entity.style == .black {
|
||||||
|
flags.insert(.isDark)
|
||||||
|
}
|
||||||
|
return .weather(
|
||||||
|
coordinates: coordinates,
|
||||||
|
emoji: entity.emoji,
|
||||||
|
temperature: entity.temperature,
|
||||||
|
flags: flags
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
case uuid
|
case uuid
|
||||||
case style
|
case style
|
||||||
case color
|
case color
|
||||||
case hasCustomColor
|
case emoji
|
||||||
case temperature
|
case temperature
|
||||||
case icon
|
case icon
|
||||||
case referenceDrawingSize
|
case referenceDrawingSize
|
||||||
@ -25,9 +25,6 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
public enum Style: Codable, Equatable {
|
public enum Style: Codable, Equatable {
|
||||||
case white
|
case white
|
||||||
case black
|
case black
|
||||||
case transparent
|
|
||||||
case custom
|
|
||||||
case blur
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var uuid: UUID
|
public var uuid: UUID
|
||||||
@ -37,20 +34,11 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
|
|
||||||
|
|
||||||
public var style: Style
|
public var style: Style
|
||||||
public var temperature: String
|
|
||||||
public var icon: TelegramMediaFile?
|
public var icon: TelegramMediaFile?
|
||||||
public var color: DrawingColor = DrawingColor(color: .white) {
|
public var emoji: String
|
||||||
didSet {
|
public var temperature: Double
|
||||||
if self.color.toUIColor().argb == UIColor.white.argb {
|
|
||||||
self.style = .white
|
public var color: DrawingColor = DrawingColor.clear
|
||||||
self.hasCustomColor = false
|
|
||||||
} else {
|
|
||||||
self.style = .custom
|
|
||||||
self.hasCustomColor = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public var hasCustomColor = false
|
|
||||||
public var lineWidth: CGFloat = 0.0
|
public var lineWidth: CGFloat = 0.0
|
||||||
|
|
||||||
public var referenceDrawingSize: CGSize
|
public var referenceDrawingSize: CGSize
|
||||||
@ -74,13 +62,14 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(temperature: String, style: Style, icon: TelegramMediaFile?) {
|
public init(emoji: String, emojiFile: TelegramMediaFile?, temperature: Double, style: Style) {
|
||||||
self.uuid = UUID()
|
self.uuid = UUID()
|
||||||
|
|
||||||
|
self.emoji = emoji
|
||||||
|
self.icon = emojiFile
|
||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
self.style = style
|
self.style = style
|
||||||
self.icon = icon
|
|
||||||
|
|
||||||
self.referenceDrawingSize = .zero
|
self.referenceDrawingSize = .zero
|
||||||
self.position = .zero
|
self.position = .zero
|
||||||
self.width = 100.0
|
self.width = 100.0
|
||||||
@ -91,10 +80,9 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||||
self.temperature = try container.decode(String.self, forKey: .temperature)
|
self.emoji = try container.decode(String.self, forKey: .emoji)
|
||||||
|
self.temperature = try container.decode(Double.self, forKey: .temperature)
|
||||||
self.style = try container.decode(Style.self, forKey: .style)
|
self.style = try container.decode(Style.self, forKey: .style)
|
||||||
self.color = try container.decodeIfPresent(DrawingColor.self, forKey: .color) ?? DrawingColor(color: .white)
|
|
||||||
self.hasCustomColor = try container.decodeIfPresent(Bool.self, forKey: .hasCustomColor) ?? false
|
|
||||||
|
|
||||||
if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) {
|
if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) {
|
||||||
self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile
|
self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile
|
||||||
@ -113,10 +101,9 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
try container.encode(self.uuid, forKey: .uuid)
|
try container.encode(self.uuid, forKey: .uuid)
|
||||||
|
try container.encode(self.emoji, forKey: .emoji)
|
||||||
try container.encode(self.temperature, forKey: .temperature)
|
try container.encode(self.temperature, forKey: .temperature)
|
||||||
try container.encode(self.style, forKey: .style)
|
try container.encode(self.style, forKey: .style)
|
||||||
try container.encode(self.color, forKey: .color)
|
|
||||||
try container.encode(self.hasCustomColor, forKey: .hasCustomColor)
|
|
||||||
|
|
||||||
var encoder = PostboxEncoder()
|
var encoder = PostboxEncoder()
|
||||||
if let icon = self.icon {
|
if let icon = self.icon {
|
||||||
@ -137,7 +124,7 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func duplicate(copy: Bool) -> DrawingEntity {
|
public func duplicate(copy: Bool) -> DrawingEntity {
|
||||||
let newEntity = DrawingWeatherEntity(temperature: self.temperature, style: self.style, icon: self.icon)
|
let newEntity = DrawingWeatherEntity(emoji: self.emoji, emojiFile: self.icon, temperature: self.temperature, style: self.style)
|
||||||
if copy {
|
if copy {
|
||||||
newEntity.uuid = self.uuid
|
newEntity.uuid = self.uuid
|
||||||
}
|
}
|
||||||
@ -156,6 +143,9 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||||||
if self.uuid != other.uuid {
|
if self.uuid != other.uuid {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if self.emoji != other.emoji {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if self.temperature != other.temperature {
|
if self.temperature != other.temperature {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,9 @@ private func prerenderTextTransformations(entity: DrawingEntity, image: UIImage,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] {
|
func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] {
|
||||||
if let entity = entity as? DrawingStickerEntity {
|
if entity is DrawingWeatherEntity {
|
||||||
|
return []
|
||||||
|
} else if let entity = entity as? DrawingStickerEntity {
|
||||||
if case let .file(_, type) = entity.content, case .reaction = type {
|
if case let .file(_, type) = entity.content, case .reaction = type {
|
||||||
return []
|
return []
|
||||||
} else {
|
} else {
|
||||||
@ -126,10 +128,10 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti
|
|||||||
return entities
|
return entities
|
||||||
} else if let entity = entity as? DrawingLocationEntity {
|
} else if let entity = entity as? DrawingLocationEntity {
|
||||||
return [prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace)]
|
return [prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace)]
|
||||||
} else if let entity = entity as? DrawingWeatherEntity {
|
|
||||||
return [prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace)]
|
|
||||||
} else if let entity = entity as? DrawingLinkEntity {
|
} else if let entity = entity as? DrawingLinkEntity {
|
||||||
return [prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace)]
|
return [prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace)]
|
||||||
|
} else if let entity = entity as? DrawingWeatherEntity {
|
||||||
|
return [prerenderTextTransformations(entity: entity, image: renderImage, textScale: textScale, colorSpace: colorSpace)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
|
@ -4583,11 +4583,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addWeather(_ weather: StickerPickerScreen.Weather.LoadedWeather) {
|
func addWeather(_ weather: StickerPickerScreen.Weather.LoadedWeather) {
|
||||||
|
let maxWeatherCount = 3
|
||||||
|
var currentWeatherCount = 0
|
||||||
|
self.entitiesView.eachView { entityView in
|
||||||
|
if entityView.entity is DrawingWeatherEntity {
|
||||||
|
currentWeatherCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currentWeatherCount >= maxWeatherCount {
|
||||||
|
self.controller?.hapticFeedback.error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.interaction?.insertEntity(
|
self.interaction?.insertEntity(
|
||||||
DrawingWeatherEntity(
|
DrawingWeatherEntity(
|
||||||
temperature: stringForTemperature(weather.temperature),
|
emoji: weather.emoji,
|
||||||
style: .white,
|
emojiFile: weather.emojiFile,
|
||||||
icon: weather.emojiFile
|
temperature: weather.temperature,
|
||||||
|
style: .white
|
||||||
),
|
),
|
||||||
scale: nil,
|
scale: nil,
|
||||||
position: nil
|
position: nil
|
||||||
@ -6061,7 +6074,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
})
|
})
|
||||||
self.present(controller, in: .window(.root))
|
self.present(controller, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybePresentDiscardAlert() {
|
func maybePresentDiscardAlert() {
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
if !self.isEligibleForDraft() {
|
if !self.isEligibleForDraft() {
|
||||||
|
@ -58,18 +58,14 @@ func getWeather(context: AccountContext) -> Signal<StickerPickerScreen.Weather,
|
|||||||
return getWeatherData(context: context, location: location)
|
return getWeatherData(context: context, location: location)
|
||||||
|> mapToSignal { weather in
|
|> mapToSignal { weather in
|
||||||
if let weather {
|
if let weather {
|
||||||
return context.animatedEmojiStickers
|
if let match = context.animatedEmojiStickersValue[weather.emoji.strippedEmoji]?.first {
|
||||||
|> take(1)
|
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
|
||||||
|> mapToSignal { result in
|
emoji: weather.emoji.strippedEmoji,
|
||||||
if let match = result[weather.emoji.strippedEmoji]?.first {
|
emojiFile: match.file,
|
||||||
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
|
temperature: weather.temperature
|
||||||
emoji: weather.emoji.strippedEmoji,
|
)))
|
||||||
emojiFile: match.file,
|
} else {
|
||||||
temperature: weather.temperature
|
return .single(.none)
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
return .single(.none)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(.none)
|
return .single(.none)
|
||||||
|
@ -20,6 +20,7 @@ import LottieComponent
|
|||||||
import LottieComponentResourceContent
|
import LottieComponentResourceContent
|
||||||
import StickerResources
|
import StickerResources
|
||||||
import AnimationCache
|
import AnimationCache
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
private let shadowImage: UIImage = {
|
private let shadowImage: UIImage = {
|
||||||
return UIImage(bundleImageName: "Stories/ReactionShadow")!
|
return UIImage(bundleImageName: "Stories/ReactionShadow")!
|
||||||
@ -224,12 +225,16 @@ public func storyPreviewWithAddedReactions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private protocol ItemView: UIView {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
final class StoryItemOverlaysView: UIView {
|
final class StoryItemOverlaysView: UIView {
|
||||||
static let counterFont: UIFont = {
|
static let counterFont: UIFont = {
|
||||||
return Font.with(size: 17.0, design: .camera, weight: .semibold, traits: .monospacedNumbers)
|
return Font.with(size: 17.0, design: .camera, weight: .semibold, traits: .monospacedNumbers)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private final class ItemView: HighlightTrackingButton {
|
private final class ReactionView: HighlightTrackingButton, ItemView {
|
||||||
private let shadowView: UIImageView
|
private let shadowView: UIImageView
|
||||||
private let coverView: UIImageView
|
private let coverView: UIImageView
|
||||||
|
|
||||||
@ -524,6 +529,120 @@ final class StoryItemOverlaysView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class WeatherView: UIView, ItemView {
|
||||||
|
private let backgroundView = UIView()
|
||||||
|
private let directStickerView = ComponentView<Empty>()
|
||||||
|
private let text = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var file: TelegramMediaFile?
|
||||||
|
private var textFont: UIFont?
|
||||||
|
|
||||||
|
private var customEmojiLoadDisposable: Disposable?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.backgroundView.clipsToBounds = true
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.backgroundView.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.customEmojiLoadDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(
|
||||||
|
context: AccountContext,
|
||||||
|
emoji: String,
|
||||||
|
emojiFile: TelegramMediaFile?,
|
||||||
|
temperature: Double,
|
||||||
|
flags: MediaArea.WeatherFlags,
|
||||||
|
synchronous: Bool,
|
||||||
|
size: CGSize,
|
||||||
|
cornerRadius: CGFloat,
|
||||||
|
isActive: Bool
|
||||||
|
) {
|
||||||
|
self.backgroundView.backgroundColor = flags.contains(.isDark) ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff)
|
||||||
|
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
||||||
|
self.backgroundView.layer.cornerRadius = cornerRadius
|
||||||
|
|
||||||
|
let itemSize = CGSize(width: floor(size.height * 0.71), height: floor(size.height * 0.71))
|
||||||
|
|
||||||
|
if self.file?.fileId != emojiFile?.fileId, let file = emojiFile {
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
self.customEmojiLoadDisposable?.dispose()
|
||||||
|
self.customEmojiLoadDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)).start()
|
||||||
|
|
||||||
|
let placeholderColor = flags.contains(.isDark) ? UIColor(white: 1.0, alpha: 0.1) : UIColor(white: 0.0, alpha: 0.1)
|
||||||
|
let _ = self.directStickerView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.ResourceContent(context: context, file: file, attemptSynchronously: synchronous, providesPlaceholder: true),
|
||||||
|
placeholderColor: placeholderColor,
|
||||||
|
renderingScale: 2.0,
|
||||||
|
loop: true
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: itemSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFont: UIFont
|
||||||
|
if let current = self.textFont {
|
||||||
|
textFont = current
|
||||||
|
} else {
|
||||||
|
textFont = Font.with(size: floorToScreenPixels(size.height * 0.69), design: .camera, weight: .semibold, traits: .monospacedNumbers)
|
||||||
|
self.textFont = textFont
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = NSMutableAttributedString(
|
||||||
|
string: stringForTemperature(temperature),
|
||||||
|
font: textFont,
|
||||||
|
textColor: flags.contains(.isDark) ? UIColor(rgb: 0xffffff) : UIColor(rgb: 0x000000)
|
||||||
|
)
|
||||||
|
string.addAttribute(.kern, value: -(size.height / 38.0) as NSNumber, range: NSMakeRange(0, string.length))
|
||||||
|
|
||||||
|
let textSize = self.text.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(text: .plain(string))
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: size
|
||||||
|
)
|
||||||
|
|
||||||
|
if let view = self.text.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: size.width - textSize.width - size.height * 0.2, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||||
|
let textTransition = ComponentTransition.immediate
|
||||||
|
textTransition.setFrame(view: view, frame: textFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let directStickerView = self.directStickerView.view as? LottieComponent.View {
|
||||||
|
if directStickerView.superview == nil {
|
||||||
|
self.addSubview(directStickerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
let stickerFrame = itemSize.centered(around: CGPoint(x: size.height * 0.5 + size.height * 0.058, y: size.height * 0.5))
|
||||||
|
|
||||||
|
let stickerTransition = ComponentTransition.immediate
|
||||||
|
stickerTransition.setPosition(view: directStickerView, position: stickerFrame.center)
|
||||||
|
stickerTransition.setBounds(view: directStickerView, bounds: CGRect(origin: CGPoint(), size: stickerFrame.size))
|
||||||
|
|
||||||
|
directStickerView.externalShouldPlay = isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var itemViews: [Int: ItemView] = [:]
|
private var itemViews: [Int: ItemView] = [:]
|
||||||
var activate: ((UIView, MessageReaction.Reaction) -> Void)?
|
var activate: ((UIView, MessageReaction.Reaction) -> Void)?
|
||||||
var requestUpdate: (() -> Void)?
|
var requestUpdate: (() -> Void)?
|
||||||
@ -561,25 +680,37 @@ final class StoryItemOverlaysView: UIView {
|
|||||||
isActive: Bool,
|
isActive: Bool,
|
||||||
transition: ComponentTransition
|
transition: ComponentTransition
|
||||||
) {
|
) {
|
||||||
|
func getFrameAndRotation(coordinates: MediaArea.Coordinates, scale: CGFloat = 1.0) -> (frame: CGRect, rotation: CGFloat, cornerRadius: CGFloat)? {
|
||||||
|
let referenceSize = size
|
||||||
|
var areaSize = CGSize(width: coordinates.width / 100.0 * referenceSize.width, height: coordinates.height / 100.0 * referenceSize.height)
|
||||||
|
areaSize.width *= scale
|
||||||
|
areaSize.height *= scale
|
||||||
|
let targetFrame = CGRect(x: coordinates.x / 100.0 * referenceSize.width - areaSize.width * 0.5, y: coordinates.y / 100.0 * referenceSize.height - areaSize.height * 0.5, width: areaSize.width, height: areaSize.height)
|
||||||
|
if targetFrame.width < 5.0 || targetFrame.height < 5.0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var cornerRadius: CGFloat = 0.0
|
||||||
|
if let radius = coordinates.cornerRadius {
|
||||||
|
cornerRadius = radius / 100.0 * areaSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
return (targetFrame, coordinates.rotation * (CGFloat.pi / 180.0), cornerRadius)
|
||||||
|
}
|
||||||
|
|
||||||
var nextId = 0
|
var nextId = 0
|
||||||
for mediaArea in story.mediaAreas {
|
for mediaArea in story.mediaAreas {
|
||||||
switch mediaArea {
|
switch mediaArea {
|
||||||
case let .reaction(coordinates, reaction, flags):
|
case let .reaction(coordinates, reaction, flags):
|
||||||
let referenceSize = size
|
guard let (itemFrame, itemRotation, _) = getFrameAndRotation(coordinates: coordinates, scale: 0.97) else {
|
||||||
var areaSize = CGSize(width: coordinates.width / 100.0 * referenceSize.width, height: coordinates.height / 100.0 * referenceSize.height)
|
|
||||||
areaSize.width *= 0.97
|
|
||||||
areaSize.height *= 0.97
|
|
||||||
let targetFrame = CGRect(x: coordinates.x / 100.0 * referenceSize.width - areaSize.width * 0.5, y: coordinates.y / 100.0 * referenceSize.height - areaSize.height * 0.5, width: areaSize.width, height: areaSize.height)
|
|
||||||
if targetFrame.width < 5.0 || targetFrame.height < 5.0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemView: ItemView
|
let itemView: ReactionView
|
||||||
let itemId = nextId
|
let itemId = nextId
|
||||||
if let current = self.itemViews[itemId] {
|
if let current = self.itemViews[itemId] as? ReactionView {
|
||||||
itemView = current
|
itemView = current
|
||||||
} else {
|
} else {
|
||||||
itemView = ItemView(frame: CGRect())
|
itemView = ReactionView(frame: CGRect())
|
||||||
itemView.activate = { [weak self] view, reaction in
|
itemView.activate = { [weak self] view, reaction in
|
||||||
self?.activate?(view, reaction)
|
self?.activate?(view, reaction)
|
||||||
}
|
}
|
||||||
@ -590,9 +721,9 @@ final class StoryItemOverlaysView: UIView {
|
|||||||
self.addSubview(itemView)
|
self.addSubview(itemView)
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setPosition(view: itemView, position: targetFrame.center)
|
transition.setPosition(view: itemView, position: itemFrame.center)
|
||||||
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: targetFrame.size))
|
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||||
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(coordinates.rotation * (CGFloat.pi / 180.0), 0.0, 0.0, 1.0))
|
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(itemRotation, 0.0, 0.0, 1.0))
|
||||||
|
|
||||||
var counter = 0
|
var counter = 0
|
||||||
if let reactionData = story.views?.reactions.first(where: { $0.value == reaction }) {
|
if let reactionData = story.views?.reactions.first(where: { $0.value == reaction }) {
|
||||||
@ -607,7 +738,39 @@ final class StoryItemOverlaysView: UIView {
|
|||||||
availableReactions: availableReactions,
|
availableReactions: availableReactions,
|
||||||
entityFiles: entityFiles,
|
entityFiles: entityFiles,
|
||||||
synchronous: attemptSynchronous,
|
synchronous: attemptSynchronous,
|
||||||
size: targetFrame.size,
|
size: itemFrame.size,
|
||||||
|
isActive: isActive
|
||||||
|
)
|
||||||
|
|
||||||
|
nextId += 1
|
||||||
|
case let .weather(coordinates, emoji, temperature, flags):
|
||||||
|
guard let (itemFrame, itemRotation, cornerRadius) = getFrameAndRotation(coordinates: coordinates) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemView: WeatherView
|
||||||
|
let itemId = nextId
|
||||||
|
if let current = self.itemViews[itemId] as? WeatherView {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
itemView = WeatherView(frame: CGRect())
|
||||||
|
self.itemViews[itemId] = itemView
|
||||||
|
self.addSubview(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setPosition(view: itemView, position: itemFrame.center)
|
||||||
|
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||||
|
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(itemRotation, 0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
itemView.update(
|
||||||
|
context: context,
|
||||||
|
emoji: emoji,
|
||||||
|
emojiFile: context.animatedEmojiStickersValue[emoji]?.first?.file,
|
||||||
|
temperature: temperature,
|
||||||
|
flags: flags,
|
||||||
|
synchronous: attemptSynchronous,
|
||||||
|
size: itemFrame.size,
|
||||||
|
cornerRadius: cornerRadius,
|
||||||
isActive: isActive
|
isActive: isActive
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user