This commit is contained in:
Isaac 2024-07-19 20:25:36 +08:00
commit 9b49738353
18 changed files with 401 additions and 81 deletions

View file

@ -12535,3 +12535,5 @@ Sorry for the inconvenience.";
"Stars.Transaction.FragmentUnknown_URL" = "https://fragment.com/stars";
"Conversation.StatusBotSubscribers_1" = "1 user";
"Conversation.StatusBotSubscribers_any" = "%d users";
"Story.Editor.Add" = "Add";

View file

@ -1060,10 +1060,12 @@ public protocol SharedAccountContext: AnyObject {
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController

View file

@ -153,6 +153,7 @@ protocol BrowserContent: UIView {
var getNavigationController: () -> NavigationController? { get set }
var minimize: () -> Void { get set }
var close: () -> Void { get set }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set }

View file

@ -67,6 +67,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { }
var close: () -> Void = { }
var openPeer: (EnginePeer) -> Void = { _ in }

View file

@ -521,6 +521,16 @@ public class BrowserScreen: ViewController, MinimizableController {
}
self.minimize()
}
browserContent.close = { [weak self] in
guard let self, let controller = self.controller else {
return
}
if controller.isMinimized {
} else {
controller.dismiss()
}
}
self.content.append(browserContent)
self.requestLayout(transition: transition)
@ -952,6 +962,9 @@ public class BrowserScreen: ViewController, MinimizableController {
private let context: AccountContext
private let subject: Subject
var openPreviousOnClose = false
public init(context: AccountContext, subject: Subject) {
self.context = context
self.subject = subject

View file

@ -140,6 +140,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { }
var close: () -> Void = { }
var present: (ViewController, Any?) -> Void = { _, _ in }
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
var getNavigationController: () -> NavigationController? = { return nil }
@ -191,6 +192,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
if #available(iOS 15.0, *) {
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
}
if #available(iOS 16.4, *) {
self.webView.isInspectable = true
}
self.addSubview(self.webView)
}
@ -217,35 +221,54 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.updateLayout(size: size, insets: insets, transition: .immediate)
}
}
private let setupFontFunctions = """
(function() {
const styleId = 'telegram-font-overrides';
function setTelegramFontOverrides(font, textSizeAdjust) {
let style = document.getElementById(styleId);
if (!style) {
style = document.createElement('style');
style.id = styleId;
document.head.appendChild(style);
}
let cssRules = '* {';
if (font !== null) {
cssRules += `
font-family: ${font} !important;
`;
}
if (textSizeAdjust !== null) {
cssRules += `
-webkit-text-size-adjust: ${textSizeAdjust} !important;
`;
}
cssRules += '}';
style.innerHTML = cssRules;
if (font === null && textSizeAdjust === null) {
style.parentNode.removeChild(style);
}
}
window.setTelegramFontOverrides = setTelegramFontOverrides;
})();
"""
var currentFontState = BrowserPresentationState.FontState(size: 100, isSerif: false)
func updateFontState(_ state: BrowserPresentationState.FontState) {
self.updateFontState(state, force: false)
}
func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) {
if self.currentFontState.size != state.size || (force && self.currentFontState.size != 100) {
self.setFontSize(state.size)
}
if self.currentFontState.isSerif != state.isSerif || (force && self.currentFontState.isSerif) {
self.setFontSerif(state.isSerif)
}
self.currentFontState = state
}
private func setFontSize(_ fontSize: Int32) {
let js = "document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='\(fontSize)%'"
self.webView.evaluateJavaScript(js, completionHandler: nil)
}
private func setFontSerif(_ force: Bool) {
let js: String
if force {
js = "document.getElementsByTagName(\'body\')[0].style.fontFamily = 'Georgia, serif';"
} else {
js = "document.getElementsByTagName(\'body\')[0].style.fontFamily = '\"Lucida Grande\", \"Lucida Sans Unicode\", Arial, Helvetica, Verdana, sans-serif';"
}
self.webView.evaluateJavaScript(js) { _, _ in
}
let fontFamily = state.isSerif ? "'Georgia, serif'" : "null"
let textSizeAdjust = state.size != 100 ? "'\(state.size)%'" : "null"
let js = "\(setupFontFunctions) setTelegramFontOverrides(\(fontFamily), \(textSizeAdjust))";
self.webView.evaluateJavaScript(js) { _, _ in }
}
private var didSetupSearch = false
@ -513,6 +536,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
}
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
if let url = navigationAction.request.url?.absoluteString {
self.open(url: url, new: true)
}
}
return nil
}
func webViewDidClose(_ webView: WKWebView) {
self.close()
}
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(.prompt)
@ -619,8 +655,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
private func open(url: String, new: Bool) {
let subject: BrowserScreen.Subject = .webPage(url: url)
if new, let navigationController = self.getNavigationController() {
navigationController._keepModalDismissProgress = true
self.minimize()
let controller = BrowserScreen(context: self.context, subject: subject)
navigationController._keepModalDismissProgress = true
navigationController.pushViewController(controller)
} else {
self.pushContent(subject)

View file

@ -91,7 +91,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let context: AccountContext
private let peersFilter: ChatListNodePeersFilter
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
private let location: ChatListControllerLocation
private var location: ChatListControllerLocation
private let displaySearchFilters: Bool
private let hasDownloads: Bool
private var interaction: ChatListSearchInteraction?
@ -577,6 +577,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
for token in tokens {
tokensIdSet.insert(token.id)
}
if case .chatList(.archive) = self.location, !tokens.contains(where: { $0.id == AnyHashable(ChatListTokenId.archive.rawValue) }) {
self.location = .chatList(groupId: .root)
self.paneContainerNode.location = self.location
}
if !tokensIdSet.contains(ChatListTokenId.date.rawValue) && updatedOptions?.date != nil {
updatedOptions = updatedOptions?.withUpdatedDate(nil)
}
@ -590,7 +596,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var options = options
var tokens: [SearchBarToken] = []
if case .chatList(.archive) = self.location {
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: true))
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false))
} else if case .forum = self.location, let forumPeer = self.forumPeer {
tokens.append(SearchBarToken(id: ChatListTokenId.forum.rawValue, icon: nil, iconOffset: -1.0, peer: (forumPeer, self.context, self.presentationData.theme), title: self.presentationData.strings.ChatList_Archive, permanent: true))
}
@ -670,7 +676,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var tokens: [SearchBarToken] = []
if case .chatList(.archive) = self.location {
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: true))
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false))
} else if case .forum = self.location, let forumPeer = self.forumPeer {
tokens.append(SearchBarToken(id: ChatListTokenId.forum.rawValue, icon: nil, iconOffset: -1.0, peer: (forumPeer, self.context, self.presentationData.theme), title: self.presentationData.strings.ChatList_Archive, permanent: true))
}

View file

@ -159,7 +159,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let peersFilter: ChatListNodePeersFilter
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
private let location: ChatListControllerLocation
var location: ChatListControllerLocation
private let searchQuery: Signal<String?, NoError>
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
private let globalPeerSearchContext: GlobalPeerSearchContext

View file

@ -512,6 +512,10 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
}
} else {
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.scrollView.contentSize.height / 3.0), animated: true)
if let chatController = self.baseNavigationController()?.topViewController as? ChatController {
chatController.updatePushedTransition(1.0, transition: .animated(duration: 0.45, curve: .customSpring(damping: 180.0, initialVelocity: 0.0)))
}
}
}

View file

@ -2982,12 +2982,16 @@ public func mediaPickerController(
public func storyMediaPickerController(
context: AccountContext,
isDark: Bool,
getSourceRect: @escaping () -> CGRect,
completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
dismissed: @escaping () -> Void,
groupsPresented: @escaping () -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
var presentationData = context.sharedContext.currentPresentationData.with({ $0 })
if isDark {
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
}
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
return nil

View file

@ -2986,7 +2986,7 @@ public class CameraScreen: ViewController {
if let current = self.galleryController {
controller = current
} else {
controller = self.context.sharedContext.makeStoryMediaPickerScreen(context: self.context, getSourceRect: { [weak self] in
controller = self.context.sharedContext.makeStoryMediaPickerScreen(context: self.context, isDark: true, getSourceRect: { [weak self] in
if let self {
if let galleryButton = self.node.componentHost.findTaggedView(tag: galleryButtonTag) {
return galleryButton.convert(galleryButton.bounds, to: self.view).offsetBy(dx: 0.0, dy: -15.0)

View file

@ -1543,7 +1543,14 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
}
let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments)
var videoCorners = corners
var imageCorners = corners
if let file = media as? TelegramMediaFile, file.isInstantVideo {
videoCorners = ImageCorners(radius: boundingSize.width / 2.0)
imageCorners = ImageCorners(radius: 0.0)
}
let arguments = TransformImageArguments(corners: imageCorners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments)
let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize).ensuredValid
let cleanImageFrame = CGRect(origin: imageFrame.origin, size: CGSize(width: imageFrame.width - arguments.corners.extendedEdges.right, height: imageFrame.height))
@ -1563,7 +1570,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
strongSelf.automaticPlayback = automaticPlayback
strongSelf.automaticDownload = automaticDownload
strongSelf.preferredStoryHighQuality = associatedData.preferredStoryHighQuality
if let previousArguments = strongSelf.currentImageArguments {
if previousArguments.imageSize == arguments.imageSize {
strongSelf.pinchContainerNode.frame = imageFrame
@ -1616,7 +1623,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
statusFrame.origin.y = floor(imageFrame.height / 2.0 - statusFrame.height / 2.0)
statusNode.frame = statusFrame
}
var updatedVideoNodeReadySignal: Signal<Void, NoError>?
var updatedPlayerStatusSignal: Signal<MediaPlayerStatus?, NoError>?
if let currentReplaceVideoNode = replaceVideoNode {
@ -1628,7 +1635,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
if currentReplaceVideoNode, let updatedVideoFile = updateVideoFile {
let decoration = ChatBubbleVideoDecoration(corners: arguments.corners, nativeSize: nativeSize, contentMode: contentMode.bubbleVideoDecorationContentMode, backgroundColor: arguments.emptyColor ?? .black)
let decoration = ChatBubbleVideoDecoration(corners: videoCorners, nativeSize: nativeSize, contentMode: contentMode.bubbleVideoDecorationContentMode, backgroundColor: arguments.emptyColor ?? .black)
strongSelf.videoNodeDecoration = decoration
let mediaManager = context.sharedContext.mediaManager
@ -1700,10 +1707,17 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }), strongSelf.extendedMediaOverlayNode == nil {
strongSelf.internallyVisible = false
}
if let videoNode = strongSelf.videoNode {
if !(replaceVideoNode ?? false), let decoration = videoNode.decoration as? ChatBubbleVideoDecoration, decoration.corners != corners {
decoration.updateCorners(corners)
if !(replaceVideoNode ?? false), let decoration = videoNode.decoration as? ChatBubbleVideoDecoration, decoration.corners != videoCorners {
decoration.updateCorners(videoCorners)
}
if !videoCorners.isEmpty && imageCorners.isEmpty {
strongSelf.imageNode.clipsToBounds = true
strongSelf.imageNode.cornerRadius = videoCorners.topLeft.radius
} else {
strongSelf.imageNode.cornerRadius = 0.0
}
videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate)

View file

@ -818,7 +818,7 @@ final class MediaEditorScreenComponent: Component {
}
var doneButtonTitle: String?
var doneButtonIcon: UIImage
var doneButtonIcon: UIImage?
switch controller.mode {
case .storyEditor:
doneButtonTitle = isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased()
@ -826,6 +826,10 @@ final class MediaEditorScreenComponent: Component {
case .stickerEditor:
doneButtonTitle = nil
doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)!
case .botPreview:
//TODO:localize
doneButtonTitle = environment.strings.Story_Editor_Add
doneButtonIcon = nil
}
let doneButtonSize = self.doneButton.update(
@ -859,6 +863,8 @@ final class MediaEditorScreenComponent: Component {
}
case .stickerEditor:
controller.requestStickerCompletion(animated: true)
case .botPreview:
controller.requestStoryCompletion(animated: true)
}
}
)),
@ -2415,6 +2421,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case storyEditor
case stickerEditor(mode: StickerEditorMode)
case botPreview
}
public enum TransitionIn {
@ -2873,7 +2880,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let mediaEntity = DrawingMediaEntity(size: fittedSize)
mediaEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
switch controller.mode {
case .storyEditor:
case .storyEditor, .botPreview:
if fittedSize.height > fittedSize.width {
mediaEntity.scale = max(storyDimensions.width / fittedSize.width, storyDimensions.height / fittedSize.height)
} else {
@ -3626,7 +3633,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
transitionInView.contentMode = .scaleAspectFill
var initialScale: CGFloat
switch controller.mode {
case .storyEditor:
case .storyEditor, .botPreview:
if image.size.height > image.size.width {
initialScale = max(self.previewContainerView.bounds.width / image.size.width, self.previewContainerView.bounds.height / image.size.height)
} else {
@ -4784,12 +4791,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
mediaEditor.maybePauseVideo()
var hasInteractiveStickers = true
if let controller = self.controller, case .stickerEditor = controller.mode {
hasInteractiveStickers = false
if let controller = self.controller {
switch controller.mode {
case .stickerEditor, .botPreview:
hasInteractiveStickers = false
default:
break
}
}
var weatherSignal: Signal<StickerPickerScreen.Weather, NoError>
if "".isEmpty {
if hasInteractiveStickers {
let weatherPromise: Promise<StickerPickerScreen.Weather>
if let current = self.weatherPromise {
weatherPromise = current
@ -6101,7 +6113,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
save = presentationData.strings.Story_Editor_DraftKeepMedia
}
text = presentationData.strings.Story_Editor_DraftDiscaedText
case .stickerEditor:
case .stickerEditor, .botPreview:
title = presentationData.strings.Story_Editor_DraftDiscardMedia
text = presentationData.strings.Story_Editor_DiscardText
}
@ -7440,12 +7452,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private final class DoneButtonContentComponent: CombinedComponent {
let backgroundColor: UIColor
let icon: UIImage
let icon: UIImage?
let title: String?
init(
backgroundColor: UIColor,
icon: UIImage,
icon: UIImage?,
title: String?
) {
self.backgroundColor = backgroundColor
@ -7469,12 +7481,14 @@ private final class DoneButtonContentComponent: CombinedComponent {
let text = Child(Text.self)
return { context in
let iconSize = context.component.icon.size
let icon = icon.update(
component: Image(image: context.component.icon, tintColor: .white, size: iconSize),
availableSize: CGSize(width: 180.0, height: 100.0),
transition: .immediate
)
var iconChild: _UpdatedChildComponent?
if let iconImage = context.component.icon {
iconChild = icon.update(
component: Image(image: iconImage, tintColor: .white, size: iconImage.size),
availableSize: CGSize(width: 180.0, height: 100.0),
transition: .immediate
)
}
let backgroundHeight: CGFloat = 33.0
var backgroundSize = CGSize(width: backgroundHeight, height: backgroundHeight)
@ -7494,7 +7508,10 @@ private final class DoneButtonContentComponent: CombinedComponent {
transition: .immediate
)
let updatedBackgroundWidth = backgroundSize.width + textSpacing + title!.size.width
var updatedBackgroundWidth = backgroundSize.width + title!.size.width
if let _ = iconChild {
updatedBackgroundWidth += textSpacing
}
if updatedBackgroundWidth < 126.0 {
backgroundSize.width = updatedBackgroundWidth
} else {
@ -7514,16 +7531,22 @@ private final class DoneButtonContentComponent: CombinedComponent {
)
if let title {
var titlePosition = backgroundSize.width / 2.0
if let _ = iconChild {
titlePosition = title.size.width / 2.0 + 15.0
}
context.add(title
.position(CGPoint(x: title.size.width / 2.0 + 15.0, y: backgroundHeight / 2.0))
.position(CGPoint(x: titlePosition, y: backgroundHeight / 2.0))
.opacity(hideTitle ? 0.0 : 1.0)
)
}
context.add(icon
.position(CGPoint(x: background.size.width - 16.0, y: backgroundSize.height / 2.0))
)
if let iconChild {
context.add(iconChild
.position(CGPoint(x: background.size.width - 16.0, y: backgroundSize.height / 2.0))
)
}
return backgroundSize
}
}

View file

@ -58,9 +58,10 @@ func getWeather(context: AccountContext) -> Signal<StickerPickerScreen.Weather,
return getWeatherData(context: context, location: location)
|> mapToSignal { weather in
if let weather {
if let match = context.animatedEmojiStickersValue[weather.emoji.strippedEmoji]?.first {
let effectiveEmoji = emojiFor(for: weather.emoji.strippedEmoji, date: Date(), location: location)
if let match = context.animatedEmojiStickersValue[effectiveEmoji]?.first {
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
emoji: weather.emoji.strippedEmoji,
emoji: effectiveEmoji,
emojiFile: match.file,
temperature: weather.temperature
)))
@ -97,3 +98,123 @@ private struct WeatherBotConfiguration {
}
}
}
private let J1970: Double = 2440588.0
private let moonEmojis = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑"]
private func emojiFor(for emoji: String, date: Date, location: CLLocationCoordinate2D) -> String {
var emoji = emoji
if !"".isEmpty, ["☀️", "🌤️"].contains(emoji) && !isDay(latitude: location.latitude, longitude: location.longitude, dateTime: date) {
emoji = moonPhaseEmoji(for: date)
}
return emoji
}
private func moonPhaseEmoji(for date: Date) -> String {
let julianDate = toJulianDate(date: date)
let referenceNewMoon: Double = 2451550.1
let synodicMonth: Double = 29.53058867
let daysSinceNewMoon = julianDate - referenceNewMoon
let newMoons = daysSinceNewMoon / synodicMonth
let currentMoonPhase = (newMoons - floor(newMoons)) * synodicMonth
switch currentMoonPhase {
case 0..<1.84566:
return moonEmojis[0]
case 1.84566..<5.53699:
return moonEmojis[1]
case 5.53699..<9.22831:
return moonEmojis[2]
case 9.22831..<12.91963:
return moonEmojis[3]
case 12.91963..<16.61096:
return moonEmojis[4]
case 16.61096..<20.30228:
return moonEmojis[5]
case 20.30228..<23.99361:
return moonEmojis[6]
case 23.99361..<27.68493:
return moonEmojis[7]
default:
return moonEmojis[8]
}
}
private func isDay(latitude: Double, longitude: Double, dateTime: Date) -> Bool {
let calendar = Calendar.current
let date = calendar.startOfDay(for: dateTime)
let time = dateTime.timeIntervalSince(date)
let sunrise = calculateSunrise(latitude: latitude, longitude: longitude, date: date)
let sunset = calculateSunset(latitude: latitude, longitude: longitude, date: date)
return time >= sunrise * 3600 && time <= sunset * 3600
}
private func calculateSunrise(latitude: Double, longitude: Double, date: Date) -> Double {
return calculateSunTime(latitude: latitude, longitude: longitude, date: date, isSunrise: true)
}
private func calculateSunset(latitude: Double, longitude: Double, date: Date) -> Double {
return calculateSunTime(latitude: latitude, longitude: longitude, date: date, isSunrise: false)
}
private func calculateSunTime(latitude: Double, longitude: Double, date: Date, isSunrise: Bool) -> Double {
let calendar = Calendar.current
let dayOfYear = calendar.ordinality(of: .day, in: .year, for: date)!
let zenith = 90.833
let D2R = Double.pi / 180.0
let R2D = 180.0 / Double.pi
let lngHour = longitude / 15.0
let t = Double(dayOfYear) + ((isSunrise ? 6.0 : 18.0) - lngHour) / 24.0
let M = (0.9856 * t) - 3.289
var L = M + (1.916 * sin(M * D2R)) + (0.020 * sin(2 * M * D2R)) + 282.634
if L > 360.0 {
L -= 360.0
} else if L < 0.0 {
L += 360.0
}
var RA = R2D * atan(0.91764 * tan(L * D2R))
if RA > 360.0 {
RA -= 360.0
} else if RA < 0.0 {
RA += 360.0
}
let Lquadrant = (floor(L / 90.0)) * 90.0
let RAquadrant = (floor(RA / 90.0)) * 90.0
RA += (Lquadrant - RAquadrant)
RA /= 15.0
let sinDec = 0.39782 * sin(L * D2R)
let cosDec = cos(asin(sinDec))
let cosH = (cos(zenith * D2R) - (sinDec * sin(latitude * D2R))) / (cosDec * cos(latitude * D2R))
if cosH > 1.0 || cosH < -1.0 {
return -1
}
var H = isSunrise ? (360.0 - R2D * acos(cosH)) : R2D * acos(cosH)
H /= 15.0
let T = H + RA - (0.06571 * t) - 6.622
var UT = T - lngHour
if UT > 24.0 {
UT -= 24.0
} else if UT < 0.0 {
UT += 24.0
}
return UT
}
private func toJulianDate(date: Date) -> Double {
return date.timeIntervalSince1970 / 86400.0 + J1970 - 0.5
}

View file

@ -9801,6 +9801,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.controller?.push(controller)
}
private func openBotPreviewEditor(target: Stories.PendingTarget, source: Any, transitionIn: (UIView, CGRect, UIImage?)?) {
let context = self.context
let externalState = MediaEditorTransitionOutExternalState(
storyTarget: target,
isForcedTarget: false,
isPeerArchived: false,
transitionOut: nil
)
let controller = context.sharedContext.makeBotPreviewEditorScreen(
context: context,
source: source,
target: target,
transitionArguments: transitionIn,
externalState: externalState,
completion: { result, commit in
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
var viewControllers = rootController.viewControllers
viewControllers = viewControllers.filter { !($0 is AttachmentController)}
rootController.setViewControllers(viewControllers, animated: false)
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
}
},
cancelled: {}
)
self.controller?.push(controller)
}
private func openPostStory(sourceFrame: CGRect?) {
self.postingAvailabilityDisposable?.dispose()
@ -9808,12 +9838,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if !botInfo.flags.contains(.canEdit) {
return
}
let cameraTransitionIn: StoryCameraTransitionIn? = nil
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(customTarget: .botPreview(self.peerId), transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
coordinator?.animateIn()
}
let controller = self.context.sharedContext.makeStoryMediaPickerScreen(
context: self.context,
isDark: false,
getSourceRect: { return .zero },
completion: { [weak self] result, transitionView, transitionRect, transitionImage, transitionOut, dismissed in
guard let self else {
return
}
self.openBotPreviewEditor(target: .botPreview(self.peerId), source: result, transitionIn: (transitionView, transitionRect, transitionImage))
},
dismissed: {},
groupsPresented: {}
)
self.controller?.push(controller)
} else {
let canPostStatus: Signal<StoriesUploadAvailability, NoError>
canPostStatus = self.context.engine.messages.checkStoriesUploadAvailability(target: .peer(self.peerId))

View file

@ -567,11 +567,7 @@ final class StoryItemOverlaysView: UIView {
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
) -> CGSize {
let itemSize = CGSize(width: floor(size.height * 0.71), height: floor(size.height * 0.71))
if self.file?.fileId != emojiFile?.fileId, let file = emojiFile {
@ -615,14 +611,19 @@ final class StoryItemOverlaysView: UIView {
MultilineTextComponent(text: .plain(string))
),
environment: {},
containerSize: size
containerSize: CGSize(width: .greatestFiniteMagnitude, height: size.height)
)
let leftInset = size.height * 0.058
let rightInset = size.height * 0.2
let spacing = size.height * 0.205
let contentWidth: CGFloat = leftInset + itemSize.width + spacing + textSize.width + rightInset
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 textFrame = CGRect(origin: CGPoint(x: contentWidth - textSize.width - rightInset, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
let textTransition = ComponentTransition.immediate
textTransition.setFrame(view: view, frame: textFrame)
}
@ -632,7 +633,7 @@ final class StoryItemOverlaysView: UIView {
self.addSubview(directStickerView)
}
let stickerFrame = itemSize.centered(around: CGPoint(x: size.height * 0.5 + size.height * 0.058, y: size.height * 0.5))
let stickerFrame = itemSize.centered(around: CGPoint(x: size.height * 0.5 + leftInset, y: size.height * 0.5))
let stickerTransition = ComponentTransition.immediate
stickerTransition.setPosition(view: directStickerView, position: stickerFrame.center)
@ -640,6 +641,14 @@ final class StoryItemOverlaysView: UIView {
directStickerView.externalShouldPlay = isActive
}
let contentSize = CGSize(width: contentWidth, height: size.height)
self.backgroundView.backgroundColor = flags.contains(.isDark) ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff)
self.backgroundView.frame = CGRect(origin: .zero, size: contentSize)
self.backgroundView.layer.cornerRadius = cornerRadius
return contentSize
}
}
@ -754,15 +763,12 @@ final class StoryItemOverlaysView: UIView {
itemView = current
} else {
itemView = WeatherView(frame: CGRect())
itemView.isUserInteractionEnabled = false
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(
let itemSize = itemView.update(
context: context,
emoji: emoji,
emojiFile: context.animatedEmojiStickersValue[emoji]?.first?.file,
@ -774,6 +780,10 @@ final class StoryItemOverlaysView: UIView {
isActive: isActive
)
transition.setPosition(view: itemView, position: itemFrame.center)
transition.setBounds(view: itemView, bounds: CGRect(origin: CGPoint(), size: itemSize))
transition.setTransform(view: itemView, transform: CATransform3DMakeRotation(itemRotation, 0.0, 0.0, 1.0))
nextId += 1
default:
break

View file

@ -32,6 +32,7 @@ import ChatMessageNotificationItem
import PhoneNumberFormat
import AttachmentUI
import MinimizedContainer
import BrowserUI
final class UnauthorizedApplicationContext {
let sharedContext: SharedAccountContextImpl
@ -445,6 +446,8 @@ final class AuthorizedApplicationContext {
minimizedContainer.collapse()
} else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController {
topContoller.minimizeIfNeeded()
} else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen {
topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil)
}
for controller in strongSelf.rootController.viewControllers {

View file

@ -2533,6 +2533,46 @@ public final class SharedAccountContextImpl: SharedAccountContext {
})
}
public func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController {
let subject: Signal<MediaEditorScreen.Subject?, NoError>
if let asset = source as? PHAsset {
subject = .single(.asset(asset))
} else if let image = source as? UIImage {
subject = .single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
} else {
subject = .single(.empty(PixelDimensions(width: 1080, height: 1920)))
}
let editorController = MediaEditorScreen(
context: context,
mode: .botPreview,
subject: subject,
customTarget: nil,
transitionIn: transitionArguments.flatMap { .gallery(
MediaEditorScreen.TransitionIn.GalleryTransitionIn(
sourceView: $0.0,
sourceRect: $0.1,
sourceImage: $0.2
)
) },
transitionOut: { finished, isNew in
if !finished, let transitionArguments {
return MediaEditorScreen.TransitionOut(
destinationView: transitionArguments.0,
destinationRect: transitionArguments.0.bounds,
destinationCornerRadius: 0.0
)
}
return nil
}, completion: { result, commit in
completion(result, commit)
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
)
editorController.cancelled = { _ in
cancelled()
}
return editorController
}
public func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController {
let subject: Signal<MediaEditorScreen.Subject?, NoError>
var mode: MediaEditorScreen.Mode.StickerEditorMode
@ -2605,8 +2645,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion)
}
public func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController {
return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed, groupsPresented: groupsPresented)
public func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController {
return storyMediaPickerController(context: context, isDark: isDark, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed, groupsPresented: groupsPresented)
}
public func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {