diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 91a5b9c3ed..43792c1328 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12393,6 +12393,10 @@ Sorry for the inconvenience."; "HashtagSearch.StoriesFound_any" = "%@ Stories Found"; "HashtagSearch.StoriesFoundInfo" = "View stories with %@"; +"HashtagSearch.Stories_1" = "%@ Story"; +"HashtagSearch.Stories_any" = "%@ Stories"; +"HashtagSearch.LocalStoriesFound" = "%1$@ in %2$@"; + "Stars.BotRevenue.Title" = "Stars Balance"; "Stars.BotRevenue.Revenue.Title" = "Stars Received"; "Stars.BotRevenue.Proceeds.Title" = "Rewards Overview"; @@ -13104,3 +13108,18 @@ Sorry for the inconvenience."; "Chat.PrivateMessageEditTimestamp.YesterdayAt" = "edited yesterday at %@"; "Stars.Transaction.Gift.Title" = "Gift"; + +"ChatList.Search.Open" = "OPEN"; +"ChatList.Search.ShowMore" = "Show More"; + +"AttachmentMenu.AddPhotoOrVideo" = "Add Photo or Video"; +"AttachmentMenu.AddDocument" = "Add Document"; + +"Chat.BotAd.Title" = "Ad"; +"Chat.BotAd.Remove" = "remove"; + +"ChatList.Search.TopAppsInfo" = "Which apps are included here? [Learn >]()"; + +"TopApps.Info.Title" = "Top Mini Apps"; +"TopApps.Info.Text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini app in [@botfather]() (as described [here]()), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on weekly average."; +"TopApps.Info.Done" = "Understood"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index f80d2a96c3..aa4ffd7267 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -813,7 +813,7 @@ public enum CollectibleItemInfoScreenSubject { public enum StorySearchControllerScope { - case query(String) + case query(EnginePeer?, String) case location(coordinates: MediaArea.Coordinates, venue: MediaArea.Venue) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index a8ebc7b29c..c9ca2a8af4 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -35,6 +35,7 @@ import AvatarNode private enum ChatListRecentEntryStableId: Hashable { case topPeers case peerId(EnginePeer.Id, ChatListRecentEntry.Section) + case footer } private enum ChatListRecentEntry: Comparable, Identifiable { @@ -46,13 +47,16 @@ private enum ChatListRecentEntry: Comparable, Identifiable { case topPeers([EnginePeer], PresentationTheme, PresentationStrings) case peer(index: Int, peer: RecentlySearchedPeer, Section, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, EngineGlobalNotificationSettings, PeerStoryStats?, Bool) + case footer(PresentationTheme, String) var stableId: ChatListRecentEntryStableId { switch self { - case .topPeers: - return .topPeers - case let .peer(_, peer, section, _, _, _, _, _, _, _, _): - return .peerId(peer.peer.peerId, section) + case .topPeers: + return .topPeers + case let .peer(_, peer, section, _, _, _, _, _, _, _, _): + return .peerId(peer.peer.peerId, section) + case .footer: + return .footer } } @@ -79,6 +83,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else { return false } + case let .footer(lhsTheme, lhsText): + if case let .footer(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } } } @@ -88,11 +98,15 @@ private enum ChatListRecentEntry: Comparable, Identifiable { return true case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _): switch rhs { - case .topPeers: - return false + case .topPeers: + return false case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _): - return lhsIndex <= rhsIndex + return lhsIndex <= rhsIndex + case .footer: + return true } + case .footer: + return false } } @@ -110,7 +124,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable { animationRenderer: MultiAnimationRenderer, openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void, isChannelsTabExpanded: Bool?, - toggleChannelsTabExpanded: @escaping () -> Void + toggleChannelsTabExpanded: @escaping () -> Void, + openTopAppsInfo: @escaping () -> Void ) -> ListViewItem { switch self { case let .topPeers(peers, theme, strings): @@ -273,9 +288,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable { var buttonAction: ContactsPeerItemButtonAction? if case .chats = key, case let .user(user) = primaryPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { - //TODO:localize buttonAction = ContactsPeerItemButtonAction( - title: "OPEN", + title: presentationData.strings.ChatList_Search_Open, action: { peer, _, _ in peerSelected(primaryPeer, nil, true) } @@ -344,6 +358,10 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } } ) + case let .footer(_, text): + return ItemListTextItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat), text: .markdown(text), sectionId: 0, linkAction: { _ in + openTopAppsInfo() + }, style: .plain, textSize: .larger, textAlignment: .center, trimBottomInset: true, additionalInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0)) } } } @@ -911,8 +929,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if case .publicPosts = key { header = ChatListSearchItemHeader(type: .publicPosts, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) } else { - //TODO:localize - header = ChatListSearchItemHeader(type: .publicPosts, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Show More >", action: { + header = ChatListSearchItemHeader(type: .publicPosts, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "\(presentationData.strings.ChatList_Search_ShowMore) >", action: { openPublicPosts() }) } @@ -1045,6 +1062,7 @@ private func chatListSearchContainerPreparedRecentTransition( animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void, + openTopAppsInfo: @escaping () -> Void, isChannelsTabExpanded: Bool?, toggleChannelsTabExpanded: @escaping () -> Void, isEmpty: Bool @@ -1052,8 +1070,8 @@ private func chatListSearchContainerPreparedRecentTransition( let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdateAll) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded, openTopAppsInfo: openTopAppsInfo), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, filter: filter, key: key, peerSelected: peerSelected, disabledPeerSelected: disabledPeerSelected, peerContextAction: peerContextAction, clearRecentlySearchedPeers: clearRecentlySearchedPeers, deletePeer: deletePeer, animationCache: animationCache, animationRenderer: animationRenderer, openStories: openStories, isChannelsTabExpanded: isChannelsTabExpanded, toggleChannelsTabExpanded: toggleChannelsTabExpanded, openTopAppsInfo: openTopAppsInfo), directionHint: nil) } return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates, isEmpty: isEmpty) } @@ -3677,6 +3695,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { false )) } + + result.append(.footer(presentationData.theme, presentationData.strings.ChatList_Search_TopAppsInfo)) } var isEmpty = false @@ -3811,6 +3831,15 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).startStandalone() }, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer, openStories: { peerId, avatarNode in interaction.openStories?(peerId, avatarNode) + }, openTopAppsInfo: { + let alertController = textAlertController( + context: context, + title: presentationData.strings.TopApps_Info_Title, + text: presentationData.strings.TopApps_Info_Text, + actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.TopApps_Info_Done, action: {})], + parseMarkdown: true + ) + interaction.present(alertController, nil) }, isChannelsTabExpanded: recentItems.isChannelsTabExpanded, toggleChannelsTabExpanded: { diff --git a/submodules/ComponentFlow/Source/Components/RoundedRectangle.swift b/submodules/ComponentFlow/Source/Components/RoundedRectangle.swift index 1ea0212393..e4141d06fa 100644 --- a/submodules/ComponentFlow/Source/Components/RoundedRectangle.swift +++ b/submodules/ComponentFlow/Source/Components/RoundedRectangle.swift @@ -13,17 +13,19 @@ public final class RoundedRectangle: Component { public let gradientDirection: GradientDirection public let stroke: CGFloat? public let strokeColor: UIColor? + public let size: CGSize? - public convenience init(color: UIColor, cornerRadius: CGFloat?, stroke: CGFloat? = nil, strokeColor: UIColor? = nil) { - self.init(colors: [color], cornerRadius: cornerRadius, stroke: stroke, strokeColor: strokeColor) + public convenience init(color: UIColor, cornerRadius: CGFloat?, stroke: CGFloat? = nil, strokeColor: UIColor? = nil, size: CGSize? = nil) { + self.init(colors: [color], cornerRadius: cornerRadius, stroke: stroke, strokeColor: strokeColor, size: size) } - public init(colors: [UIColor], cornerRadius: CGFloat?, gradientDirection: GradientDirection = .horizontal, stroke: CGFloat? = nil, strokeColor: UIColor? = nil) { + public init(colors: [UIColor], cornerRadius: CGFloat?, gradientDirection: GradientDirection = .horizontal, stroke: CGFloat? = nil, strokeColor: UIColor? = nil, size: CGSize? = nil) { self.colors = colors self.cornerRadius = cornerRadius self.gradientDirection = gradientDirection self.stroke = stroke self.strokeColor = strokeColor + self.size = size } public static func ==(lhs: RoundedRectangle, rhs: RoundedRectangle) -> Bool { @@ -42,6 +44,9 @@ public final class RoundedRectangle: Component { if lhs.strokeColor != rhs.strokeColor { return false } + if lhs.size != rhs.size { + return false + } return true } @@ -49,8 +54,9 @@ public final class RoundedRectangle: Component { var component: RoundedRectangle? func update(component: RoundedRectangle, availableSize: CGSize, transition: ComponentTransition) -> CGSize { + let size = component.size ?? availableSize if self.component != component { - let cornerRadius = component.cornerRadius ?? min(availableSize.width, availableSize.height) * 0.5 + let cornerRadius = component.cornerRadius ?? min(size.width, size.height) * 0.5 if component.colors.count == 1, let color = component.colors.first { let imageSize = CGSize(width: max(component.stroke ?? 0.0, cornerRadius) * 2.0, height: max(component.stroke ?? 0.0, cornerRadius) * 2.0) @@ -75,7 +81,7 @@ public final class RoundedRectangle: Component { self.image = UIGraphicsGetImageFromCurrentImageContext()?.stretchableImage(withLeftCapWidth: Int(cornerRadius), topCapHeight: Int(cornerRadius)) UIGraphicsEndImageContext() } else if component.colors.count > 1 { - let imageSize = availableSize + let imageSize = size UIGraphicsBeginImageContextWithOptions(imageSize, false, 0.0) if let context = UIGraphicsGetCurrentContext() { context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: imageSize), cornerRadius: cornerRadius).cgPath) @@ -106,7 +112,7 @@ public final class RoundedRectangle: Component { } } - return availableSize + return size } } diff --git a/submodules/Display/Source/TextAlertController.swift b/submodules/Display/Source/TextAlertController.swift index 252d5efc54..55ee25acd1 100644 --- a/submodules/Display/Source/TextAlertController.swift +++ b/submodules/Display/Source/TextAlertController.swift @@ -458,7 +458,10 @@ public func standardTextAlertController(theme: AlertControllerTheme, title: Stri let boldFont = title == nil ? Font.bold(theme.baseFontSize) : Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0)) let body = MarkdownAttributeSet(font: font, textColor: theme.primaryColor) let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.primaryColor) - attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center) + let link = MarkdownAttributeSet(font: font, textColor: theme.accentColor) + attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { url in + return ("URL", url) + }), textAlignment: .center) } else { attributedText = NSAttributedString(string: text, font: title == nil ? Font.semibold(theme.baseFontSize) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0)), textColor: theme.primaryColor, paragraphAlignment: .center) } diff --git a/submodules/DrawingUI/Sources/DrawingLinkEntityView.swift b/submodules/DrawingUI/Sources/DrawingLinkEntityView.swift index 5ef068ce40..2084221c27 100644 --- a/submodules/DrawingUI/Sources/DrawingLinkEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingLinkEntityView.swift @@ -209,7 +209,7 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate if !self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { string = self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() } else { - string = self.linkEntity.url.uppercased().replacingOccurrences(of: "http://", with: "").replacingOccurrences(of: "https://", with: "").replacingOccurrences(of: "tonsite://", with: "") + string = self.linkEntity.url.uppercased().replacingOccurrences(of: "HTTP://", with: "").replacingOccurrences(of: "HTTPS://", with: "").replacingOccurrences(of: "TONSITE://", with: "") } let text = NSMutableAttributedString(string: string) let range = NSMakeRange(0, text.length) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index bb0cc92fa0..e43afdcdbd 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -13,6 +13,7 @@ import ChatListSearchItemHeader final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private let context: AccountContext private weak var controller: HashtagSearchController? + private let peer: EnginePeer? private var query: String private var isCashtag = false private var presentationData: PresentationData @@ -51,6 +52,7 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg init(context: AccountContext, controller: HashtagSearchController, peer: EnginePeer?, query: String, navigationBar: NavigationBar?, navigationController: NavigationController?) { self.context = context self.controller = controller + self.peer = peer self.query = query self.navigationBar = navigationBar self.isCashtag = query.hasPrefix("$") @@ -84,12 +86,17 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg self.currentController = nil } - let myChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: false) - self.myChatContents = myChatContents - self.myController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: myChatContents), botStart: nil, mode: .standard(.default), params: nil) - self.myController?.alwaysShowSearchResultsAsList = true - self.myController?.showListEmptyResults = true - self.myController?.customNavigationController = navigationController + if let _ = peer, controller.mode != .chatOnly { + let myChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: false) + self.myChatContents = myChatContents + self.myController = context.sharedContext.makeChatController(context: context, chatLocation: .customChatContents, subject: .customChatContents(contents: myChatContents), botStart: nil, mode: .standard(.default), params: nil) + self.myController?.alwaysShowSearchResultsAsList = true + self.myController?.showListEmptyResults = true + self.myController?.customNavigationController = navigationController + } else { + self.myChatContents = nil + self.myController = nil + } let globalChatContents = HashtagSearchGlobalChatContents(context: context, query: query, publicPosts: true) self.globalChatContents = globalChatContents @@ -371,7 +378,11 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg self.globalStorySearchContext = nil if !self.query.isEmpty { - let globalStorySearchContext = SearchStoryListContext(account: self.context.account, source: .hashtag(self.query)) + var peerId: EnginePeer.Id? + if self.controller?.mode == .chatOnly { + peerId = self.peer?.id + } + let globalStorySearchContext = SearchStoryListContext(account: self.context.account, source: .hashtag(peerId, self.query)) self.globalStorySearchDisposable.set((globalStorySearchContext.state |> deliverOnMainQueue).startStrict(next: { [weak self] state in guard let self else { @@ -429,9 +440,75 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg self.insertSubnode(self.clippingNode, at: 0) } + var currentTopInset: CGFloat = 0.0 + var globalTopInset: CGFloat = 0.0 + if let state = self.globalStorySearchState { + var parentController: ViewController? + if self.controller?.mode == .chatOnly { + parentController = self.currentController + } else { + parentController = self.globalController + } + if let parentController { + let componentView: ComponentView + var panelTransition = ComponentTransition(transition) + if let current = self.globalStorySearchComponentView { + componentView = current + } else { + panelTransition = .immediate + componentView = ComponentView() + self.globalStorySearchComponentView = componentView + } + let panelSize = componentView.update( + transition: .immediate, + component: AnyComponent(StoryResultsPanelComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + query: self.query, + peer: self.controller?.mode == .chatOnly ? self.peer : nil, + state: state, + sideInset: layout.safeInsets.left, + action: { [weak self] in + guard let self else { + return + } + var peer: EnginePeer? + if self.controller?.mode == .chatOnly { + peer = self.peer + } + let searchController = self.context.sharedContext.makeStorySearchController(context: self.context, scope: .query(peer, self.query), listContext: self.globalStorySearchContext) + self.controller?.push(searchController) + } + )), + environment: {}, + containerSize: layout.size + ) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top - 36.0), size: panelSize) + if let view = componentView.view { + if view.superview == nil { + parentController.view.addSubview(view) + view.layer.animatePosition(from: CGPoint(x: 0.0, y: -panelSize.height), to: .zero, duration: 0.25, additive: true) + } + panelTransition.setFrame(view: view, frame: panelFrame) + } + if self.controller?.mode == .chatOnly { + currentTopInset += panelSize.height + } else { + globalTopInset += panelSize.height + } + } + } else if let globalStorySearchComponentView = self.globalStorySearchComponentView { + globalStorySearchComponentView.view?.removeFromSuperview() + self.globalStorySearchComponentView = nil + } + if let controller = self.currentController { + var topInset: CGFloat = insets.top - 79.0 + topInset += currentTopInset + transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)) - controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 79.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) if controller.displayNode.supernode == nil { controller.viewWillAppear(false) @@ -454,52 +531,10 @@ final class HashtagSearchControllerNode: ASDisplayNode, ASGestureRecognizerDeleg controller.beginMessageSearch(self.query) } } - + if let controller = self.globalController { var topInset: CGFloat = insets.top - 89.0 - if let state = self.globalStorySearchState { - let componentView: ComponentView - var panelTransition = ComponentTransition(transition) - if let current = self.globalStorySearchComponentView { - componentView = current - } else { - panelTransition = .immediate - componentView = ComponentView() - self.globalStorySearchComponentView = componentView - } - let panelSize = componentView.update( - transition: .immediate, - component: AnyComponent(StoryResultsPanelComponent( - context: self.context, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - query: self.query, - state: state, - sideInset: layout.safeInsets.left, - action: { [weak self] in - guard let self else { - return - } - let searchController = self.context.sharedContext.makeStorySearchController(context: self.context, scope: .query(self.query), listContext: self.globalStorySearchContext) - self.controller?.push(searchController) - } - )), - environment: {}, - containerSize: layout.size - ) - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top - 36.0), size: panelSize) - if let view = componentView.view { - if view.superview == nil { - controller.view.addSubview(view) - view.layer.animatePosition(from: CGPoint(x: 0.0, y: -panelSize.height), to: .zero, duration: 0.25, additive: true) - } - panelTransition.setFrame(view: view, frame: panelFrame) - } - topInset += panelSize.height - } else if let globalStorySearchComponentView = self.globalStorySearchComponentView { - globalStorySearchComponentView.view?.removeFromSuperview() - self.globalStorySearchComponentView = nil - } + topInset += globalTopInset transition.updateFrame(node: controller.displayNode, frame: CGRect(origin: CGPoint(x: layout.size.width * 2.0, y: 0.0), size: layout.size)) controller.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) diff --git a/submodules/HashtagSearchUI/Sources/StoryResultsPanelComponent.swift b/submodules/HashtagSearchUI/Sources/StoryResultsPanelComponent.swift index 32a693623c..507f743db4 100644 --- a/submodules/HashtagSearchUI/Sources/StoryResultsPanelComponent.swift +++ b/submodules/HashtagSearchUI/Sources/StoryResultsPanelComponent.swift @@ -13,6 +13,7 @@ final class StoryResultsPanelComponent: CombinedComponent { let theme: PresentationTheme let strings: PresentationStrings let query: String + let peer: EnginePeer? let state: StoryListContext.State let sideInset: CGFloat let action: () -> Void @@ -22,6 +23,7 @@ final class StoryResultsPanelComponent: CombinedComponent { theme: PresentationTheme, strings: PresentationStrings, query: String, + peer: EnginePeer?, state: StoryListContext.State, sideInset: CGFloat, action: @escaping () -> Void @@ -30,6 +32,7 @@ final class StoryResultsPanelComponent: CombinedComponent { self.theme = theme self.strings = strings self.query = query + self.peer = peer self.state = state self.sideInset = sideInset self.action = action @@ -45,6 +48,9 @@ final class StoryResultsPanelComponent: CombinedComponent { if lhs.query != rhs.query { return false } + if lhs.peer != rhs.peer { + return false + } if lhs.state != rhs.state { return false } @@ -97,14 +103,31 @@ final class StoryResultsPanelComponent: CombinedComponent { transition: .immediate ) + let titleString: NSAttributedString + if let peer = component.peer, let username = peer.addressName { + let storiesString = component.strings.HashtagSearch_Stories(Int32(component.state.totalCount)) + let fullString = component.strings.HashtagSearch_LocalStoriesFound(storiesString, "@\(username)") + titleString = NSMutableAttributedString( + string: fullString.string, + font: Font.semibold(15.0), + textColor: component.theme.rootController.navigationBar.primaryTextColor, + paragraphAlignment: .natural + ) + if let lastRange = fullString.ranges.last?.range { + (titleString as? NSMutableAttributedString)?.addAttribute(NSAttributedString.Key.foregroundColor, value: component.theme.rootController.navigationBar.accentTextColor, range: lastRange) + } + } else { + titleString = NSAttributedString( + string: component.strings.HashtagSearch_StoriesFound(Int32(component.state.totalCount)), + font: Font.semibold(15.0), + textColor: component.theme.rootController.navigationBar.primaryTextColor, + paragraphAlignment: .natural + ) + } + let title = title.update( component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: component.strings.HashtagSearch_StoriesFound(Int32(component.state.totalCount)), - font: Font.semibold(15.0), - textColor: component.theme.rootController.navigationBar.primaryTextColor, - paragraphAlignment: .natural - )), + text: .plain(titleString), horizontalAlignment: .natural, maximumNumberOfLines: 1 ), diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift index 7eae8757e6..b9ccc205fa 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift @@ -14,29 +14,63 @@ public enum ItemListTextItemText { case large(String) case markdown(String) case custom(context: AccountContext, string: NSAttributedString) + + var text: String { + switch self { + case let .plain(text), let .large(text), let .markdown(text): + return text + case let .custom(_, string): + return string.string + } + } } public enum ItemListTextItemLinkAction { case tap(String) } +public enum ItemListTextItemTextAlignment { + case natural + case center + + var textAlignment: NSTextAlignment { + switch self { + case .natural: + return .natural + case .center: + return .center + } + } +} + +public enum ItemListTextItemTextSize { + case generic + case larger +} + public class ItemListTextItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let text: ItemListTextItemText public let sectionId: ItemListSectionId let linkAction: ((ItemListTextItemLinkAction) -> Void)? let style: ItemListStyle + let textSize: ItemListTextItemTextSize + let textAlignment: ItemListTextItemTextAlignment let trimBottomInset: Bool + let additionalInsets: UIEdgeInsets public let isAlwaysPlain: Bool = true public let tag: ItemListItemTag? - public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks, tag: ItemListItemTag? = nil, trimBottomInset: Bool = false) { + public init(presentationData: ItemListPresentationData, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks, textSize: ItemListTextItemTextSize = .generic, textAlignment: ItemListTextItemTextAlignment = .natural, tag: ItemListItemTag? = nil, trimBottomInset: Bool = false, additionalInsets: UIEdgeInsets = .zero) { self.presentationData = presentationData self.text = text self.sectionId = sectionId self.linkAction = linkAction self.style = style + self.textSize = textSize + self.textAlignment = textAlignment self.trimBottomInset = trimBottomInset + self.additionalInsets = additionalInsets self.tag = tag } @@ -127,13 +161,19 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { return { [weak self] item, params, neighbors in let leftInset: CGFloat = 15.0 - let topInset: CGFloat = 7.0 + var topInset: CGFloat = 7.0 var bottomInset: CGFloat = 7.0 - let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) - let largeTitleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize)) + var titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) + var textColor: UIColor = item.presentationData.theme.list.freeTextColor + if case .large = item.text { + titleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize)) + } else if case .larger = item.textSize { + titleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize / 17.0 * 15.0)) + textColor = item.presentationData.theme.list.itemSecondaryTextColor + } let titleBoldFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize) - + var themeUpdated = false var chevronImage = currentChevronImage if currentItem?.presentationData.theme !== item.presentationData.theme { @@ -145,14 +185,19 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { case let .plain(text): attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.freeTextColor) case let .large(text): - attributedText = NSAttributedString(string: text, font: largeTitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) + attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) case let .markdown(text): - let mutableAttributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.presentationData.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in + let mutableAttributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in return (TelegramTextAttributes.URL, contents) - })).mutableCopy() as! NSMutableAttributedString + }), textAlignment: item.textAlignment.textAlignment).mutableCopy() as! NSMutableAttributedString if let _ = text.range(of: ">]"), let range = mutableAttributedText.string.range(of: ">") { if themeUpdated || currentChevronImage == nil { - chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor) + switch item.textSize { + case .generic: + chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor) + case .larger: + chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: item.presentationData.theme.list.itemAccentColor) + } } if let chevronImage { mutableAttributedText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mutableAttributedText.string)) @@ -162,7 +207,7 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { case let .custom(_, string): attributedText = string } - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0 - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0 - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: item.textAlignment.textAlignment, cutout: nil, insets: UIEdgeInsets())) let contentSize: CGSize @@ -174,6 +219,10 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { default: break } + + topInset += item.additionalInsets.top + bottomInset += item.additionalInsets.bottom + contentSize = CGSize(width: params.width, height: titleLayout.size.height + topInset + bottomInset) if item.trimBottomInset { @@ -202,7 +251,15 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { } let _ = titleApply(textArguments) - strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: leftInset + params.leftInset, y: topInset), size: titleLayout.size) + let titleOrigin: CGFloat + switch item.textAlignment { + case .natural: + titleOrigin = leftInset + params.leftInset + case .center: + titleOrigin = floorToScreenPixels((contentSize.width - titleLayout.size.width) / 2.0) + } + + strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: titleOrigin, y: topInset), size: titleLayout.size) } }) } @@ -261,7 +318,7 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { } } - if let rects = rects { + if var rects { let linkHighlightingNode: LinkHighlightingNode if let current = self.linkHighlightingNode { linkHighlightingNode = current @@ -270,6 +327,10 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { self.linkHighlightingNode = linkHighlightingNode self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode) } + if item.text.text.contains(">]"), var lastRect = rects.last { + lastRect.size.width += 8.0 + rects[rects.count - 1] = lastRect + } linkHighlightingNode.frame = self.textNode.textNode.frame linkHighlightingNode.updateRects(rects) } else if let linkHighlightingNode = self.linkHighlightingNode { diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index fde42317a1..801468bc2c 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -85,7 +85,7 @@ public final class JoinLinkPreviewController: ViewController { strongSelf.controllerNode.setRequestPeer(image: invite.photoRepresentation, title: invite.title, about: invite.about, memberCount: invite.participantsCount, isGroup: !invite.flags.isBroadcast, isVerified: invite.flags.isVerified, isFake: invite.flags.isFake, isScam: invite.flags.isScam) } else { let data = JoinLinkPreviewData(isGroup: !invite.flags.isBroadcast, isJoined: false) - strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data) + strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, about: invite.about, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data) } case let .alreadyJoined(peer): strongSelf.navigateToPeer(peer, nil) diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift index 072a48a65d..7516769aef 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift @@ -392,8 +392,8 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, ASScrollVi self.setNeedsLayout() } - func setInvitePeer(image: TelegramMediaImageRepresentation?, title: String, memberCount: Int32, members: [EnginePeer], data: JoinLinkPreviewData) { - let contentNode = JoinLinkPreviewPeerContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, content: .invite(isGroup: data.isGroup, image: image, title: title, memberCount: memberCount, members: members)) + func setInvitePeer(image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, members: [EnginePeer], data: JoinLinkPreviewData) { + let contentNode = JoinLinkPreviewPeerContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, content: .invite(isGroup: data.isGroup, image: image, title: title, about: about, memberCount: memberCount, members: members)) contentNode.join = { [weak self] in self?.join?() } diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift index 60b8ea8e4c..6acb4bc232 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift @@ -32,33 +32,33 @@ private final class MoreNode: ASDisplayNode { final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainerNode { enum Content { - case invite(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, memberCount: Int32, members: [EnginePeer]) + case invite(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, members: [EnginePeer]) case request(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, isVerified: Bool, isFake: Bool, isScam: Bool) var isGroup: Bool { switch self { - case let .invite(isGroup, _, _, _, _), let .request(isGroup, _, _, _, _, _, _, _): + case let .invite(isGroup, _, _, _, _, _), let .request(isGroup, _, _, _, _, _, _, _): return isGroup } } var image: TelegramMediaImageRepresentation? { switch self { - case let .invite(_, image, _, _, _), let .request(_, image, _, _, _, _, _, _): + case let .invite(_, image, _, _, _, _), let .request(_, image, _, _, _, _, _, _): return image } } var title: String { switch self { - case let .invite(_, _, title, _, _), let .request(_, _, title, _, _, _, _, _): + case let .invite(_, _, title, _, _, _), let .request(_, _, title, _, _, _, _, _): return title } } var memberCount: Int32 { switch self { - case let .invite(_, _, _, memberCount, _), let .request(_, _, _, _, memberCount, _, _, _): + case let .invite(_, _, _, _, memberCount, _), let .request(_, _, _, _, memberCount, _, _, _): return memberCount } } @@ -138,7 +138,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: .green, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.opaqueItemBackgroundColor, avatarPlaceholderColor: theme.list.mediaPlaceholderColor) - if case let .invite(isGroup, _, _, memberCount, members) = content { + if case let .invite(isGroup, _, _, _, memberCount, members) = content { self.peerNodes = members.compactMap { peer in guard peer.id != context.account.peerId else { return nil @@ -176,7 +176,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer self.addSubnode(self.countNode) let membersString: String if content.isGroup { - if case let .invite(_, _, _, memberCount, members) = content, !members.isEmpty { + if case let .invite(_, _, _, _, memberCount, members) = content, !members.isEmpty { membersString = strings.Invitation_Members(memberCount) } else { membersString = strings.Conversation_StatusMembers(content.memberCount) @@ -195,7 +195,13 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer } self.moreNode.flatMap(self.peersScrollNode.addSubnode) - if case let .request(isGroup, _, _, about, _, _, _, _) = content { + switch content { + case let .invite(_, _, _, about, _, _): + if let about = about, !about.isEmpty { + self.aboutNode.attributedText = NSAttributedString(string: about, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) + self.addSubnode(self.aboutNode) + } + case let .request(isGroup, _, _, about, _, _, _, _): if let about = about, !about.isEmpty { self.aboutNode.attributedText = NSAttributedString(string: about, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) self.addSubnode(self.aboutNode) @@ -339,12 +345,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer transition.updateFrame(node: self.countNode, frame: CGRect(origin: CGPoint(x: floor((size.width - countSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0), size: countSize)) var verticalOffset = verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0 + countSize.height + 18.0 - - if let aboutSize = aboutSize { - transition.updateFrame(node: self.aboutNode, frame: CGRect(origin: CGPoint(x: floor((size.width - aboutSize.width) / 2.0), y: verticalOffset), size: aboutSize)) - verticalOffset += aboutSize.height + 20.0 - } - + let peerSize = CGSize(width: 85.0, height: 95.0) let peerInset: CGFloat = 10.0 @@ -367,6 +368,11 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer verticalOffset += 100.0 } + if let aboutSize = aboutSize { + transition.updateFrame(node: self.aboutNode, frame: CGRect(origin: CGPoint(x: floor((size.width - aboutSize.width) / 2.0), y: verticalOffset), size: aboutSize)) + verticalOffset += aboutSize.height + 20.0 + } + let buttonInset: CGFloat = 16.0 let actionButtonHeight = self.actionButtonNode.updateLayout(width: size.width - buttonInset * 2.0, transition: transition) transition.updateFrame(node: self.actionButtonNode, frame: CGRect(x: buttonInset, y: verticalOffset, width: size.width, height: actionButtonHeight)) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index fc01db2811..c355b93bff 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -415,8 +415,7 @@ public func legacyAttachmentMenu( let galleryTitle: String if addingMedia { - //TODO:localize - galleryTitle = "Add Photo or Video" + galleryTitle = presentationData.strings.AttachmentMenu_AddPhotoOrVideo } else { galleryTitle = editing ? presentationData.strings.Conversation_EditingMessageMediaChange : presentationData.strings.AttachmentMenu_PhotoOrVideo } @@ -436,8 +435,7 @@ public func legacyAttachmentMenu( underlyingViews.append(galleryItem) if addingMedia { - //TODO:localize - let fileItem = TGMenuSheetButtonItemView(title: "Add Document", type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in + let fileItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_AddDocument, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in controller?.dismiss(animated: true) openFileGallery() })! diff --git a/submodules/MediaPasteboardUI/BUILD b/submodules/MediaPasteboardUI/BUILD index df60617029..ced1985f41 100644 --- a/submodules/MediaPasteboardUI/BUILD +++ b/submodules/MediaPasteboardUI/BUILD @@ -19,6 +19,7 @@ swift_library( "//submodules/AccountContext:AccountContext", "//submodules/AttachmentUI:AttachmentUI", "//submodules/MediaPickerUI:MediaPickerUI", + "//submodules/AttachmentTextInputPanelNode", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift b/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift index e4aea03f6f..eaf4f3d86f 100644 --- a/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift +++ b/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift @@ -8,6 +8,7 @@ import AttachmentUI import MediaPickerUI import AccountContext import LegacyComponents +import AttachmentTextInputPanelNode public func mediaPasteboardScreen( context: AccountContext, @@ -15,9 +16,10 @@ public func mediaPasteboardScreen( peer: EnginePeer, subjects: [MediaPickerScreen.Subject.Media], presentMediaPicker: @escaping (_ subject: MediaPickerScreen.Subject, _ saveEditedPhotos: Bool, _ bannedSendPhotos: (Int32, Bool)?, _ bannedSendVideos: (Int32, Bool)?, _ present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void) -> Void, - getSourceRect: (() -> CGRect?)? = nil + getSourceRect: (() -> CGRect?)? = nil, + makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil } ) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: peer.id), buttons: [.standalone], initialButton: .standalone) + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: peer.id), buttons: [.standalone], initialButton: .standalone, makeEntityInputView: makeEntityInputView) controller.requestController = { _, present in presentMediaPicker(.media(subjects), false, nil, nil, { mediaPicker, mediaPickerContext in present(mediaPicker, mediaPickerContext) diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 39f2a74a06..3744387c63 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -3146,3 +3146,20 @@ public func callDefaultBackground() -> Signal<(TransformImageArguments) -> Drawi return context }) } + +public func solidColorImage(_ color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + return .single({ arguments in + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } + + context.withFlippedContext { c in + c.setFillColor(color.withAlphaComponent(1.0).cgColor) + c.fill(arguments.drawingRect) + } + + addCorners(context, arguments: arguments) + + return context + }) +} diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 68533e416a..293b755d92 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -55,6 +55,7 @@ swift_library( "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/Stars/StarsAvatarComponent", + "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index ed03072b36..f8b1794b5c 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -10809,15 +10809,16 @@ public extension Api.functions.stories { } } public extension Api.functions.stories { - static func searchPosts(flags: Int32, hashtag: String?, area: Api.MediaArea?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func searchPosts(flags: Int32, hashtag: String?, area: Api.MediaArea?, peer: Api.InputPeer?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1827279210) + buffer.appendInt32(-780072697) serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(hashtag!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {area!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {peer!.serialize(buffer, true)} serializeString(offset, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.searchPosts", parameters: [("flags", String(describing: flags)), ("hashtag", String(describing: hashtag)), ("area", String(describing: area)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.FoundStories? in + return (FunctionDescription(name: "stories.searchPosts", parameters: [("flags", String(describing: flags)), ("hashtag", String(describing: hashtag)), ("area", String(describing: area)), ("peer", String(describing: peer)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.FoundStories? in let reader = BufferReader(buffer) var result: Api.stories.FoundStories? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index dd47bffef6..3450b1b4a7 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 191 + return 192 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 77b650f91e..b6e2c1fd7f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -1297,7 +1297,7 @@ public final class PeerStoryListContext: StoryListContext { public final class SearchStoryListContext: StoryListContext { public enum Source { - case hashtag(String) + case hashtag(EnginePeer.Id?, String) case mediaArea(MediaArea) } @@ -1368,21 +1368,33 @@ public final class SearchStoryListContext: StoryListContext { var searchHashtag: String? = nil var area: Api.MediaArea? = nil + var peer: Signal = .single(nil) var flags: Int32 = 0 switch source { - case let .hashtag(query): + case let .hashtag(peerId, query): if query.hasPrefix("#") { searchHashtag = String(query[query.index(after: query.startIndex)...]) } else { searchHashtag = query } flags |= (1 << 0) + + if let peerId { + peer = account.postbox.transaction { transaction in + return transaction.getPeer(peerId).flatMap(apiInputPeer) + } + flags |= (1 << 2) + } case let .mediaArea(mediaArea): area = apiMediaAreasFromMediaAreas([mediaArea], transaction: nil).first flags |= (1 << 1) } - self.requestDisposable = (account.network.request(Api.functions.stories.searchPosts(flags: flags, hashtag: searchHashtag, area: area, offset: loadMoreToken, limit: Int32(limit))) + self.requestDisposable = (peer + |> castError(MTRpcError.self) + |> mapToSignal { inputPeer in + return account.network.request(Api.functions.stories.searchPosts(flags: flags, hashtag: searchHashtag, area: area, peer: inputPeer, offset: loadMoreToken, limit: Int32(limit))) + } |> map { result -> Api.stories.FoundStories? in return result } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index f6522e1c14..6feeb1003f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1545,11 +1545,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info { - if info.flags.contains(.messagesShouldHaveProfiles) { + if info.flags.contains(.messagesShouldHaveProfiles) && !item.presentationData.isPreview { var allowAuthor = incoming overrideEffectiveAuthor = true - if let author = firstMessage.author, author is TelegramChannel, !incoming || item.presentationData.isPreview { + if let author = firstMessage.author, author is TelegramChannel, !incoming { allowAuthor = true ignoreNameHiding = true } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 692c16fd51..5b51aec255 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -1260,7 +1260,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen let starsRevenueContextAndState = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> mapToSignal { peer -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in - guard let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal else { + var showStarsState = false + if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal { + showStarsState = true + } + #if DEBUG + showStarsState = "".isEmpty + #endif + + guard showStarsState else { return .single((nil, nil)) } let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift index 3cf859f663..1cdc3d2a22 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift @@ -98,8 +98,8 @@ final class StorySearchGridScreenComponent: Component { } else { let paneNodeScope: PeerInfoStoryPaneNode.Scope switch component.scope { - case let .query(query): - paneNodeScope = .search(query: query) + case let .query(peer, query): + paneNodeScope = .search(peerId: peer?.id, query: query) case let .location(coordinates, venue): paneNodeScope = .location(coordinates: coordinates, venue: venue) } @@ -273,8 +273,12 @@ public final class StorySearchGridScreen: ViewControllerComponentContainer { title = nil } switch self.scope { - case let .query(query): - self.titleView?.titleContent = .custom("\(query)", title, false) + case let .query(peer, query): + if let peer, let addressName = peer.addressName { + self.titleView?.titleContent = .custom("\(query)@\(addressName)", title, false) + } else { + self.titleView?.titleContent = .custom("\(query)", title, false) + } case .location: self.titleView?.titleContent = .custom(presentationData.strings.StoryGridScreen_TitleLocationSearch, nil, false) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 3eaa03290d..7fd1a3f52e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -1492,7 +1492,7 @@ private final class StorySearchHeaderComponent: Component { public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { public enum Scope { case peer(id: EnginePeer.Id, isSaved: Bool, isArchived: Bool) - case search(query: String) + case search(peerId: EnginePeer.Id?, query: String) case location(coordinates: MediaArea.Coordinates, venue: MediaArea.Venue) case botPreview(id: EnginePeer.Id) } @@ -1749,8 +1749,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr switch self.scope { case let .peer(id, _, isArchived): self.listSource = PeerStoryListContext(account: context.account, peerId: id, isArchived: isArchived) - case let .search(query): - self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(query)) + case let .search(peerId, query): + self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(peerId, query)) case let .location(coordinates, venue): self.listSource = SearchStoryListContext(account: context.account, source: .mediaArea(.venue(coordinates: coordinates, venue: venue))) case let .botPreview(id): diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index 85a6dd2ab3..ee82996b1c 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -28,7 +28,6 @@ final class StarsStatisticsScreenComponent: Component { let context: AccountContext let peerId: EnginePeer.Id let revenueContext: StarsRevenueStatsContext - let transactionsContext: StarsTransactionsContext let openTransaction: (StarsContext.State.Transaction) -> Void let withdraw: () -> Void let showTimeoutTooltip: (Int32) -> Void @@ -38,7 +37,6 @@ final class StarsStatisticsScreenComponent: Component { context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext, - transactionsContext: StarsTransactionsContext, openTransaction: @escaping (StarsContext.State.Transaction) -> Void, withdraw: @escaping () -> Void, showTimeoutTooltip: @escaping (Int32) -> Void, @@ -47,7 +45,6 @@ final class StarsStatisticsScreenComponent: Component { self.context = context self.peerId = peerId self.revenueContext = revenueContext - self.transactionsContext = transactionsContext self.openTransaction = openTransaction self.withdraw = withdraw self.showTimeoutTooltip = showTimeoutTooltip @@ -71,6 +68,21 @@ final class StarsStatisticsScreenComponent: Component { override func touchesShouldCancel(in view: UIView) -> Bool { return true } + + override var contentOffset: CGPoint { + set(value) { + var value = value + if value.y > self.contentSize.height - self.bounds.height { + value.y = max(0.0, self.contentSize.height - self.bounds.height) + self.bounces = false + } else { + self.bounces = true + } + super.contentOffset = value + } get { + return super.contentOffset + } + } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { @@ -124,7 +136,12 @@ final class StarsStatisticsScreenComponent: Component { private let transactionsHeader = ComponentView() private let transactionsBackground = UIView() - private let transactionsView = ComponentView() + + private let panelContainer = ComponentView() + + private var allTransactionsContext: StarsTransactionsContext? + private var incomingTransactionsContext: StarsTransactionsContext? + private var outgoingTransactionsContext: StarsTransactionsContext? private var component: StarsStatisticsScreenComponent? private weak var state: EmptyComponentState? @@ -132,6 +149,12 @@ final class StarsStatisticsScreenComponent: Component { private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)? private var controller: (() -> ViewController?)? + private var enableVelocityTracking: Bool = false + private var previousVelocityM1: CGFloat = 0.0 + private var previousVelocity: CGFloat = 0.0 + + private var listIsExpanded = false + private var ignoreScrolling: Bool = false private var stateDisposable: Disposable? @@ -198,6 +221,10 @@ final class StarsStatisticsScreenComponent: Component { self.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.scrollView.contentInset.top), animated: true) } + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.enableVelocityTracking = true + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreScrolling { self.updateScrolling(transition: .immediate) @@ -205,57 +232,128 @@ final class StarsStatisticsScreenComponent: Component { if let view = self.chartView.view as? ListItemComponentAdaptor.View, let node = view.itemNode as? StatsGraphItemNode { node.resetInteraction() } + + if self.enableVelocityTracking { + self.previousVelocityM1 = self.previousVelocity + if let value = (scrollView.value(forKey: (["_", "verticalVelocity"] as [String]).joined()) as? NSNumber)?.doubleValue { + self.previousVelocity = CGFloat(value) + } + } + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + guard let navigationMetrics = self.navigationMetrics else { + return + } + + if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { + let paneAreaExpansionFinalPoint: CGFloat = panelContainerView.frame.minY - navigationMetrics.navigationHeight + if abs(scrollView.contentOffset.y - paneAreaExpansionFinalPoint) < .ulpOfOne { + panelContainerView.transferVelocity(self.previousVelocityM1) + } + } + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + guard let _ = self.navigationMetrics else { + return + } + + let paneAreaExpansionDistance: CGFloat = 32.0 + let paneAreaExpansionFinalPoint: CGFloat = scrollView.contentSize.height - scrollView.bounds.height + if targetContentOffset.pointee.y > paneAreaExpansionFinalPoint - paneAreaExpansionDistance && targetContentOffset.pointee.y < paneAreaExpansionFinalPoint { + targetContentOffset.pointee.y = paneAreaExpansionFinalPoint + self.enableVelocityTracking = false + self.previousVelocity = 0.0 + self.previousVelocityM1 = 0.0 } } private var lastScrollBounds: CGRect? private var lastBottomOffset: CGFloat? private func updateScrolling(transition: ComponentTransition) { - guard let environment = self.environment?[ViewControllerComponentContainer.Environment.self].value else { - return - } - let scrollBounds = self.scrollView.bounds - - let topContentOffset = self.scrollView.contentOffset.y - let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset)) / 20.0 - - let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - let expansionDistance: CGFloat = 32.0 - var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance - expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) + let isLockedAtPanels = scrollBounds.maxY == self.scrollView.contentSize.height - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) - - let bottomOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) - self.lastBottomOffset = bottomOffset - - let transactionsScrollBounds: CGRect - if let transactionsView = self.transactionsView.view { - transactionsScrollBounds = CGRect(origin: CGPoint(x: 0.0, y: scrollBounds.origin.y - transactionsView.frame.minY), size: scrollBounds.size) - } else { - transactionsScrollBounds = .zero + if let _ = self.navigationMetrics { + let topContentOffset = self.scrollView.contentOffset.y + let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset)) / 20.0 + + let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) + animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) + animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) + + let expansionDistance: CGFloat = 32.0 + var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance + expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) + + transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) + if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { + panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) + } + + let listIsExpanded = expansionDistanceFactor == 0.0 + if listIsExpanded != self.listIsExpanded { + self.listIsExpanded = listIsExpanded + if !self.isUpdating { + self.state?.updated(transition: .init(animation: .curve(duration: 0.25, curve: .slide))) + } + } } - self.lastScrollBounds = transactionsScrollBounds - let _ = self.transactionsView.updateEnvironment( + let _ = self.panelContainer.updateEnvironment( transition: transition, environment: { - StarsTransactionsPanelEnvironment( - theme: environment.theme, - strings: environment.strings, - dateTimeFormat: environment.dateTimeFormat, - containerInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right), - isScrollable: false, - isCurrent: true, - externalScrollBounds: transactionsScrollBounds, - externalBottomOffset: bottomOffset - ) + StarsTransactionsPanelContainerEnvironment(isScrollable: isLockedAtPanels) } ) +// guard let environment = self.environment?[ViewControllerComponentContainer.Environment.self].value else { +// return +// } +// +// let scrollBounds = self.scrollView.bounds +// +// let topContentOffset = self.scrollView.contentOffset.y +// let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset)) / 20.0 +// +// let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) +// animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) +// animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) +// +// let expansionDistance: CGFloat = 32.0 +// var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance +// expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) +// +// transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) +// +// let bottomOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) +// self.lastBottomOffset = bottomOffset +// +// let transactionsScrollBounds: CGRect +// if let transactionsView = self.transactionsView.view { +// transactionsScrollBounds = CGRect(origin: CGPoint(x: 0.0, y: scrollBounds.origin.y - transactionsView.frame.minY), size: scrollBounds.size) +// } else { +// transactionsScrollBounds = .zero +// } +// self.lastScrollBounds = transactionsScrollBounds +// +// let _ = self.transactionsView.updateEnvironment( +// transition: transition, +// environment: { +// StarsTransactionsPanelEnvironment( +// theme: environment.theme, +// strings: environment.strings, +// dateTimeFormat: environment.dateTimeFormat, +// containerInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right), +// isScrollable: false, +// isCurrent: true, +// externalScrollBounds: transactionsScrollBounds, +// externalBottomOffset: bottomOffset +// ) +// } +// ) } private var isUpdating = false @@ -523,73 +621,118 @@ final class StarsStatisticsScreenComponent: Component { } contentHeight += balanceSize.height - contentHeight += 27.0 - - let transactionsHeaderSize = self.transactionsHeader.update( - transition: transition, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.Stars_BotRevenue_Transactions_Title.uppercased(), - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), - environment: {}, - containerSize: availableSize - ) - let transactionsHeaderFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 32.0, y: contentHeight), size: transactionsHeaderSize) - if let transactionsHeaderView = self.transactionsHeader.view { - if transactionsHeaderView.superview == nil { - self.scrollView.addSubview(transactionsHeaderView) + contentHeight += 44.0 + + var panelItems: [StarsTransactionsPanelContainerComponent.Item] = [] + if "".isEmpty { + let allTransactionsContext: StarsTransactionsContext + if let current = self.allTransactionsContext { + allTransactionsContext = current + } else { + allTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(subject: .peer(component.peerId), mode: .all) + self.allTransactionsContext = allTransactionsContext } - transition.setFrame(view: transactionsHeaderView, frame: transactionsHeaderFrame) - } - contentHeight += transactionsHeaderSize.height - contentHeight += 6.0 - - self.transactionsBackground.backgroundColor = environment.theme.list.itemBlocksBackgroundColor - self.transactionsBackground.layer.cornerRadius = 11.0 - if #available(iOS 13.0, *) { - self.transactionsBackground.layer.cornerCurve = .continuous + + let incomingTransactionsContext: StarsTransactionsContext + if let current = self.incomingTransactionsContext { + incomingTransactionsContext = current + } else { + incomingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(subject: .peer(component.peerId), mode: .incoming) + self.incomingTransactionsContext = incomingTransactionsContext + } + + let outgoingTransactionsContext: StarsTransactionsContext + if let current = self.outgoingTransactionsContext { + outgoingTransactionsContext = current + } else { + outgoingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(subject: .peer(component.peerId), mode: .outgoing) + self.outgoingTransactionsContext = outgoingTransactionsContext + } + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "all", + title: environment.strings.Stars_Intro_AllTransactions, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: allTransactionsContext, + isAccount: true, + action: { transaction in + component.openTransaction(transaction) + } + )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "incoming", + title: environment.strings.Stars_Intro_Incoming, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: incomingTransactionsContext, + isAccount: true, + action: { transaction in + component.openTransaction(transaction) + } + )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "outgoing", + title: environment.strings.Stars_Intro_Outgoing, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: outgoingTransactionsContext, + isAccount: true, + action: { transaction in + component.openTransaction(transaction) + } + )) + )) } - let transactionsSize = self.transactionsView.update( - transition: .immediate, - component: AnyComponent(StarsTransactionsListPanelComponent( - context: component.context, - transactionsContext: component.transactionsContext, - isAccount: false, - action: { transaction in - component.openTransaction(transaction) - } - )), - environment: { - StarsTransactionsPanelEnvironment( + var wasLockedAtPanels = false + if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics { + if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel { + wasLockedAtPanels = true + } + } + + let panelTransition = transition + if !panelItems.isEmpty { + let panelContainerInset: CGFloat = self.listIsExpanded ? 0.0 : 16.0 + let panelContainerCornerRadius: CGFloat = self.listIsExpanded ? 0.0 : 11.0 + + let panelContainerSize = self.panelContainer.update( + transition: panelTransition, + component: AnyComponent(StarsTransactionsPanelContainerComponent( theme: environment.theme, - strings: strings, + strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, - containerInsets: .zero, - isScrollable: false, - isCurrent: true, - externalScrollBounds: self.lastScrollBounds ?? .zero, - externalBottomOffset: self.lastBottomOffset ?? 1000 - ) - }, - containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height) - ) - self.transactionsView.parentState = state - let transactionsFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - transactionsSize.width) / 2.0), y: contentHeight), size: transactionsSize) - if let panelContainerView = self.transactionsView.view { - if panelContainerView.superview == nil { - self.scrollContainerView.addSubview(panelContainerView) + insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left + panelContainerInset, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right + panelContainerInset), + items: panelItems, + currentPanelUpdated: { [weak self] id, transition in + guard let self else { + return + } + self.currentSelectedPanelId = id + self.state?.updated(transition: transition) + } + )), + environment: { + StarsTransactionsPanelContainerEnvironment(isScrollable: wasLockedAtPanels) + }, + containerSize: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight) + ) + if let panelContainerView = self.panelContainer.view { + if panelContainerView.superview == nil { + self.scrollContainerView.addSubview(panelContainerView) + } + transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - panelContainerSize.width) / 2.0), y: contentHeight), size: panelContainerSize)) + transition.setCornerRadius(layer: panelContainerView.layer, cornerRadius: panelContainerCornerRadius) } - transition.setFrame(view: panelContainerView, frame: transactionsFrame) + contentHeight += panelContainerSize.height + } else { + self.panelContainer.view?.removeFromSuperview() } - transition.setFrame(view: self.transactionsBackground, frame: transactionsFrame) - - contentHeight += transactionsSize.height - contentHeight += 31.0 self.ignoreScrolling = true @@ -625,7 +768,6 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { private let context: AccountContext private let peerId: EnginePeer.Id private let revenueContext: StarsRevenueStatsContext - private let transactionsContext: StarsTransactionsContext private weak var tooltipScreen: UndoOverlayController? private var timer: Foundation.Timer? @@ -634,7 +776,6 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { self.context = context self.peerId = peerId self.revenueContext = revenueContext - self.transactionsContext = context.engine.payments.peerStarsTransactionsContext(subject: .peer(peerId), mode: .all) var withdrawImpl: (() -> Void)? var buyAdsImpl: (() -> Void)? @@ -644,7 +785,6 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { context: context, peerId: peerId, revenueContext: revenueContext, - transactionsContext: self.transactionsContext, openTransaction: { transaction in openTransactionImpl?(transaction) }, @@ -701,13 +841,14 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { } let controller = confirmStarsRevenueWithdrawalController(context: context, peerId: peerId, amount: amount, present: { [weak self] c, a in self?.present(c, in: .window(.root)) - }, completion: { [weak self] url in + }, completion: { url in let presentationData = context.sharedContext.currentPresentationData.with { $0 } context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) Queue.mainQueue().after(2.0) { revenueContext.reload() - self?.transactionsContext.reload() + //TODO: + //self?.transactionsContext.reload() } }) self.present(controller, in: .window(.root)) @@ -788,9 +929,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) }) } - - self.transactionsContext.loadMore() - + self.scrollToTop = { [weak self] in guard let self, let componentView = self.node.hostView.componentView as? StarsStatisticsScreenComponent.View else { return diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD index 35f40fa69f..b58ec7b9b8 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/Stars/StarsImageComponent", + "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent", "//submodules/ConfettiEffect", ], visibility = [ diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index ed5fd15396..33946e7fd7 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -19,6 +19,7 @@ import AccountContext import PresentationDataUtils import StarsImageComponent import ConfettiEffect +import PremiumPeerShortcutComponent private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -263,6 +264,8 @@ private final class SheetContent: CombinedComponent { let star = Child(StarsImageComponent.self) let closeButton = Child(Button.self) let title = Child(Text.self) + let peerShortcut = Child(PremiumPeerShortcutComponent.self) + let text = Child(BalancedTextComponent.self) let button = Child(ButtonComponent.self) let balanceTitle = Child(MultilineTextComponent.self) @@ -297,7 +300,11 @@ private final class SheetContent: CombinedComponent { if let photo = component.invoice.photo { subject = .photo(photo) } else { - subject = .transactionPeer(.peer(peer)) + if "".isEmpty { + subject = .color(.lightGray) + } else { + subject = .transactionPeer(.peer(peer)) + } } } else { subject = .none @@ -314,7 +321,8 @@ private final class SheetContent: CombinedComponent { theme: theme, diameter: 90.0, backgroundColor: theme.actionSheet.opaqueItemBackgroundColor, - icon: isSubscription ? .star : nil + icon: isSubscription && !"".isEmpty ? .star : nil, + value: "".isEmpty ? component.invoice.totalAmount : nil ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition @@ -350,7 +358,11 @@ private final class SheetContent: CombinedComponent { let titleString: String if isSubscription { - titleString = strings.Stars_Transfer_Subscribe_Channel_Title + if "".isEmpty { + titleString = "Subscription Name" + } else { + titleString = strings.Stars_Transfer_Subscribe_Channel_Title + } } else { titleString = strings.Stars_Transfer_Title } @@ -365,6 +377,25 @@ private final class SheetContent: CombinedComponent { ) contentSize.height += title.size.height contentSize.height += 13.0 + + if "".isEmpty, let peer = state.botPeer { + contentSize.height -= 3.0 + let peerShortcut = peerShortcut.update( + component: PremiumPeerShortcutComponent( + context: component.context, + theme: theme, + peer: peer + + ), + availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(peerShortcut + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + peerShortcut.size.height / 2.0)) + ) + contentSize.height += peerShortcut.size.height + contentSize.height += 13.0 + } let textFont = Font.regular(15.0) let boldTextFont = Font.semibold(15.0) @@ -377,7 +408,11 @@ private final class SheetContent: CombinedComponent { let amount = component.invoice.totalAmount let infoText: String if case .starsChatSubscription = context.component.source { - infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(amount))).string + if "".isEmpty { + infoText = "Do you want to subscribe to **Subscription Name** in **\(state.botPeer?.compactDisplayTitle ?? "")** for **\(strings.Stars_Transfer_Info_Stars(Int32(amount)))** per month?" + } else { + infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(amount))).string + } } else if !component.extendedMedia.isEmpty { var description: String = "" var photoCount: Int32 = 0 @@ -499,7 +534,9 @@ private final class SheetContent: CombinedComponent { let amountString = presentationStringsFormattedNumber(Int32(amount), presentationData.dateTimeFormat.groupingSeparator) let buttonAttributedString: NSMutableAttributedString if case .starsChatSubscription = component.source { - buttonAttributedString = NSMutableAttributedString(string: strings.Stars_Transfer_Subscribe, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + //TODO:localize + buttonAttributedString = NSMutableAttributedString(string: "Subscribe for # \(amountString) / month", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + //buttonAttributedString = NSMutableAttributedString(string: strings.Stars_Transfer_Subscribe, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) } else { buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_Pay) # \(amountString)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 58e24479c6..6ffefdbfd4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -2919,7 +2919,7 @@ final class StoryItemSetContainerSendMessage { } if !hashtag.isEmpty { if peerName == nil { - let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, scope: .query(hashtag), listContext: nil) + let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, scope: .query(nil, hashtag), listContext: nil) navigationController.pushViewController(searchController) } else { let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift index 602ed24931..9e251e8af9 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -10,6 +10,7 @@ import MediaPickerUI import MediaPasteboardUI import LegacyMediaPickerUI import MediaEditor +import ChatEntityKeyboardInputNode extension ChatControllerImpl { func displayPasteMenu(_ subjects: [MediaPickerScreen.Subject.Media]) { @@ -32,7 +33,13 @@ extension ChatControllerImpl { }) } }, - getSourceRect: nil + getSourceRect: nil, + makeEntityInputView: { [weak self] in + guard let self else { + return nil + } + return EntityInputView(context: self.context, isDark: false, areCustomEmojiEnabled: self.presentationInterfaceState.customEmojiAvailable) + } ) controller.navigationPresentation = .flatModal strongSelf.push(controller) diff --git a/submodules/TelegramUI/Sources/ChatAdPanelNode.swift b/submodules/TelegramUI/Sources/ChatAdPanelNode.swift index 01251c5b68..7c4de5a295 100644 --- a/submodules/TelegramUI/Sources/ChatAdPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatAdPanelNode.swift @@ -179,8 +179,7 @@ final class ChatAdPanelNode: ASDisplayNode { self.theme = interfaceState.theme self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor self.removeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 15.0, color: interfaceState.theme.chat.inputPanel.panelControlAccentColor.withMultipliedAlpha(0.1)) - //TODO:localize - self.removeTextNode.attributedText = NSAttributedString(string: "remove", font: Font.regular(11.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor) + self.removeTextNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_BotAd_Remove, font: Font.regular(11.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor) } self.contextContainer.isGestureEnabled = false @@ -331,8 +330,7 @@ final class ChatAdPanelNode: ASDisplayNode { let textConstrainedSize = CGSize(width: width - contentLeftInset - contentRightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude) - //TODO:localize - let (adLayout, adApply) = makeAdLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Ad", font: Font.semibold(14.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: .zero)) + let (adLayout, adApply) = makeAdLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Chat_BotAd_Title, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: .zero)) var titleText: String = "" if let author = message.author { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4cd76b306d..088842b981 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9577,7 +9577,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if case let .customChatContents(contents) = self.subject, case let .hashTagSearch(publicPostsValue) = contents.kind { publicPosts = publicPostsValue } - let searchController = HashtagSearchController(context: self.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, mode: .chatOnly, publicPosts: publicPosts) + let searchController = HashtagSearchController(context: self.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, mode: peerName != nil ? .chatOnly : .generic, publicPosts: publicPosts) self.effectiveNavigationController?.pushViewController(searchController) } })) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index e33af7813a..76ef4d9905 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2337,10 +2337,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions) giftController.navigationPresentation = .modal controller?.push(giftController) - - if case .chatList = source, let _ = currentBirthdays { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone() - } } }) diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index a07d619330..d13355b17b 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -697,23 +697,6 @@ public func patternColor(for color: UIColor, intensity: CGFloat, prominent: Bool return .black } -public func solidColorImage(_ color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - return .single({ arguments in - guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { - return nil - } - - context.withFlippedContext { c in - c.setFillColor(color.withAlphaComponent(1.0).cgColor) - c.fill(arguments.drawingRect) - } - - addCorners(context, arguments: arguments) - - return context - }) -} - public func drawWallpaperGradientImage(_ colors: [UIColor], rotation: Int32? = nil, context: CGContext, size: CGSize) { guard !colors.isEmpty else { return diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 2dc2e7e10e..cd9ab2fd67 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -604,7 +604,7 @@ public final class WebAppController: ViewController, AttachmentContainable { scrollInset.bottom = 0.0 } - let frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - frameBottomInset))) + let frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight - frameBottomInset))) var bottomInset = layout.intrinsicInsets.bottom + layout.additionalInsets.bottom if let inputHeight = self.validLayout?.0.inputHeight, inputHeight > 44.0 { @@ -642,6 +642,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } else { webView.customBottomInset = layout.intrinsicInsets.bottom } + webView.customSideInset = layout.safeInsets.left } if let placeholderNode = self.placeholderNode { diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index 4e7f2ca134..20956490af 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -91,6 +91,14 @@ function tgBrowserDisconnectObserver() { final class WebAppWebView: WKWebView { var handleScriptMessage: (WKScriptMessage) -> Void = { _ in } + + var customSideInset: CGFloat = 0.0 { + didSet { + if self.customSideInset != oldValue { + self.setNeedsLayout() + } + } + } var customBottomInset: CGFloat = 0.0 { didSet { @@ -101,7 +109,7 @@ final class WebAppWebView: WKWebView { } override var safeAreaInsets: UIEdgeInsets { - return UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.customBottomInset, right: 0.0) + return UIEdgeInsets(top: 0.0, left: self.customSideInset, bottom: self.customBottomInset, right: self.customSideInset) } init(account: Account) { diff --git a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh index 0bd88402a1..31bfa57c0a 100755 --- a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh +++ b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh @@ -48,7 +48,7 @@ CONFIGURE_FLAGS="--enable-cross-compile --disable-programs \ --enable-libvpx \ --enable-audiotoolbox \ --enable-bsf=aac_adtstoasc,vp9_superframe,h264_mp4toannexb \ - --enable-decoder=h264,libvpx_vp9,hevc,libopus,mp3,aac,flac,alac_at,pcm_s16le,pcm_s24le,pcm_f32le,gsm_ms_at \ + --enable-decoder=h264,libvpx_vp9,hevc,libopus,mp3,aac,flac,alac_at,pcm_s16le,pcm_s24le,pcm_f32le,gsm_ms_at,vorbis \ --enable-encoder=libvpx_vp9,aac_at \ --enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska,mpegts \ --enable-parser=aac,h264,mp3,libopus \