Various improvements

This commit is contained in:
Ilya Laktyushin 2024-10-19 20:24:02 +04:00
parent cc6f7882f1
commit 3fe6f7a069
36 changed files with 673 additions and 277 deletions

View File

@ -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";

View File

@ -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)
}

View File

@ -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: {

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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<Empty>
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<Empty>
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)

View File

@ -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
),

View File

@ -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 {

View File

@ -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)

View File

@ -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?()
}

View File

@ -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))

View File

@ -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()
})!

View File

@ -19,6 +19,7 @@ swift_library(
"//submodules/AccountContext:AccountContext",
"//submodules/AttachmentUI:AttachmentUI",
"//submodules/MediaPickerUI:MediaPickerUI",
"//submodules/AttachmentTextInputPanelNode",
],
visibility = [
"//visibility:public",

View File

@ -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)

View File

@ -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
})
}

View File

@ -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",

View File

@ -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<Api.stories.FoundStories>) {
static func searchPosts(flags: Int32, hashtag: String?, area: Api.MediaArea?, peer: Api.InputPeer?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.FoundStories>) {
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() {

View File

@ -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! {

View File

@ -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<Api.InputPeer?, NoError> = .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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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):

View File

@ -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<Empty>()
private let transactionsBackground = UIView()
private let transactionsView = ComponentView<StarsTransactionsPanelEnvironment>()
private let panelContainer = ComponentView<StarsTransactionsPanelContainerEnvironment>()
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<CGPoint>) {
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

View File

@ -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 = [

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}
}))

View File

@ -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()
}
}
})

View File

@ -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

View File

@ -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 {

View File

@ -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) {

View File

@ -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 \