mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Various improvements
This commit is contained in:
parent
cc6f7882f1
commit
3fe6f7a069
@ -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";
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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?()
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
})!
|
||||
|
@ -19,6 +19,7 @@ swift_library(
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AttachmentUI:AttachmentUI",
|
||||
"//submodules/MediaPickerUI:MediaPickerUI",
|
||||
"//submodules/AttachmentTextInputPanelNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
|
@ -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! {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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 = [
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}))
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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 \
|
||||
|
Loading…
x
Reference in New Issue
Block a user