mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
855dc9c9c0
commit
d6e97be56b
@ -6911,7 +6911,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"SponsoredMessageMenu.Info" = "What are sponsored\nmessages?";
|
||||
"SponsoredMessageInfoScreen.Title" = "What are sponsored messages?";
|
||||
"SponsoredMessageInfoScreen.Text" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.\n\nUnline other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can't spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible adverticers at:\n[url]\nAds should no longer be synonymous with abuse of user privacy. Let us redefine how a tech compony should operate — together.";
|
||||
"SponsoredMessageInfoScreen.MarkdownText" = "Unlike other apps, Telegram never uses your private data to target ads. [Learn more in the Privacy Policy](https://telegram.org/privacy#5-6-no-ads-based-on-user-data)\nYou are seeing this message only because someone chose this public one-to many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.\n\nUnline other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can't spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible adverticers at:\n[url]\nAds should no longer be synonymous with abuse of user privacy. Let us redefine how a tech compony should operate — together.";
|
||||
"SponsoredMessageInfo.Action" = "Learn More";
|
||||
"SponsoredMessageInfo.Url" = "https://telegram.org/ads";
|
||||
|
||||
|
@ -17,6 +17,7 @@ swift_library(
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/Markdown",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -7,6 +7,7 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import Markdown
|
||||
|
||||
public final class AdInfoScreen: ViewController {
|
||||
private final class Node: ViewControllerTracingNode {
|
||||
@ -84,9 +85,16 @@ public final class AdInfoScreen: ViewController {
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
var openUrl: (() -> Void)?
|
||||
var openUrl: ((String) -> Void)?
|
||||
|
||||
#if DEBUG && false
|
||||
let rawText = "First Line\n**Bold Text** [Description](http://google.com) text\n[url]\nabcdee"
|
||||
#else
|
||||
let rawText = self.presentationData.strings.SponsoredMessageInfoScreen_MarkdownText
|
||||
#endif
|
||||
|
||||
let defaultUrl = self.presentationData.strings.SponsoredMessageInfo_Url
|
||||
|
||||
let rawText = self.presentationData.strings.SponsoredMessageInfoScreen_Text
|
||||
var items: [Item] = []
|
||||
var didAddUrl = false
|
||||
for component in rawText.components(separatedBy: "[url]") {
|
||||
@ -100,20 +108,40 @@ public final class AdInfoScreen: ViewController {
|
||||
|
||||
let textNode = ImmediateTextNode()
|
||||
textNode.maximumNumberOfLines = 0
|
||||
textNode.attributedText = NSAttributedString(string: itemText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
textNode.attributedText = parseMarkdownIntoAttributedString(itemText, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
))
|
||||
items.append(.text(textNode))
|
||||
textNode.highlightAttributeAction = { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
return NSAttributedString.Key(rawValue: "URL")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
textNode.tapAttributeAction = { attributes, _ in
|
||||
if let value = attributes[NSAttributedString.Key(rawValue: "URL")] as? String {
|
||||
openUrl?(value)
|
||||
}
|
||||
}
|
||||
textNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5)
|
||||
|
||||
if !didAddUrl {
|
||||
didAddUrl = true
|
||||
items.append(.link(LinkNode(text: self.presentationData.strings.SponsoredMessageInfo_Url, color: self.presentationData.theme.list.itemAccentColor, action: {
|
||||
openUrl?()
|
||||
openUrl?(defaultUrl)
|
||||
})))
|
||||
}
|
||||
}
|
||||
if !didAddUrl {
|
||||
didAddUrl = true
|
||||
items.append(.link(LinkNode(text: self.presentationData.strings.SponsoredMessageInfo_Url, color: self.presentationData.theme.list.itemAccentColor, action: {
|
||||
openUrl?()
|
||||
openUrl?(defaultUrl)
|
||||
})))
|
||||
}
|
||||
self.items = items
|
||||
@ -133,11 +161,11 @@ public final class AdInfoScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
openUrl = { [weak self] in
|
||||
openUrl = { [weak self] url in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.context.sharedContext.applicationBindings.openUrl(strongSelf.presentationData.strings.SponsoredMessageInfo_Url)
|
||||
strongSelf.context.sharedContext.applicationBindings.openUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
self.contentOffset = offset
|
||||
self.contentOffsetChanged(offset: offset)
|
||||
|
||||
if self.contactListNode.listNode.isTracking {
|
||||
/*if self.contactListNode.listNode.isTracking {
|
||||
if case let .known(value) = offset {
|
||||
if !self.storiesUnlocked {
|
||||
if value < -40.0 {
|
||||
@ -220,7 +220,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
self.contactListNode.contentScrollingEnded = { [weak self] listView in
|
||||
@ -280,43 +280,18 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
private func contentScrollingEnded(listView: ListView) -> Bool {
|
||||
if "".isEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
/*if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
|
||||
if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View {
|
||||
if let clippedScrollOffset = navigationBarComponentView.clippedScrollOffset {
|
||||
if navigationBarComponentView.effectiveStoriesInsetHeight > 0.0 {
|
||||
if clippedScrollOffset > 0.0 && clippedScrollOffset < navigationBarComponentView.effectiveStoriesInsetHeight {
|
||||
if clippedScrollOffset < navigationBarComponentView.effectiveStoriesInsetHeight * 0.5 {
|
||||
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
|
||||
} else {
|
||||
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight, animated: true)
|
||||
}
|
||||
return true
|
||||
if clippedScrollOffset > 0.0 && clippedScrollOffset < ChatListNavigationBar.searchScrollHeight {
|
||||
if clippedScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
|
||||
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
|
||||
} else {
|
||||
let searchScrollOffset = clippedScrollOffset - navigationBarComponentView.effectiveStoriesInsetHeight
|
||||
if searchScrollOffset > 0.0 && searchScrollOffset < ChatListNavigationBar.searchScrollHeight {
|
||||
if searchScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
|
||||
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight, animated: true)
|
||||
} else {
|
||||
let _ = listView.scrollToOffsetFromTop(navigationBarComponentView.effectiveStoriesInsetHeight + ChatListNavigationBar.searchScrollHeight, animated: true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if clippedScrollOffset > 0.0 && clippedScrollOffset < ChatListNavigationBar.searchScrollHeight {
|
||||
if clippedScrollOffset < ChatListNavigationBar.searchScrollHeight * 0.5 {
|
||||
let _ = listView.scrollToOffsetFromTop(0.0, animated: true)
|
||||
} else {
|
||||
let _ = listView.scrollToOffsetFromTop(ChatListNavigationBar.searchScrollHeight, animated: true)
|
||||
}
|
||||
return true
|
||||
let _ = listView.scrollToOffsetFromTop(ChatListNavigationBar.searchScrollHeight, animated: true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -1235,7 +1235,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
|
||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound {
|
||||
if !self.stackFromBottom {
|
||||
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top)
|
||||
if !keepMinimalScrollHeightWithTopInset.isZero {
|
||||
completeHeight = max(completeHeight, self.visibleSize.height + effectiveInsets.top + effectiveInsets.bottom)
|
||||
}
|
||||
//completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top)
|
||||
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||
} else {
|
||||
effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - completeHeight)
|
||||
@ -1647,7 +1650,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
|
||||
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset {
|
||||
if !self.stackFromBottom {
|
||||
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
|
||||
if !keepMinimalScrollHeightWithTopInset.isZero {
|
||||
completeHeight = max(completeHeight, self.visibleSize.height + effectiveInsets.top + effectiveInsets.bottom)
|
||||
}
|
||||
//completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
|
||||
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
|
||||
}
|
||||
}
|
||||
|
@ -580,7 +580,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
public let topShadowNode: ASImageNode
|
||||
public let bottomShadowNode: ASImageNode
|
||||
|
||||
public var storyParams: (peer: EnginePeer, items: [EngineStoryItem], count: Int, hasUnseen: Bool)?
|
||||
public var storyParams: (peer: EnginePeer, items: [EngineStoryItem], count: Int, hasUnseen: Bool, hasUnseenPrivate: Bool)?
|
||||
private var expandedStorySetIndicator: ComponentView<Empty>?
|
||||
public var expandedStorySetIndicatorTransitionView: (UIView, CGRect)? {
|
||||
if let setView = self.expandedStorySetIndicator?.view as? StorySetIndicatorComponent.View {
|
||||
@ -1268,6 +1268,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
peer: storyParams.peer,
|
||||
items: storyParams.items,
|
||||
hasUnseen: storyParams.hasUnseen,
|
||||
hasUnseenPrivate: storyParams.hasUnseenPrivate,
|
||||
totalCount: storyParams.count,
|
||||
theme: defaultDarkPresentationTheme,
|
||||
action: { [weak self] in
|
||||
|
@ -1074,6 +1074,10 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
for id in removeIds {
|
||||
if let item = self.visibleItems.removeValue(forKey: id) {
|
||||
if let blurLayer = item.blurLayer {
|
||||
item.blurLayer = nil
|
||||
blurLayer.removeFromSuperlayer()
|
||||
}
|
||||
if let layer = item.layer {
|
||||
items.itemBinding.unbindLayer(layer: layer)
|
||||
layer.removeFromSuperlayer()
|
||||
|
@ -607,7 +607,11 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
|
||||
if let firstFrameFile = firstFrameFile {
|
||||
account.postbox.mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "first-frame", keepDuration: .general, tempFile: firstFrameFile)
|
||||
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: firstFrameFile.path), options: .mappedIfSafe) {
|
||||
let localResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: nil, isSecretRelated: false)
|
||||
account.postbox.mediaBox.storeResourceData(localResource.id, data: data)
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: localResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
||||
}
|
||||
}
|
||||
|
||||
let fileMedia = TelegramMediaFile(
|
||||
|
@ -544,9 +544,9 @@ public final class PeerStoryListContext {
|
||||
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputUser)
|
||||
}
|
||||
|> mapToSignal { inputUser -> Signal<([EngineStoryItem], Int, PeerReference?), NoError> in
|
||||
|> mapToSignal { inputUser -> Signal<([EngineStoryItem], Int, PeerReference?, Bool), NoError> in
|
||||
guard let inputUser = inputUser else {
|
||||
return .single(([], 0, nil))
|
||||
return .single(([], 0, nil, false))
|
||||
}
|
||||
|
||||
let signal: Signal<Api.stories.Stories, MTRpcError>
|
||||
@ -562,18 +562,20 @@ public final class PeerStoryListContext {
|
||||
|> `catch` { _ -> Signal<Api.stories.Stories?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<([EngineStoryItem], Int, PeerReference?), NoError> in
|
||||
|> mapToSignal { result -> Signal<([EngineStoryItem], Int, PeerReference?, Bool), NoError> in
|
||||
guard let result = result else {
|
||||
return .single(([], 0, nil))
|
||||
return .single(([], 0, nil, false))
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> ([EngineStoryItem], Int, PeerReference?) in
|
||||
return account.postbox.transaction { transaction -> ([EngineStoryItem], Int, PeerReference?, Bool) in
|
||||
var storyItems: [EngineStoryItem] = []
|
||||
var totalCount: Int = 0
|
||||
var hasMore: Bool = false
|
||||
|
||||
switch result {
|
||||
case let .stories(count, stories, users):
|
||||
totalCount = Int(count)
|
||||
hasMore = stories.count >= 100
|
||||
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users))
|
||||
|
||||
@ -619,11 +621,11 @@ public final class PeerStoryListContext {
|
||||
}
|
||||
}
|
||||
|
||||
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init))
|
||||
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init), hasMore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] storyItems, totalCount, peerReference in
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] storyItems, totalCount, peerReference, hasMore in
|
||||
guard let `self` = self else {
|
||||
return
|
||||
}
|
||||
@ -650,7 +652,11 @@ public final class PeerStoryListContext {
|
||||
updatedState.peerReference = peerReference
|
||||
}
|
||||
|
||||
updatedState.loadMoreToken = (storyItems.last?.id).flatMap(Int.init)
|
||||
if hasMore {
|
||||
updatedState.loadMoreToken = (storyItems.last?.id).flatMap(Int.init)
|
||||
} else {
|
||||
updatedState.loadMoreToken = nil
|
||||
}
|
||||
if updatedState.loadMoreToken != nil {
|
||||
updatedState.totalCount = max(totalCount, updatedState.items.count)
|
||||
} else {
|
||||
|
@ -26,6 +26,8 @@ swift_library(
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
||||
"//submodules/TelegramUI/Components/MoreHeaderButton",
|
||||
"//submodules/TelegramUI/Components/MediaEditorScreen",
|
||||
"//submodules/SaveToCameraRoll",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -14,6 +14,8 @@ import ChatTitleView
|
||||
import BottomButtonPanelComponent
|
||||
import UndoUI
|
||||
import MoreHeaderButton
|
||||
import MediaEditorScreen
|
||||
import SaveToCameraRoll
|
||||
|
||||
final class PeerInfoStoryGridScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -93,11 +95,11 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = component
|
||||
self.saveSelected()
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: strings.Common_Delete, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
@ -279,6 +281,72 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func saveSelected() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let component = self.component, let peer else {
|
||||
return
|
||||
}
|
||||
guard let peerReference = PeerReference(peer._asPeer()) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let paneNode = self.paneNode, !paneNode.selectedIds.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
var signals: [Signal<Float, NoError>] = []
|
||||
let sortedItems = paneNode.selectedItems.sorted(by: { lhs, rhs in return lhs.key < rhs.key })
|
||||
if sortedItems.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let saveScreen = SaveProgressScreen(context: component.context, content: .progress("Saving", 0.0))
|
||||
self.environment?.controller()?.present(saveScreen, in: .current)
|
||||
|
||||
let valueNorm: Float = 1.0 / Float(sortedItems.count)
|
||||
var progressStart: Float = 0.0
|
||||
for (_, item) in sortedItems {
|
||||
let itemOffset = progressStart
|
||||
progressStart += valueNorm
|
||||
signals.append(saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .other, mediaReference: .story(peer: peerReference, id: item.id, media: item.media._asMedia()))
|
||||
|> map { progress -> Float in
|
||||
return itemOffset + progress * valueNorm
|
||||
})
|
||||
}
|
||||
|
||||
var allSignal: Signal<Float, NoError> = .single(0.0)
|
||||
for signal in signals {
|
||||
allSignal = allSignal |> then(signal)
|
||||
}
|
||||
|
||||
let disposable = (allSignal
|
||||
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
|
||||
guard let saveScreen else {
|
||||
return
|
||||
}
|
||||
saveScreen.content = .progress("Saving", progress)
|
||||
}, completed: { [weak saveScreen] in
|
||||
guard let saveScreen else {
|
||||
return
|
||||
}
|
||||
saveScreen.content = .completion("Saved")
|
||||
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
||||
saveScreen?.dismiss()
|
||||
})
|
||||
})
|
||||
|
||||
saveScreen.cancelled = {
|
||||
disposable.dispose()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func update(component: PeerInfoStoryGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
@ -1487,29 +1487,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
|
||||
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
|
||||
//TODO:load more
|
||||
/*guard let anchor = anchor as? VisualMediaHoleAnchor else {
|
||||
return .never()
|
||||
}
|
||||
let mappedDirection: SparseMessageList.LoadHoleDirection
|
||||
switch location {
|
||||
case .around:
|
||||
mappedDirection = .around
|
||||
case .toLower:
|
||||
mappedDirection = .later
|
||||
case .toUpper:
|
||||
mappedDirection = .earlier
|
||||
}
|
||||
let listSource = self.listSource
|
||||
return Signal { subscriber in
|
||||
listSource.loadHole(anchor: anchor.messageId, direction: mappedDirection, completion: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
return Signal { _ in
|
||||
listSource.loadMore()
|
||||
|
||||
return EmptyDisposable
|
||||
}*/
|
||||
|
||||
return .never()
|
||||
}
|
||||
|> runOn(.mainQueue())
|
||||
}
|
||||
|
||||
public func updateContentType(contentType: ContentType) {
|
||||
@ -1575,7 +1559,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
let timezoneOffset = Int32(TimeZone.current.secondsFromGMT())
|
||||
|
||||
var mappedItems: [SparseItemGrid.Item] = []
|
||||
let mappedHoles: [SparseItemGrid.HoleAnchor] = []
|
||||
var mappedHoles: [SparseItemGrid.HoleAnchor] = []
|
||||
var totalCount: Int = 0
|
||||
if let peerReference = state.peerReference {
|
||||
for item in state.items {
|
||||
@ -1586,6 +1570,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
localMonthTimestamp: Month(localTimestamp: item.timestamp + timezoneOffset).packedValue
|
||||
))
|
||||
}
|
||||
if mappedItems.count < state.totalCount, let lastItem = state.items.last {
|
||||
mappedHoles.append(VisualMediaHoleAnchor(index: mappedItems.count, storyId: 1, localMonthTimestamp: Month(localTimestamp: lastItem.timestamp + timezoneOffset).packedValue))
|
||||
}
|
||||
}
|
||||
totalCount = state.totalCount
|
||||
totalCount = max(mappedItems.count, totalCount)
|
||||
|
@ -1247,6 +1247,10 @@ private final class StoryContainerScreenComponent: Component {
|
||||
} else if slice.previousItemId != nil {
|
||||
component.content.navigate(navigation: .item(.previous))
|
||||
} else if let environment = self.environment {
|
||||
if let sourceIsAvatar = component.transitionIn?.sourceIsAvatar, sourceIsAvatar {
|
||||
} else {
|
||||
self.dismissWithoutTransitionOut = true
|
||||
}
|
||||
environment.controller()?.dismiss()
|
||||
}
|
||||
|
||||
|
@ -1750,7 +1750,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.sendMessageContext.videoRecorderValue?.dismissVideo()
|
||||
self.sendMessageContext.discardMediaRecordingPreview(view: self)
|
||||
},
|
||||
attachmentAction: { [weak self] in
|
||||
attachmentAction: component.slice.peer.isService ? nil : { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -3635,7 +3635,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.requestSave()
|
||||
})))
|
||||
|
||||
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) {
|
||||
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) {
|
||||
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
@ -3773,6 +3773,46 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) {
|
||||
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] link in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if let link {
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
component.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .linkCopied(text: "Link copied."),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), nil)
|
||||
}
|
||||
})
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.sendMessageContext.performShareAction(view: self)
|
||||
})))
|
||||
}
|
||||
|
||||
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings())
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in
|
||||
@ -3824,7 +3864,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
isHidden = storiesHidden
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: isHidden ? "Unhide \(component.slice.peer.compactDisplayTitle)" : "Hide \(component.slice.peer.compactDisplayTitle)", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: isHidden ? "Unarchive" : "Archive", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/MoveToChats" : "Chat/Context Menu/MoveToContacts"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3864,6 +3904,25 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
component.controller()?.present(tooltipScreen, in: .current)
|
||||
})))
|
||||
|
||||
#if DEBUG
|
||||
let saveText: String
|
||||
if case .file = component.slice.item.storyItem.media {
|
||||
saveText = "Save Video"
|
||||
} else {
|
||||
saveText = "Save Image"
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.requestSave()
|
||||
})))
|
||||
#endif
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Report", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, a in
|
||||
|
@ -2353,7 +2353,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
})
|
||||
}
|
||||
|
||||
func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) {
|
||||
func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, navigation: ChatControllerInteractionNavigateToPeer = .info, sourceMessageId: MessageId? = nil) {
|
||||
guard let component = view.component, let parentController = component.controller() else {
|
||||
return
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ public final class StorySetIndicatorComponent: Component {
|
||||
public let peer: EnginePeer
|
||||
public let items: [EngineStoryItem]
|
||||
public let hasUnseen: Bool
|
||||
public let hasUnseenPrivate: Bool
|
||||
public let totalCount: Int
|
||||
public let theme: PresentationTheme
|
||||
public let action: () -> Void
|
||||
@ -101,6 +102,7 @@ public final class StorySetIndicatorComponent: Component {
|
||||
peer: EnginePeer,
|
||||
items: [EngineStoryItem],
|
||||
hasUnseen: Bool,
|
||||
hasUnseenPrivate: Bool,
|
||||
totalCount: Int,
|
||||
theme: PresentationTheme,
|
||||
action: @escaping () -> Void
|
||||
@ -109,6 +111,7 @@ public final class StorySetIndicatorComponent: Component {
|
||||
self.peer = peer
|
||||
self.items = items
|
||||
self.hasUnseen = hasUnseen
|
||||
self.hasUnseenPrivate = hasUnseenPrivate
|
||||
self.totalCount = totalCount
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
@ -121,6 +124,9 @@ public final class StorySetIndicatorComponent: Component {
|
||||
if lhs.hasUnseen != rhs.hasUnseen {
|
||||
return false
|
||||
}
|
||||
if lhs.hasUnseenPrivate != rhs.hasUnseenPrivate {
|
||||
return false
|
||||
}
|
||||
if lhs.totalCount != rhs.totalCount {
|
||||
return false
|
||||
}
|
||||
@ -349,7 +355,9 @@ public final class StorySetIndicatorComponent: Component {
|
||||
|
||||
let borderColors: [UInt32]
|
||||
|
||||
if component.hasUnseen {
|
||||
if component.hasUnseenPrivate {
|
||||
borderColors = [component.theme.chatList.storyUnseenPrivateColors.topColor.argb, component.theme.chatList.storyUnseenPrivateColors.bottomColor.argb]
|
||||
} else if component.hasUnseen {
|
||||
borderColors = [component.theme.chatList.storyUnseenColors.topColor.argb, component.theme.chatList.storyUnseenColors.bottomColor.argb]
|
||||
} else {
|
||||
borderColors = [UIColor(white: 1.0, alpha: 0.3).argb, UIColor(white: 1.0, alpha: 0.3).argb]
|
||||
|
@ -457,9 +457,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
|
||||
}, iconSource: nil, action: { _, f in
|
||||
/*c.dismiss(completion: {
|
||||
controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context))
|
||||
})*/
|
||||
f(.dismissWithoutContent)
|
||||
controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context))
|
||||
})))
|
||||
@ -625,6 +622,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
|
||||
for media in messages[0].media {
|
||||
if let story = media as? TelegramMediaStory {
|
||||
if let story = message.associatedStories[story.storyId], story.data.isEmpty {
|
||||
canPin = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var loadStickerSaveStatusSignal: Signal<Bool?, NoError> = .single(nil)
|
||||
if let loadStickerSaveStatus = loadStickerSaveStatus {
|
||||
loadStickerSaveStatusSignal = context.engine.stickers.isStickerSaved(id: loadStickerSaveStatus)
|
||||
@ -1943,6 +1948,10 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
}
|
||||
} else if let action = media as? TelegramMediaAction, case .phoneCall = action.action {
|
||||
optionsMap[id]!.insert(.rateCall)
|
||||
} else if let story = media as? TelegramMediaStory {
|
||||
if let story = message.associatedStories[story.storyId], story.data.isEmpty {
|
||||
isShareProtected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
@ -1962,7 +1971,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
optionsMap[id]!.insert(.deleteLocally)
|
||||
}
|
||||
} else if id.peerId == accountPeerId {
|
||||
if !(message.flags.isSending || message.flags.contains(.Failed)) {
|
||||
if !(message.flags.isSending || message.flags.contains(.Failed)) && !isShareProtected {
|
||||
optionsMap[id]!.insert(.forward)
|
||||
}
|
||||
optionsMap[id]!.insert(.deleteLocally)
|
||||
@ -2006,7 +2015,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
banPeer = nil
|
||||
}
|
||||
}
|
||||
if !message.containsSecretMedia && !isAction {
|
||||
if !message.containsSecretMedia && !isAction && !isShareProtected {
|
||||
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.isCopyProtected() {
|
||||
if !(message.flags.isSending || message.flags.contains(.Failed)) {
|
||||
optionsMap[id]!.insert(.forward)
|
||||
@ -2023,7 +2032,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia {
|
||||
if !isAction && !message.isCopyProtected() {
|
||||
if !isAction && !message.isCopyProtected() && !isShareProtected {
|
||||
if !(message.flags.isSending || message.flags.contains(.Failed)) {
|
||||
optionsMap[id]!.insert(.forward)
|
||||
}
|
||||
@ -2057,7 +2066,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
}
|
||||
}
|
||||
} else if let user = peer as? TelegramUser {
|
||||
if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies && !message.isCopyProtected() {
|
||||
if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies && !message.isCopyProtected() && !isShareProtected {
|
||||
if !(message.flags.isSending || message.flags.contains(.Failed)) {
|
||||
optionsMap[id]!.insert(.forward)
|
||||
}
|
||||
|
@ -536,7 +536,11 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
if let backgroundNode = self.backgroundNode, backgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
if let item = self.item, item.message.media.contains(where: { $0 is TelegramMediaStory }) {
|
||||
return .none
|
||||
} else {
|
||||
return .openMessage
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
|
@ -3903,7 +3903,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
case .placeholder:
|
||||
return nil
|
||||
}
|
||||
}, state.items.count, state.hasUnseen)
|
||||
}, state.items.count, state.hasUnseen, state.hasUnseenCloseFriends)
|
||||
}
|
||||
|
||||
self.requestLayout(animated: false)
|
||||
|
Loading…
x
Reference in New Issue
Block a user