mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit 'f4cc9d1aad'
This commit is contained in:
commit
4ee31cd176
26 changed files with 552 additions and 389 deletions
|
|
@ -492,14 +492,22 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
||||
self.currentError = error
|
||||
if (error as NSError).code != -999 {
|
||||
self.currentError = error
|
||||
} else {
|
||||
self.currentError = nil
|
||||
}
|
||||
if let (size, insets) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
||||
self.currentError = error
|
||||
if (error as NSError).code != -999 {
|
||||
self.currentError = error
|
||||
} else {
|
||||
self.currentError = nil
|
||||
}
|
||||
if let (size, insets) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, transition: .immediate)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ public enum ChatOpenWebViewSource: Equatable {
|
|||
case generic
|
||||
case menu
|
||||
case inline(bot: EnginePeer)
|
||||
case webApp(botApp: BotApp)
|
||||
}
|
||||
|
||||
public final class ChatPanelInterfaceInteraction {
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes
|
|||
let transition: ContainedViewLayoutTransition
|
||||
let dismissProgress: CGFloat
|
||||
if (velocity.y < -0.5 || progress >= 0.5) && self.checkInteractiveDismissWithControllers() {
|
||||
if self.isDraggingHeader, let controller = self.container.controllers.last as? MinimizableController {
|
||||
if let controller = self.container.controllers.last as? MinimizableController, self.isDraggingHeader || "".isEmpty {
|
||||
dismissProgress = 0.0
|
||||
targetOffset = 0.0
|
||||
transition = .immediate
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import AnimatedStickerNode
|
|||
import TelegramAnimatedStickerNode
|
||||
import StickerResources
|
||||
import MediaEditor
|
||||
import TelegramStringFormatting
|
||||
|
||||
private func generateIcon(style: DrawingWeatherEntity.Style) -> UIImage? {
|
||||
guard let image = UIImage(bundleImageName: "Chat/Attach Menu/Location") else {
|
||||
|
|
@ -50,7 +51,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
}
|
||||
|
||||
let backgroundView: UIView
|
||||
let blurredBackgroundView: BlurredBackgroundView
|
||||
|
||||
let textView: DrawingTextView
|
||||
let iconView: UIImageView
|
||||
|
|
@ -61,13 +61,14 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
private let cachedDisposable = MetaDisposable()
|
||||
|
||||
let temperature: String
|
||||
|
||||
init(context: AccountContext, entity: DrawingWeatherEntity) {
|
||||
self.temperature = stringForTemperature(entity.temperature)
|
||||
|
||||
self.backgroundView = UIView()
|
||||
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.clipsToBounds = false
|
||||
|
||||
|
|
@ -95,7 +96,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
|
||||
self.textView.delegate = self
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addSubview(self.blurredBackgroundView)
|
||||
self.addSubview(self.textView)
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -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.backgroundView.frame = self.bounds
|
||||
self.blurredBackgroundView.frame = self.bounds
|
||||
self.blurredBackgroundView.update(size: self.bounds.size, transition: .immediate)
|
||||
}
|
||||
|
||||
override func selectedTapAction() -> Bool {
|
||||
|
|
@ -161,16 +159,6 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
case .white:
|
||||
updatedStyle = .black
|
||||
case .black:
|
||||
updatedStyle = .transparent
|
||||
case .transparent:
|
||||
if self.weatherEntity.hasCustomColor {
|
||||
updatedStyle = .custom
|
||||
} else {
|
||||
updatedStyle = .white
|
||||
}
|
||||
case .custom:
|
||||
updatedStyle = .white
|
||||
case .blur:
|
||||
updatedStyle = .white
|
||||
}
|
||||
self.weatherEntity.style = updatedStyle
|
||||
|
|
@ -182,7 +170,7 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
|
||||
private var displayFontSize: CGFloat {
|
||||
var textFontSize: CGFloat = 0.07
|
||||
let textLength = self.weatherEntity.temperature.count
|
||||
let textLength = self.temperature.count
|
||||
if textLength > 10 {
|
||||
textFontSize = max(0.01, 0.07 - CGFloat(textLength - 10) / 100.0)
|
||||
}
|
||||
|
|
@ -194,7 +182,7 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
}
|
||||
|
||||
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 fontSize = self.displayFontSize
|
||||
|
||||
|
|
@ -213,15 +201,8 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
switch self.weatherEntity.style {
|
||||
case .white:
|
||||
textColor = .black
|
||||
case .black, .transparent, .blur:
|
||||
case .black:
|
||||
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)
|
||||
|
|
@ -241,34 +222,10 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
self.textView.textColor = .black
|
||||
self.backgroundView.backgroundColor = .white
|
||||
self.backgroundView.isHidden = false
|
||||
self.blurredBackgroundView.isHidden = true
|
||||
case .black:
|
||||
self.textView.textColor = .white
|
||||
self.backgroundView.backgroundColor = .black
|
||||
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
|
||||
|
||||
|
|
@ -282,10 +239,8 @@ public final class DrawingWeatherEntityView: DrawingEntityView, UITextViewDelega
|
|||
}
|
||||
|
||||
self.backgroundView.layer.cornerRadius = self.textSize.height * 0.2
|
||||
self.blurredBackgroundView.layer.cornerRadius = self.backgroundView.layer.cornerRadius
|
||||
if #available(iOS 13.0, *) {
|
||||
self.backgroundView.layer.cornerCurve = .continuous
|
||||
self.blurredBackgroundView.layer.cornerCurve = .continuous
|
||||
}
|
||||
|
||||
super.update(animated: animated)
|
||||
|
|
|
|||
|
|
@ -1556,11 +1556,13 @@ private func monetizationEntries(
|
|||
entries.append(.adsProceedsTitle(presentationData.theme, presentationData.strings.Monetization_StarsProceeds_Title))
|
||||
entries.append(.adsProceedsOverview(presentationData.theme, canViewRevenue ? data : nil, canViewStarsRevenue ? starsData : nil))
|
||||
|
||||
let hasTonBalance = data.balances.overallRevenue > 0
|
||||
let hasStarsBalance = (starsData?.balances.overallRevenue ?? 0) > 0
|
||||
|
||||
let proceedsInfo: String
|
||||
if canViewStarsRevenue && canViewRevenue {
|
||||
if (canViewStarsRevenue && hasStarsBalance) && (canViewRevenue && hasTonBalance) {
|
||||
proceedsInfo = presentationData.strings.Monetization_Proceeds_TonAndStars_Info
|
||||
} else if canViewStarsRevenue {
|
||||
} else if canViewStarsRevenue && hasStarsBalance {
|
||||
proceedsInfo = presentationData.strings.Monetization_Proceeds_Stars_Info
|
||||
} else {
|
||||
proceedsInfo = presentationData.strings.Monetization_Proceeds_Ton_Info
|
||||
|
|
|
|||
|
|
@ -520,6 +520,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) }
|
||||
dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) }
|
||||
dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
|
||||
dict[1132918857] = { return Api.MediaArea.parse_mediaAreaWeather($0) }
|
||||
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
||||
dict[-1808510398] = { return Api.Message.parse_message($0) }
|
||||
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ public extension Api {
|
|||
case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction)
|
||||
case mediaAreaUrl(coordinates: Api.MediaAreaCoordinates, url: String)
|
||||
case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
|
||||
case mediaAreaWeather(flags: Int32, coordinates: Api.MediaAreaCoordinates, emoji: String, temperatureC: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
|
|
@ -294,6 +295,15 @@ public extension Api {
|
|||
serializeString(venueId, buffer: buffer, boxed: false)
|
||||
serializeString(venueType, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .mediaAreaWeather(let flags, let coordinates, let emoji, let temperatureC):
|
||||
if boxed {
|
||||
buffer.appendInt32(1132918857)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
coordinates.serialize(buffer, true)
|
||||
serializeString(emoji, buffer: buffer, boxed: false)
|
||||
serializeDouble(temperatureC, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,6 +323,8 @@ public extension Api {
|
|||
return ("mediaAreaUrl", [("coordinates", coordinates as Any), ("url", url as Any)])
|
||||
case .mediaAreaVenue(let coordinates, let geo, let title, let address, let provider, let venueId, let venueType):
|
||||
return ("mediaAreaVenue", [("coordinates", coordinates as Any), ("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)])
|
||||
case .mediaAreaWeather(let flags, let coordinates, let emoji, let temperatureC):
|
||||
return ("mediaAreaWeather", [("flags", flags as Any), ("coordinates", coordinates as Any), ("emoji", emoji as Any), ("temperatureC", temperatureC as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -471,6 +483,28 @@ public extension Api {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_mediaAreaWeather(_ reader: BufferReader) -> MediaArea? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.MediaAreaCoordinates?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates
|
||||
}
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Double?
|
||||
_4 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.MediaArea.mediaAreaWeather(flags: _1!, coordinates: _2!, emoji: _3!, temperatureC: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -522,6 +522,12 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
|
|||
return .link(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), url: url)
|
||||
case let .mediaAreaChannelPost(coordinates, channelId, messageId):
|
||||
return .channelMessage(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), messageId: EngineMessage.Id(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId))
|
||||
case let .mediaAreaWeather(flags, coordinates, emoji, temperatureC):
|
||||
var parsedFlags = MediaArea.WeatherFlags()
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
parsedFlags.insert(.isDark)
|
||||
}
|
||||
return .weather(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), emoji: emoji, temperature: temperatureC, flags: parsedFlags)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -574,6 +580,12 @@ func apiMediaAreasFromMediaAreas(_ mediaAreas: [MediaArea], transaction: Transac
|
|||
}
|
||||
case let .link(_, url):
|
||||
apiMediaAreas.append(.mediaAreaUrl(coordinates: inputCoordinates, url: url))
|
||||
case let .weather(_, emoji, temperature, flags):
|
||||
var apiFlags: Int32 = 0
|
||||
if flags.contains(.isDark) {
|
||||
apiFlags |= (1 << 0)
|
||||
}
|
||||
apiMediaAreas.append(.mediaAreaWeather(flags: apiFlags, coordinates: inputCoordinates, emoji: emoji, temperatureC: temperature))
|
||||
}
|
||||
}
|
||||
return apiMediaAreas
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public enum MediaArea: Codable, Equatable {
|
|||
case coordinates
|
||||
case value
|
||||
case flags
|
||||
case temperature
|
||||
}
|
||||
|
||||
public struct Coordinates: Codable, Equatable {
|
||||
|
|
@ -149,6 +150,7 @@ public enum MediaArea: Codable, Equatable {
|
|||
case reaction(coordinates: Coordinates, reaction: MessageReaction.Reaction, flags: ReactionFlags)
|
||||
case channelMessage(coordinates: Coordinates, messageId: EngineMessage.Id)
|
||||
case link(coordinates: Coordinates, url: String)
|
||||
case weather(coordinates: Coordinates, emoji: String, temperature: Double, flags: WeatherFlags)
|
||||
|
||||
public struct ReactionFlags: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
|
@ -164,6 +166,20 @@ public enum MediaArea: Codable, Equatable {
|
|||
public static let isDark = ReactionFlags(rawValue: 1 << 0)
|
||||
public static let isFlipped = ReactionFlags(rawValue: 1 << 1)
|
||||
}
|
||||
|
||||
public struct WeatherFlags: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.rawValue = 0
|
||||
}
|
||||
|
||||
public static let isDark = WeatherFlags(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
|
||||
private enum MediaAreaType: Int32 {
|
||||
|
|
@ -171,6 +187,7 @@ public enum MediaArea: Codable, Equatable {
|
|||
case reaction
|
||||
case channelMessage
|
||||
case link
|
||||
case weather
|
||||
}
|
||||
|
||||
public enum DecodingError: Error {
|
||||
|
|
@ -201,6 +218,12 @@ public enum MediaArea: Codable, Equatable {
|
|||
let coordinates = try container.decode(MediaArea.Coordinates.self, forKey: .coordinates)
|
||||
let url = try container.decode(String.self, forKey: .value)
|
||||
self = .link(coordinates: coordinates, url: url)
|
||||
case .weather:
|
||||
let coordinates = try container.decode(MediaArea.Coordinates.self, forKey: .coordinates)
|
||||
let emoji = try container.decode(String.self, forKey: .value)
|
||||
let temperature = try container.decode(Double.self, forKey: .temperature)
|
||||
let flags = WeatherFlags(rawValue: try container.decodeIfPresent(Int32.self, forKey: .flags) ?? 0)
|
||||
self = .weather(coordinates: coordinates, emoji: emoji, temperature: temperature, flags: flags)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,6 +248,12 @@ public enum MediaArea: Codable, Equatable {
|
|||
try container.encode(MediaAreaType.link.rawValue, forKey: .type)
|
||||
try container.encode(coordinates, forKey: .coordinates)
|
||||
try container.encode(url, forKey: .value)
|
||||
case let .weather(coordinates, emoji, temperature, flags):
|
||||
try container.encode(MediaAreaType.weather.rawValue, forKey: .type)
|
||||
try container.encode(coordinates, forKey: .coordinates)
|
||||
try container.encode(emoji, forKey: .value)
|
||||
try container.encode(temperature, forKey: .temperature)
|
||||
try container.encode(flags.rawValue, forKey: .flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +269,8 @@ public extension MediaArea {
|
|||
return coordinates
|
||||
case let .link(coordinates, _):
|
||||
return coordinates
|
||||
case let .weather(coordinates, _, _, _):
|
||||
return coordinates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import Foundation
|
||||
|
||||
private enum TemperatureUnit {
|
||||
case celsius
|
||||
case fahrenheit
|
||||
|
||||
var suffix: String {
|
||||
switch self {
|
||||
case .celsius:
|
||||
return "°C"
|
||||
case .fahrenheit:
|
||||
return "°F"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var cachedTemperatureUnit: TemperatureUnit?
|
||||
private func currentTemperatureUnit() -> TemperatureUnit {
|
||||
if let cachedTemperatureUnit {
|
||||
return cachedTemperatureUnit
|
||||
}
|
||||
let temperatureFormatter = MeasurementFormatter()
|
||||
temperatureFormatter.locale = Locale.current
|
||||
|
||||
let fahrenheitMeasurement = Measurement(value: 0, unit: UnitTemperature.fahrenheit)
|
||||
let fahrenheitString = temperatureFormatter.string(from: fahrenheitMeasurement)
|
||||
|
||||
var temperatureUnit: TemperatureUnit = .celsius
|
||||
if fahrenheitString.contains("F") || fahrenheitString.contains("Fahrenheit") {
|
||||
temperatureUnit = .fahrenheit
|
||||
}
|
||||
cachedTemperatureUnit = temperatureUnit
|
||||
return temperatureUnit
|
||||
}
|
||||
|
||||
public func stringForTemperature(_ value: Double) -> String {
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.unitStyle = .short
|
||||
formatter.numberFormatter.maximumFractionDigits = 0
|
||||
formatter.unitOptions = .temperatureWithoutUnit
|
||||
let valueString = formatter.string(from: Measurement(value: value, unit: UnitTemperature.celsius)).trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,.").inverted)
|
||||
return valueString + currentTemperatureUnit().suffix
|
||||
}
|
||||
|
|
@ -120,7 +120,7 @@ public enum CodableDrawingEntity: Equatable {
|
|||
rotation = entity.rotation
|
||||
scale = entity.scale
|
||||
if let size {
|
||||
cornerRadius = 10.0 / (size.width * entity.scale)
|
||||
cornerRadius = (size.height * 0.17) / size.width
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
|
|
@ -191,6 +191,17 @@ public enum CodableDrawingEntity: Equatable {
|
|||
coordinates: coordinates,
|
||||
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:
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
case uuid
|
||||
case style
|
||||
case color
|
||||
case hasCustomColor
|
||||
case emoji
|
||||
case temperature
|
||||
case icon
|
||||
case referenceDrawingSize
|
||||
|
|
@ -25,9 +25,6 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
public enum Style: Codable, Equatable {
|
||||
case white
|
||||
case black
|
||||
case transparent
|
||||
case custom
|
||||
case blur
|
||||
}
|
||||
|
||||
public var uuid: UUID
|
||||
|
|
@ -37,20 +34,11 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
|
||||
|
||||
public var style: Style
|
||||
public var temperature: String
|
||||
public var icon: TelegramMediaFile?
|
||||
public var color: DrawingColor = DrawingColor(color: .white) {
|
||||
didSet {
|
||||
if self.color.toUIColor().argb == UIColor.white.argb {
|
||||
self.style = .white
|
||||
self.hasCustomColor = false
|
||||
} else {
|
||||
self.style = .custom
|
||||
self.hasCustomColor = true
|
||||
}
|
||||
}
|
||||
}
|
||||
public var hasCustomColor = false
|
||||
public var emoji: String
|
||||
public var temperature: Double
|
||||
|
||||
public var color: DrawingColor = DrawingColor.clear
|
||||
public var lineWidth: CGFloat = 0.0
|
||||
|
||||
public var referenceDrawingSize: CGSize
|
||||
|
|
@ -74,13 +62,14 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
return false
|
||||
}
|
||||
|
||||
public init(temperature: String, style: Style, icon: TelegramMediaFile?) {
|
||||
public init(emoji: String, emojiFile: TelegramMediaFile?, temperature: Double, style: Style) {
|
||||
self.uuid = UUID()
|
||||
|
||||
self.emoji = emoji
|
||||
self.icon = emojiFile
|
||||
self.temperature = temperature
|
||||
self.style = style
|
||||
self.icon = icon
|
||||
|
||||
|
||||
self.referenceDrawingSize = .zero
|
||||
self.position = .zero
|
||||
self.width = 100.0
|
||||
|
|
@ -91,10 +80,9 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
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.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) {
|
||||
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 {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
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.style, forKey: .style)
|
||||
try container.encode(self.color, forKey: .color)
|
||||
try container.encode(self.hasCustomColor, forKey: .hasCustomColor)
|
||||
|
||||
var encoder = PostboxEncoder()
|
||||
if let icon = self.icon {
|
||||
|
|
@ -137,7 +124,7 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
}
|
||||
|
||||
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 {
|
||||
newEntity.uuid = self.uuid
|
||||
}
|
||||
|
|
@ -156,6 +143,9 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable {
|
|||
if self.uuid != other.uuid {
|
||||
return false
|
||||
}
|
||||
if self.emoji != other.emoji {
|
||||
return false
|
||||
}
|
||||
if self.temperature != other.temperature {
|
||||
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] {
|
||||
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 {
|
||||
return []
|
||||
} else {
|
||||
|
|
@ -126,10 +128,10 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti
|
|||
return entities
|
||||
} else if let entity = entity as? DrawingLocationEntity {
|
||||
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 {
|
||||
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 []
|
||||
|
|
|
|||
|
|
@ -4583,16 +4583,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
}
|
||||
|
||||
func addWeather(_ weather: StickerPickerScreen.Weather.LoadedWeather) {
|
||||
let weatherFormatter = MeasurementFormatter()
|
||||
weatherFormatter.locale = Locale.current
|
||||
weatherFormatter.unitStyle = .short
|
||||
weatherFormatter.numberFormatter.maximumFractionDigits = 0
|
||||
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(
|
||||
DrawingWeatherEntity(
|
||||
temperature: weatherFormatter.string(from: Measurement(value: weather.temperature, unit: UnitTemperature.celsius)),
|
||||
style: .white,
|
||||
icon: weather.emojiFile
|
||||
emoji: weather.emoji,
|
||||
emojiFile: weather.emojiFile,
|
||||
temperature: weather.temperature,
|
||||
style: .white
|
||||
),
|
||||
scale: nil,
|
||||
position: nil
|
||||
|
|
@ -6066,7 +6074,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
})
|
||||
self.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
|
||||
func maybePresentDiscardAlert() {
|
||||
self.hapticFeedback.impact(.light)
|
||||
if !self.isEligibleForDraft() {
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
import Foundation
|
||||
import CoreLocation
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import StickerPickerScreen
|
||||
import AccountContext
|
||||
import DeviceLocationManager
|
||||
|
||||
func emojiFor(for meteocode: Int, date: Date, location: CLLocationCoordinate2D) -> String? {
|
||||
var emoji = weatherEmoji(for: meteocode)
|
||||
if ["☀️", "🌤️"].contains(emoji) && isNightTime(date: date, location: location) && !"".isEmpty {
|
||||
emoji = moonPhaseEmoji(for: date)
|
||||
}
|
||||
return emoji
|
||||
}
|
||||
|
||||
private func moonPhaseEmoji(for date: Date) -> String {
|
||||
let newMoonDate = Date(timeIntervalSince1970: 1612137600)
|
||||
let lunarMonth: TimeInterval = 29.53058867 * 24 * 60 * 60
|
||||
|
||||
let daysSinceNewMoon = date.timeIntervalSince(newMoonDate) / (24 * 60 * 60)
|
||||
let currentMoonPhase = daysSinceNewMoon.truncatingRemainder(dividingBy: lunarMonth) / lunarMonth
|
||||
|
||||
switch currentMoonPhase {
|
||||
case 0..<0.03:
|
||||
return "🌑"
|
||||
case 0.03..<0.22:
|
||||
return "🌒"
|
||||
case 0.22..<0.28:
|
||||
return "🌓"
|
||||
case 0.28..<0.47:
|
||||
return "🌔"
|
||||
case 0.47..<0.53:
|
||||
return "🌕"
|
||||
case 0.53..<0.72:
|
||||
return "🌖"
|
||||
case 0.72..<0.78:
|
||||
return "🌗"
|
||||
case 0.78..<0.97:
|
||||
return "🌘"
|
||||
default:
|
||||
return "🌑"
|
||||
}
|
||||
}
|
||||
|
||||
func weatherEmoji(for meteocode: Int) -> String? {
|
||||
switch meteocode {
|
||||
case 0:
|
||||
return "☀️"
|
||||
case 1, 2, 3:
|
||||
return "🌤️"
|
||||
case 45, 48:
|
||||
return "🌫️"
|
||||
case 51, 53, 55:
|
||||
return "🌧️" // Drizzle: Light, moderate, and dense intensity
|
||||
case 56, 57:
|
||||
return "🌧️" // Freezing Drizzle: Light and dense intensity
|
||||
case 61, 63, 65:
|
||||
return "🌧️" // Rain: Slight, moderate, and heavy intensity
|
||||
case 66, 67:
|
||||
return "🌧️" // Freezing Rain: Light and heavy intensity
|
||||
case 71, 73, 75:
|
||||
return "🌨️" // Snow fall: Slight, moderate, and heavy intensity
|
||||
case 77:
|
||||
return "🌨️" // Snow grains
|
||||
case 80, 81, 82:
|
||||
return "🌦️" // Rain showers: Slight, moderate, and violent
|
||||
case 85, 86:
|
||||
return "🌨️"
|
||||
case 95, 96, 99:
|
||||
return "⛈️" // Thunderstorm: Slight or moderate
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct StoryWeather {
|
||||
let emoji: String
|
||||
let temperature: Double
|
||||
}
|
||||
|
||||
private func getWeatherData(location: CLLocationCoordinate2D) -> Signal<StoryWeather?, NoError> {
|
||||
let latitude = "\(location.latitude)"
|
||||
let longitude = "\(location.longitude)"
|
||||
let url = "https://api.open-meteo.com/v1/forecast?latitude=\(latitude)&longitude=\(longitude)¤t=temperature_2m,weather_code"
|
||||
|
||||
return Signal { subscriber in
|
||||
let disposable = fetchHttpResource(url: url).start(next: { result in
|
||||
if case let .dataPart(_, data, _, complete) = result, complete {
|
||||
guard let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
guard let current = dict["current"] as? [String: Any], let temperature = current["temperature_2m"] as? Double, let weatherCode = current["weather_code"] as? Int else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
if let emoji = emojiFor(for: weatherCode, date: Date(), location: location) {
|
||||
subscriber.putNext(StoryWeather(emoji: emoji, temperature: temperature))
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
func getWeather(context: AccountContext) -> Signal<StickerPickerScreen.Weather, NoError> {
|
||||
guard let locationManager = context.sharedContext.locationManager else {
|
||||
return .single(.none)
|
||||
}
|
||||
return .single(.fetching)
|
||||
|> then(
|
||||
currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0)
|
||||
|> mapToSignal { location in
|
||||
if let location {
|
||||
return getWeatherData(location: location)
|
||||
|> mapToSignal { weather in
|
||||
if let weather {
|
||||
return context.animatedEmojiStickers
|
||||
|> take(1)
|
||||
|> mapToSignal { result in
|
||||
if let match = result[weather.emoji.strippedEmoji]?.first {
|
||||
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
|
||||
emoji: weather.emoji.strippedEmoji,
|
||||
emojiFile: match.file,
|
||||
temperature: weather.temperature
|
||||
)))
|
||||
} else {
|
||||
return .single(.none)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.none)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.none)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func calculateSunriseSunset(date: Date, location: CLLocationCoordinate2D) -> (sunrise: Date, sunset: Date)? {
|
||||
guard let utcTimezone = TimeZone(identifier: "UTC") else { return nil }
|
||||
|
||||
let zenith: Double = 90.83
|
||||
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
calendar.timeZone = utcTimezone
|
||||
|
||||
guard let dayOfYear = calendar.ordinality(of: .day, in: .year, for: date) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
func toRadians(_ degrees: Double) -> Double {
|
||||
return degrees * .pi / 180.0
|
||||
}
|
||||
|
||||
func toDegrees(_ radians: Double) -> Double {
|
||||
return radians * 180.0 / .pi
|
||||
}
|
||||
|
||||
func normalise(_ value: Double, maximum: Double) -> Double {
|
||||
var value = value
|
||||
if value < 0 {
|
||||
value += maximum
|
||||
}
|
||||
if value > maximum {
|
||||
value -= maximum
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func calculateTime(isSunrise: Bool) -> Date? {
|
||||
let day = Double(dayOfYear)
|
||||
let lngHour = location.longitude / 15.0
|
||||
|
||||
let hourTime: Double = isSunrise ? 6 : 18
|
||||
let t = day + ((hourTime - lngHour) / 24)
|
||||
|
||||
let M = (0.9856 * t) - 3.289
|
||||
|
||||
var L = M + 1.916 * sin(toRadians(M)) + 0.020 * sin(2 * toRadians(M)) + 282.634
|
||||
L = normalise(L, maximum: 360)
|
||||
|
||||
var RA = toDegrees(atan(0.91764 * tan(toRadians(L))))
|
||||
RA = normalise(RA, maximum: 360)
|
||||
|
||||
let Lquadrant = floor(L / 90) * 90
|
||||
let RAquadrant = floor(RA / 90) * 90
|
||||
RA = RA + (Lquadrant - RAquadrant)
|
||||
RA = RA / 15
|
||||
|
||||
let sinDec = 0.39782 * sin(toRadians(L))
|
||||
let cosDec = cos(asin(sinDec))
|
||||
let cosH = (cos(toRadians(zenith)) - (sinDec * sin(toRadians(location.latitude)))) / (cosDec * cos(toRadians(location.latitude)))
|
||||
guard cosH < 1 else {
|
||||
return nil
|
||||
}
|
||||
guard cosH > -1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let tempH = isSunrise ? 360.0 - toDegrees(acos(cosH)) : toDegrees(acos(cosH))
|
||||
let H = tempH / 15.0
|
||||
let T = H + RA - (0.06571 * t) - 6.622
|
||||
|
||||
var UT = T - lngHour
|
||||
UT = normalise(UT, maximum: 24)
|
||||
|
||||
let hour = floor(UT)
|
||||
let minute = floor((UT - hour) * 60.0)
|
||||
let second = (((UT - hour) * 60) - minute) * 60.0
|
||||
|
||||
let shouldBeYesterday = lngHour > 0 && UT > 12 && isSunrise
|
||||
let shouldBeTomorrow = lngHour < 0 && UT < 12 && !isSunrise
|
||||
|
||||
let setDate: Date
|
||||
if shouldBeYesterday {
|
||||
setDate = Date(timeInterval: -(60 * 60 * 24), since: date)
|
||||
} else if shouldBeTomorrow {
|
||||
setDate = Date(timeInterval: (60 * 60 * 24), since: date)
|
||||
} else {
|
||||
setDate = date
|
||||
}
|
||||
|
||||
var components = calendar.dateComponents([.day, .month, .year], from: setDate)
|
||||
components.hour = Int(hour)
|
||||
components.minute = Int(minute)
|
||||
components.second = Int(second)
|
||||
|
||||
calendar.timeZone = utcTimezone
|
||||
return calendar.date(from: components)
|
||||
}
|
||||
|
||||
guard let sunrise = calculateTime(isSunrise: true),
|
||||
let sunset = calculateTime(isSunrise: false) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (sunrise, sunset)
|
||||
}
|
||||
|
||||
private func isNightTime(date: Date, location: CLLocationCoordinate2D) -> Bool {
|
||||
let calendar = Calendar.current
|
||||
let date = calendar.startOfDay(for: date)
|
||||
guard let (sunrise, sunset) = calculateSunriseSunset(date: date, location: location) else {
|
||||
return false
|
||||
}
|
||||
return date < sunrise || date > sunset
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import Foundation
|
||||
import CoreLocation
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import StickerPickerScreen
|
||||
import AccountContext
|
||||
import DeviceLocationManager
|
||||
|
||||
struct StoryWeather {
|
||||
let emoji: String
|
||||
let temperature: Double
|
||||
}
|
||||
|
||||
private func getWeatherData(context: AccountContext, location: CLLocationCoordinate2D) -> Signal<StoryWeather?, NoError> {
|
||||
let appConfiguration = context.currentAppConfiguration.with { $0 }
|
||||
let botConfiguration = WeatherBotConfiguration.with(appConfiguration: appConfiguration)
|
||||
|
||||
if let botUsername = botConfiguration.botName {
|
||||
return context.engine.peers.resolvePeerByName(name: botUsername)
|
||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||
guard case let .result(result) = result else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(result)
|
||||
}
|
||||
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
|
||||
guard let peer = peer else {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: "", location: .single((location.latitude, location.longitude)), offset: "")
|
||||
|> map { results -> ChatContextResultCollection? in
|
||||
return results?.results
|
||||
}
|
||||
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> map { contextResult -> StoryWeather? in
|
||||
guard let contextResult, let result = contextResult.results.first, let emoji = result.title, let temperature = result.description.flatMap(Double.init) else {
|
||||
return nil
|
||||
}
|
||||
return StoryWeather(emoji: emoji, temperature: temperature)
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func getWeather(context: AccountContext) -> Signal<StickerPickerScreen.Weather, NoError> {
|
||||
guard let locationManager = context.sharedContext.locationManager else {
|
||||
return .single(.none)
|
||||
}
|
||||
return .single(.fetching)
|
||||
|> then(
|
||||
currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0)
|
||||
|> mapToSignal { location in
|
||||
if let location {
|
||||
return getWeatherData(context: context, location: location)
|
||||
|> mapToSignal { weather in
|
||||
if let weather {
|
||||
if let match = context.animatedEmojiStickersValue[weather.emoji.strippedEmoji]?.first {
|
||||
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
|
||||
emoji: weather.emoji.strippedEmoji,
|
||||
emojiFile: match.file,
|
||||
temperature: weather.temperature
|
||||
)))
|
||||
} else {
|
||||
return .single(.none)
|
||||
}
|
||||
} else {
|
||||
return .single(.none)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.none)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private struct WeatherBotConfiguration {
|
||||
static var defaultValue: WeatherBotConfiguration {
|
||||
return WeatherBotConfiguration(botName: "izweatherbot")
|
||||
}
|
||||
|
||||
let botName: String?
|
||||
|
||||
fileprivate init(botName: String?) {
|
||||
self.botName = botName
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> WeatherBotConfiguration {
|
||||
if let data = appConfiguration.data, let botName = data["weather_search_username"] as? String {
|
||||
return WeatherBotConfiguration(botName: botName)
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -815,12 +815,14 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
|||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .image(
|
||||
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
||||
title: presentationData.strings.Stars_Intro_PurchasedTitle,
|
||||
content: .universal(
|
||||
animation: "StarsBuy",
|
||||
scale: 0.066,
|
||||
colors: [:],
|
||||
title: presentationData.strings.Stars_Intro_PurchasedTitle,
|
||||
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string,
|
||||
round: false,
|
||||
undoText: nil
|
||||
customUndoText: nil,
|
||||
timeout: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return true})
|
||||
|
|
@ -850,8 +852,42 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
|||
starsContext: starsContext,
|
||||
options: options,
|
||||
purpose: .gift(peerId: peerId),
|
||||
completion: { stars in
|
||||
|
||||
completion: { [weak self] stars in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(2.0) {
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .universal(
|
||||
animation: "StarsSend",
|
||||
scale: 0.066,
|
||||
colors: [:],
|
||||
title: nil,
|
||||
text: "\(stars) Stars sent.",
|
||||
customUndoText: "View Chat",
|
||||
timeout: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
action: { [weak self] action in
|
||||
if case .undo = action, let navigationController = self?.navigationController as? NavigationController {
|
||||
let _ = (context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true))
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
self.present(resultController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
)
|
||||
self.push(purchaseController)
|
||||
|
|
|
|||
|
|
@ -518,12 +518,21 @@ private final class SheetContent: CombinedComponent {
|
|||
if let lastController = navigationController.viewControllers.last as? ViewController {
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .image(
|
||||
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
||||
// content: .image(
|
||||
// image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
||||
// title: presentationData.strings.Stars_Transfer_PurchasedTitle,
|
||||
// text: text,
|
||||
// round: false,
|
||||
// undoText: nil
|
||||
// ),
|
||||
content: .universal(
|
||||
animation: "StarsSend",
|
||||
scale: 0.066,
|
||||
colors: [:],
|
||||
title: presentationData.strings.Stars_Transfer_PurchasedTitle,
|
||||
text: text,
|
||||
round: false,
|
||||
undoText: nil
|
||||
customUndoText: nil,
|
||||
timeout: nil
|
||||
),
|
||||
elevatedLayout: lastController is ChatController,
|
||||
action: { _ in return true}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import LottieComponentResourceContent
|
|||
import UndoUI
|
||||
import GalleryUI
|
||||
import TextLoadingEffect
|
||||
import TelegramStringFormatting
|
||||
|
||||
private final class StickerSelectionComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
|
@ -2618,7 +2619,6 @@ 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>()
|
||||
|
|
@ -2636,10 +2636,6 @@ final class StoryStickersContentView: UIView, EmojiCustomContentView {
|
|||
|
||||
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)
|
||||
|
||||
|
|
@ -2740,7 +2736,7 @@ final class StoryStickersContentView: UIView, EmojiCustomContentView {
|
|||
InteractiveStickerButtonContent(
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
title: self.weatherFormatter.string(from: Measurement(value: weather.temperature, unit: UnitTemperature.celsius)),
|
||||
title: stringForTemperature(weather.temperature),
|
||||
iconName: weather.emoji,
|
||||
iconFile: weather.emojiFile,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import LottieComponent
|
|||
import LottieComponentResourceContent
|
||||
import StickerResources
|
||||
import AnimationCache
|
||||
import TelegramStringFormatting
|
||||
|
||||
private let shadowImage: UIImage = {
|
||||
return UIImage(bundleImageName: "Stories/ReactionShadow")!
|
||||
|
|
@ -224,12 +225,16 @@ public func storyPreviewWithAddedReactions(
|
|||
}
|
||||
}
|
||||
|
||||
private protocol ItemView: UIView {
|
||||
|
||||
}
|
||||
|
||||
final class StoryItemOverlaysView: UIView {
|
||||
static let counterFont: UIFont = {
|
||||
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 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] = [:]
|
||||
var activate: ((UIView, MessageReaction.Reaction) -> Void)?
|
||||
var requestUpdate: (() -> Void)?
|
||||
|
|
@ -561,25 +680,37 @@ final class StoryItemOverlaysView: UIView {
|
|||
isActive: Bool,
|
||||
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
|
||||
for mediaArea in story.mediaAreas {
|
||||
switch mediaArea {
|
||||
case let .reaction(coordinates, reaction, flags):
|
||||
let referenceSize = size
|
||||
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 {
|
||||
guard let (itemFrame, itemRotation, _) = getFrameAndRotation(coordinates: coordinates, scale: 0.97) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let itemView: ItemView
|
||||
let itemView: ReactionView
|
||||
let itemId = nextId
|
||||
if let current = self.itemViews[itemId] {
|
||||
if let current = self.itemViews[itemId] as? ReactionView {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = ItemView(frame: CGRect())
|
||||
itemView = ReactionView(frame: CGRect())
|
||||
itemView.activate = { [weak self] view, reaction in
|
||||
self?.activate?(view, reaction)
|
||||
}
|
||||
|
|
@ -590,9 +721,9 @@ final class StoryItemOverlaysView: UIView {
|
|||
self.addSubview(itemView)
|
||||
}
|
||||
|
||||
transition.setPosition(view: itemView, position: targetFrame.center)
|
||||
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: targetFrame.size))
|
||||
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(coordinates.rotation * (CGFloat.pi / 180.0), 0.0, 0.0, 1.0))
|
||||
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))
|
||||
|
||||
var counter = 0
|
||||
if let reactionData = story.views?.reactions.first(where: { $0.value == reaction }) {
|
||||
|
|
@ -607,7 +738,39 @@ final class StoryItemOverlaysView: UIView {
|
|||
availableReactions: availableReactions,
|
||||
entityFiles: entityFiles,
|
||||
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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3434,6 +3434,8 @@ final class StoryItemSetContainerSendMessage {
|
|||
actions.append(ContextMenuAction(content: .textWithSubtitleAndIcon(title: updatedPresentationData.initial.strings.Story_ViewLink, subtitle: url, icon: generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: .white)), action: {
|
||||
action()
|
||||
}))
|
||||
case .weather:
|
||||
return
|
||||
}
|
||||
|
||||
self.selectedMediaArea = mediaArea
|
||||
|
|
|
|||
BIN
submodules/TelegramUI/Resources/Animations/StarsBuy.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/StarsBuy.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/StarsSend.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/StarsSend.tgs
Normal file
Binary file not shown.
|
|
@ -346,8 +346,6 @@ public extension ChatControllerImpl {
|
|||
}
|
||||
self.attachmentController?.dismiss(animated: true, completion: nil)
|
||||
|
||||
|
||||
|
||||
let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1692,13 +1692,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||
var clearCacheAsDelete = false
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, !isMigrated {
|
||||
var views: Int = 0
|
||||
var forwards: Int = 0
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ViewCountMessageAttribute {
|
||||
views = attribute.count
|
||||
}
|
||||
if let attribute = attribute as? ForwardCountMessageAttribute {
|
||||
forwards = attribute.count
|
||||
}
|
||||
}
|
||||
|
||||
if infoSummaryData.canViewStats, views >= 100 {
|
||||
if infoSummaryData.canViewStats, forwards >= 1 || views >= 100 {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextViewStats, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { c, _ in
|
||||
|
|
|
|||
|
|
@ -326,12 +326,15 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
if let url = controller.url, controller.source != .menu {
|
||||
self.queryId = controller.queryId
|
||||
if let parsedUrl = URL(string: url) {
|
||||
self.webView?.load(URLRequest(url: parsedUrl))
|
||||
}
|
||||
|
||||
self.checkBotIdAndUrl(url)
|
||||
|
||||
if let keepAliveSignal = controller.keepAliveSignal {
|
||||
self.keepAliveDisposable = (keepAliveSignal
|
||||
|> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
|
|
@ -351,6 +354,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.checkBotIdAndUrl(result.url)
|
||||
if let parsedUrl = URL(string: result.url) {
|
||||
strongSelf.queryId = result.queryId
|
||||
strongSelf.webView?.load(URLRequest(url: parsedUrl))
|
||||
|
|
@ -372,6 +376,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
guard let self, let parsedUrl = URL(string: result.url) else {
|
||||
return
|
||||
}
|
||||
self.checkBotIdAndUrl(result.url)
|
||||
self.controller?.titleView?.title = WebAppTitle(title: appStart.botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified)
|
||||
self.webView?.load(URLRequest(url: parsedUrl))
|
||||
})
|
||||
|
|
@ -385,6 +390,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
strongSelf.queryId = result.queryId
|
||||
strongSelf.webView?.load(URLRequest(url: parsedUrl))
|
||||
|
||||
strongSelf.checkBotIdAndUrl(result.url)
|
||||
|
||||
if let keepAliveSignal = result.keepAliveSignal {
|
||||
strongSelf.keepAliveDisposable = (keepAliveSignal
|
||||
|> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
|
|
@ -404,6 +411,15 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
}
|
||||
|
||||
func checkBotIdAndUrl(_ url: String) {
|
||||
//1985737506
|
||||
if url.hasPrefix("https://walletbot.me"), let botId = self.controller?.botId.id._internalGetInt64Value(), botId != 1985737506 {
|
||||
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: "Bot id mismatch, please report steps to app developer", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||
})])
|
||||
self.controller?.present(alertController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
@objc fileprivate func mainButtonPressed() {
|
||||
if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue