diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index e229e63842..9a30d01b79 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -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) } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 6b4bf1eebd..4ba52d7ff2 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -65,7 +65,6 @@ public enum ChatOpenWebViewSource: Equatable { case generic case menu case inline(bot: EnginePeer) - case webApp(botApp: BotApp) } public final class ChatPanelInterfaceInteraction { diff --git a/submodules/Display/Source/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift index 5ead1f9391..b7c486c8d5 100644 --- a/submodules/Display/Source/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift @@ -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 diff --git a/submodules/DrawingUI/Sources/DrawingWeatherEntityView.swift b/submodules/DrawingUI/Sources/DrawingWeatherEntityView.swift index 05d6165b24..555fefe6c6 100644 --- a/submodules/DrawingUI/Sources/DrawingWeatherEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingWeatherEntityView.swift @@ -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) diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 3a9a97ff33..2b6a0aba20 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 95280d53e2..e48473d94e 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -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) } diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index 1decdf8166..fdd58a93fb 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -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 + } + } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 0cb5627894..e8d07b397e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift index 74d9ae7a78..22038df4f0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MediaArea.swift @@ -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 } } } diff --git a/submodules/TelegramStringFormatting/Sources/WeatherFormat.swift b/submodules/TelegramStringFormatting/Sources/WeatherFormat.swift new file mode 100644 index 0000000000..9adc721d4f --- /dev/null +++ b/submodules/TelegramStringFormatting/Sources/WeatherFormat.swift @@ -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 +} diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift index 8830b70ed6..c09612a9c4 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift index 67fb6eebc5..6f42269245 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index 6a2d3760f3..4a8440725b 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -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 [] diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 28b3aac8a4..cfde1b0530 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -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() { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorUtils.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorUtils.swift deleted file mode 100644 index 52de11f16e..0000000000 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorUtils.swift +++ /dev/null @@ -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 { - 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 { - 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 -} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift new file mode 100644 index 0000000000..0a0f0bfed5 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/Weather.swift @@ -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 { + 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 in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> mapToSignal { peer -> Signal 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 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 { + 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 + } + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index f7ebfb81c6..638f2d3f57 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 71b0c643d1..84cdd6589f 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -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} diff --git a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift index e4638ad8f6..783701b2b9 100644 --- a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift +++ b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift @@ -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: CombinedComponent { final class StoryStickersContentView: UIView, EmojiCustomContentView { private let context: AccountContext - private let weatherFormatter: MeasurementFormatter let tintContainerView = UIView() private let container = ComponentView() @@ -2636,10 +2636,6 @@ final class StoryStickersContentView: UIView, EmojiCustomContentView { init(context: AccountContext, weather: Signal) { 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, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift index 7f7863caf7..ab69237e03 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift @@ -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() + private let text = ComponentView() + + 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 ) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index abfbd85c5a..e7a36f29ae 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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 diff --git a/submodules/TelegramUI/Resources/Animations/StarsBuy.tgs b/submodules/TelegramUI/Resources/Animations/StarsBuy.tgs new file mode 100644 index 0000000000..e684dd1aa7 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/StarsBuy.tgs differ diff --git a/submodules/TelegramUI/Resources/Animations/StarsSend.tgs b/submodules/TelegramUI/Resources/Animations/StarsSend.tgs new file mode 100644 index 0000000000..3d4e32a67e Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/StarsSend.tgs differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index 68df51d58c..0dba24ec14 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 3dce7ce4b4..fd103a83cb 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -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 diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 26a8165a3c..49ae1cf2de 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -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