mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Stories privacy improvements
This commit is contained in:
parent
f485065435
commit
89cdac66e1
@ -366,7 +366,7 @@ public final class TelegramUser: Peer, Equatable {
|
||||
}
|
||||
|
||||
public func withUpdatedFlags(_ flags: UserInfoFlags) -> TelegramUser {
|
||||
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: self.flags, emojiStatus: emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
|
||||
return TelegramUser(id: self.id, accessHash: self.accessHash, firstName: self.firstName, lastName: self.lastName, username: self.username, phone: self.phone, photo: self.photo, botInfo: self.botInfo, restrictionInfo: self.restrictionInfo, flags: flags, emojiStatus: self.emojiStatus, usernames: self.usernames, storiesHidden: self.storiesHidden)
|
||||
}
|
||||
|
||||
public func withUpdatedStoriesHidden(_ storiesHidden: Bool?) -> TelegramUser {
|
||||
|
@ -625,7 +625,11 @@ private func apiInputPrivacyRules(privacy: EngineStoryPrivacy, transaction: Tran
|
||||
}
|
||||
}
|
||||
if !privacyUsers.isEmpty {
|
||||
privacyRules.append(.inputPrivacyValueAllowUsers(users: privacyUsers))
|
||||
if case .contacts = privacy.base {
|
||||
privacyRules.append(.inputPrivacyValueDisallowUsers(users: privacyUsers))
|
||||
} else {
|
||||
privacyRules.append(.inputPrivacyValueAllowUsers(users: privacyUsers))
|
||||
}
|
||||
}
|
||||
if !privacyChats.isEmpty {
|
||||
privacyRules.append(.inputPrivacyValueAllowChatParticipants(chats: privacyChats))
|
||||
|
@ -7,10 +7,7 @@ import PersistentStringHash
|
||||
import Postbox
|
||||
import AccountContext
|
||||
|
||||
public enum MediaEditorResultPrivacy: Codable, Equatable {
|
||||
case story(privacy: EngineStoryPrivacy, timeout: Int, archive: Bool)
|
||||
case message(peers: [EnginePeer.Id], timeout: Int?)
|
||||
|
||||
public struct MediaEditorResultPrivacy: Codable, Equatable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case privacy
|
||||
@ -19,37 +16,34 @@ public enum MediaEditorResultPrivacy: Codable, Equatable {
|
||||
case archive
|
||||
}
|
||||
|
||||
public let privacy: EngineStoryPrivacy
|
||||
public let timeout: Int
|
||||
public let archive: Bool
|
||||
|
||||
public init(
|
||||
privacy: EngineStoryPrivacy,
|
||||
timeout: Int,
|
||||
archive: Bool
|
||||
) {
|
||||
self.privacy = privacy
|
||||
self.timeout = timeout
|
||||
self.archive = archive
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let privacy = try container.decodeIfPresent(EngineStoryPrivacy.self, forKey: .privacy) {
|
||||
let timeout = try container.decode(Int32.self, forKey: .timeout)
|
||||
let archive = try container.decode(Bool.self, forKey: .archive)
|
||||
self = .story(privacy: privacy, timeout: Int(timeout), archive: archive)
|
||||
} else if let peers = try container.decodeIfPresent([EnginePeer.Id].self, forKey: .peers) {
|
||||
let timeout = try container.decodeIfPresent(Int32.self, forKey: .timeout)
|
||||
self = .message(peers: peers, timeout: timeout.flatMap { Int($0) })
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy)
|
||||
self.timeout = Int(try container.decode(Int32.self, forKey: .timeout))
|
||||
self.archive = try container.decode(Bool.self, forKey: .archive)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case let .story(privacy, timeout, archive):
|
||||
try container.encode(privacy, forKey: .privacy)
|
||||
try container.encode(Int32(timeout), forKey: .timeout)
|
||||
try container.encode(archive, forKey: .archive)
|
||||
case let .message(peers, timeout):
|
||||
try container.encode(peers, forKey: .peers)
|
||||
if let timeout {
|
||||
try container.encode(Int32(timeout), forKey: .timeout)
|
||||
} else {
|
||||
try container.encodeNil(forKey: .timeout)
|
||||
}
|
||||
}
|
||||
|
||||
try container.encode(privacy, forKey: .privacy)
|
||||
try container.encode(Int32(timeout), forKey: .timeout)
|
||||
try container.encode(archive, forKey: .archive)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -675,20 +675,11 @@ final class MediaEditorScreenComponent: Component {
|
||||
image: state.image(.done),
|
||||
size: CGSize(width: 33.0, height: 33.0)
|
||||
)),
|
||||
action: { [weak self] in
|
||||
guard let self, let controller = environment.controller() as? MediaEditorScreen else {
|
||||
action: {
|
||||
guard let controller = environment.controller() as? MediaEditorScreen else {
|
||||
return
|
||||
}
|
||||
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
|
||||
return
|
||||
}
|
||||
var inputText = NSAttributedString(string: "")
|
||||
switch inputPanelView.getSendMessageInput() {
|
||||
case let .text(text):
|
||||
inputText = text
|
||||
}
|
||||
|
||||
controller.requestCompletion(caption: inputText, animated: true)
|
||||
controller.requestCompletion(animated: true)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
@ -879,29 +870,23 @@ final class MediaEditorScreenComponent: Component {
|
||||
|
||||
var timeoutValue: String
|
||||
let timeoutSelected: Bool
|
||||
switch component.privacy {
|
||||
case let .story(_, timeout, archive):
|
||||
switch timeout {
|
||||
case 21600:
|
||||
timeoutValue = "6"
|
||||
case 43200:
|
||||
timeoutValue = "12"
|
||||
case 86400:
|
||||
timeoutValue = "24"
|
||||
case 172800:
|
||||
timeoutValue = "48"
|
||||
default:
|
||||
timeoutValue = "24"
|
||||
}
|
||||
if archive {
|
||||
timeoutValue = "∞"
|
||||
}
|
||||
timeoutSelected = false
|
||||
case let .message(_, timeout):
|
||||
timeoutValue = "\(timeout ?? 1)"
|
||||
timeoutSelected = timeout != nil
|
||||
switch component.privacy.timeout {
|
||||
case 21600:
|
||||
timeoutValue = "6"
|
||||
case 43200:
|
||||
timeoutValue = "12"
|
||||
case 86400:
|
||||
timeoutValue = "24"
|
||||
case 172800:
|
||||
timeoutValue = "48"
|
||||
default:
|
||||
timeoutValue = "24"
|
||||
}
|
||||
|
||||
if component.privacy.archive {
|
||||
timeoutValue = "∞"
|
||||
}
|
||||
timeoutSelected = false
|
||||
|
||||
var inputPanelAvailableWidth = previewSize.width
|
||||
var inputPanelAvailableHeight = 115.0
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
@ -1067,24 +1052,15 @@ final class MediaEditorScreenComponent: Component {
|
||||
}
|
||||
|
||||
let privacyText: String
|
||||
switch component.privacy {
|
||||
case let .story(privacy, _, _):
|
||||
switch privacy.base {
|
||||
case .everyone:
|
||||
privacyText = "Everyone"
|
||||
case .closeFriends:
|
||||
privacyText = "Close Friends"
|
||||
case .contacts:
|
||||
privacyText = "Contacts"
|
||||
case .nobody:
|
||||
privacyText = "Selected Contacts"
|
||||
}
|
||||
case let .message(peerIds, _):
|
||||
if peerIds.count == 1 {
|
||||
privacyText = "1 Recipient"
|
||||
} else {
|
||||
privacyText = "\(peerIds.count) Recipients"
|
||||
}
|
||||
switch component.privacy.privacy.base {
|
||||
case .everyone:
|
||||
privacyText = "Everyone"
|
||||
case .closeFriends:
|
||||
privacyText = "Close Friends"
|
||||
case .contacts:
|
||||
privacyText = "Contacts"
|
||||
case .nobody:
|
||||
privacyText = "Selected Contacts"
|
||||
}
|
||||
|
||||
let displayTopButtons = !(self.inputPanelExternalState.isEditing || isEditingTextEntity || component.isDisplayingTool)
|
||||
@ -1519,7 +1495,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
|
||||
struct State {
|
||||
var privacy: MediaEditorResultPrivacy = .story(privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 86400, archive: false)
|
||||
var privacy: MediaEditorResultPrivacy = MediaEditorResultPrivacy(privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 86400, archive: false)
|
||||
}
|
||||
|
||||
var state = State() {
|
||||
@ -2158,18 +2134,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
}
|
||||
|
||||
if finished, case .message = controller.state.privacy {
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateOut(to: .camera)
|
||||
}
|
||||
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||
transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in
|
||||
completion()
|
||||
if let view = self.entitiesView.getView(where: { $0 is DrawingMediaEntityView }) as? DrawingMediaEntityView {
|
||||
view.previewView = nil
|
||||
}
|
||||
})
|
||||
} else if let transitionOut = controller.transitionOut(finished, isNew), let destinationView = transitionOut.destinationView {
|
||||
if isNew == true {
|
||||
self.entitiesView.seek(to: 0.0)
|
||||
}
|
||||
|
||||
if let transitionOut = controller.transitionOut(finished, isNew), let destinationView = transitionOut.destinationView {
|
||||
var destinationTransitionView: UIView?
|
||||
if !finished {
|
||||
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage, isNew != true {
|
||||
@ -2436,7 +2405,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
|
||||
private weak var storyArchiveTooltip: ViewController?
|
||||
func presentStoryArchiveTooltip(sourceView: UIView) {
|
||||
guard let controller = self.controller, case let .story(_, _, archive) = controller.state.privacy else {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -2450,7 +2419,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 5.0), size: CGSize())
|
||||
|
||||
let text: String
|
||||
if archive {
|
||||
if controller.state.privacy.archive {
|
||||
text = "Story will be kept on your page."
|
||||
} else {
|
||||
text = "Story will disappear in 24 hours."
|
||||
@ -2900,66 +2869,48 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
self.displayNode.view.addInteraction(dropInteraction)
|
||||
}
|
||||
|
||||
func openPrivacySettings() {
|
||||
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil) {
|
||||
self.hapticFeedback.impact(.light)
|
||||
|
||||
if case .message(_, _) = self.state.privacy {
|
||||
self.openSendAsMessage()
|
||||
} else {
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .stories)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var archive = true
|
||||
var timeout: Int = 86400
|
||||
let initialPrivacy: EngineStoryPrivacy
|
||||
if case let .story(privacy, timeoutValue, archiveValue) = self.state.privacy {
|
||||
initialPrivacy = privacy
|
||||
timeout = timeoutValue
|
||||
archive = archiveValue
|
||||
} else {
|
||||
initialPrivacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: [])
|
||||
}
|
||||
|
||||
self.push(
|
||||
ShareWithPeersScreen(
|
||||
context: self.context,
|
||||
initialPrivacy: initialPrivacy,
|
||||
stateContext: stateContext,
|
||||
completion: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state.privacy = .story(privacy: privacy, timeout: timeout, archive: archive)
|
||||
},
|
||||
editCategory: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openEditCategory(privacy: privacy, completion: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state.privacy = .story(privacy: privacy, timeout: timeout, archive: archive)
|
||||
self.openPrivacySettings()
|
||||
})
|
||||
},
|
||||
secondaryAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openSendAsMessage()
|
||||
let privacy = privacy ?? self.state.privacy
|
||||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .stories, initialPeerIds: Set(privacy.privacy.additionallyIncludePeers))
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let initialPrivacy = privacy.privacy
|
||||
let timeout = privacy.timeout
|
||||
let archive = privacy.archive
|
||||
self.push(
|
||||
ShareWithPeersScreen(
|
||||
context: self.context,
|
||||
initialPrivacy: initialPrivacy,
|
||||
stateContext: stateContext,
|
||||
completion: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
)
|
||||
self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive)
|
||||
},
|
||||
editCategory: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openEditCategory(privacy: privacy, completion: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openPrivacySettings(MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive))
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private func openEditCategory(privacy: EngineStoryPrivacy, completion: @escaping (EngineStoryPrivacy) -> Void) {
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .contacts(privacy.base))
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .contacts(privacy.base), initialPeerIds: Set(privacy.additionallyIncludePeers))
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
@ -2979,42 +2930,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
completion(result)
|
||||
},
|
||||
editCategory: { _ in },
|
||||
secondaryAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openSendAsMessage()
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private func openSendAsMessage() {
|
||||
var initialPeerIds = Set<EnginePeer.Id>()
|
||||
if case let .message(peers, _) = self.state.privacy {
|
||||
initialPeerIds = Set(peers)
|
||||
}
|
||||
let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .chats, initialPeerIds: initialPeerIds)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.push(
|
||||
ShareWithPeersScreen(
|
||||
context: self.context,
|
||||
initialPrivacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []),
|
||||
stateContext: stateContext,
|
||||
completion: { [weak self] privacy in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state.privacy = .message(peers: privacy.additionallyIncludePeers, timeout: nil)
|
||||
},
|
||||
editCategory: { _ in },
|
||||
secondaryAction: {}
|
||||
editCategory: { _ in }
|
||||
)
|
||||
)
|
||||
})
|
||||
@ -3029,133 +2945,80 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch self.state.privacy {
|
||||
case let .story(privacy, _, _):
|
||||
self.state.privacy = .story(privacy: privacy, timeout: timeout ?? 86400, archive: archive)
|
||||
case let .message(peers, _):
|
||||
self.state.privacy = .message(peers: peers, timeout: timeout)
|
||||
}
|
||||
self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, archive: archive)
|
||||
}
|
||||
|
||||
var currentValue: Int?
|
||||
var currentArchived = false
|
||||
|
||||
let title = "Choose how long the story will be visible."
|
||||
let currentValue = self.state.privacy.timeout
|
||||
let currentArchived = self.state.privacy.archive
|
||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
let title: String
|
||||
switch self.state.privacy {
|
||||
case let .story(_, timeoutValue, archivedValue):
|
||||
title = "Choose how long the story will be visible."
|
||||
currentValue = timeoutValue
|
||||
currentArchived = archivedValue
|
||||
case let .message(_, timeoutValue):
|
||||
title = "Choose how long the media will be kept after opening."
|
||||
currentValue = timeoutValue
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||
|
||||
switch self.state.privacy {
|
||||
case .story:
|
||||
items.append(.action(ContextMenuActionItem(text: "6 Hours", icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
return currentValue == 3600 * 6 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
if hasPremium {
|
||||
updateTimeout(3600 * 6, false)
|
||||
} else {
|
||||
self?.presentTimeoutPremiumSuggestion(3600 * 6)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "12 Hours", icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
return currentValue == 3600 * 12 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
if hasPremium {
|
||||
updateTimeout(3600 * 12, false)
|
||||
} else {
|
||||
self?.presentTimeoutPremiumSuggestion(3600 * 12)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "24 Hours", icon: { theme in
|
||||
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
return currentValue == 86400 * 2 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
if hasPremium {
|
||||
updateTimeout(86400 * 2, false)
|
||||
} else {
|
||||
self?.presentTimeoutPremiumSuggestion(86400 * 2)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Keep Always", icon: { theme in
|
||||
return currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400, true)
|
||||
})))
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: "Select 'Keep Always' to show the story on your page.", textLayout: .multiline, textFont: .small, icon: { theme in
|
||||
return nil
|
||||
}, action: { _, _ in
|
||||
})))
|
||||
case .message:
|
||||
items.append(.action(ContextMenuActionItem(text: "Until First View", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(1, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(3, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(10, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "1 Minute", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(60, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Keep Always", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(nil, false)
|
||||
})))
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: "6 Hours", icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
return currentValue == 3600 * 6 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
if hasPremium {
|
||||
updateTimeout(3600 * 6, false)
|
||||
} else {
|
||||
self?.presentTimeoutPremiumSuggestion(3600 * 6)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "12 Hours", icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
return currentValue == 3600 * 12 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
if hasPremium {
|
||||
updateTimeout(3600 * 12, false)
|
||||
} else {
|
||||
self?.presentTimeoutPremiumSuggestion(3600 * 12)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "24 Hours", icon: { theme in
|
||||
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400, false)
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
return currentValue == 86400 * 2 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
if hasPremium {
|
||||
updateTimeout(86400 * 2, false)
|
||||
} else {
|
||||
self?.presentTimeoutPremiumSuggestion(86400 * 2)
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Keep Always", icon: { theme in
|
||||
return currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
updateTimeout(86400, true)
|
||||
})))
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: "Select 'Keep Always' to show the story on your page.", textLayout: .multiline, textFont: .small, icon: { theme in
|
||||
return nil
|
||||
}, action: { _, _ in
|
||||
})))
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
||||
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
@ -3251,6 +3114,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
})
|
||||
}
|
||||
|
||||
private func getCaption() -> NSAttributedString {
|
||||
return (self.node.componentHost.view as? MediaEditorScreenComponent.View)?.getInputText() ?? NSAttributedString()
|
||||
}
|
||||
|
||||
private func saveDraft(id: Int64?) {
|
||||
guard let subject = self.node.subject, let values = self.node.mediaEditor?.values else {
|
||||
return
|
||||
@ -3258,7 +3125,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
try? FileManager.default.createDirectory(atPath: draftPath(), withIntermediateDirectories: true)
|
||||
|
||||
let privacy = self.state.privacy
|
||||
let caption = (self.node.componentHost.view as? MediaEditorScreenComponent.View)?.getInputText() ?? NSAttributedString()
|
||||
let caption = self.getCaption()
|
||||
|
||||
if let resultImage = self.node.mediaEditor?.resultImage {
|
||||
self.node.mediaEditor?.seek(0.0, andPlay: false)
|
||||
@ -3330,7 +3197,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
|
||||
private var didComplete = false
|
||||
func requestCompletion(caption: NSAttributedString, animated: Bool) {
|
||||
func requestCompletion(animated: Bool) {
|
||||
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject, !self.didComplete else {
|
||||
return
|
||||
}
|
||||
@ -3351,13 +3218,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||
|
||||
let caption = self.getCaption()
|
||||
|
||||
let randomId: Int64
|
||||
if case let .draft(_, id) = subject, let id {
|
||||
randomId = id
|
||||
} else {
|
||||
randomId = Int64.random(in: .min ... .max)
|
||||
}
|
||||
|
||||
if mediaEditor.resultIsVideo {
|
||||
var firstFrame: Signal<UIImage?, NoError>
|
||||
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
|
||||
|
@ -519,19 +519,11 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch categoryId {
|
||||
case .everyone:
|
||||
if self.selectedCategories.contains(categoryId) {
|
||||
} else {
|
||||
self.selectedCategories.removeAll()
|
||||
self.selectedCategories.insert(categoryId)
|
||||
}
|
||||
case .contacts, .closeFriends, .selectedContacts:
|
||||
if self.selectedCategories.contains(categoryId) {
|
||||
} else {
|
||||
self.selectedCategories.removeAll()
|
||||
self.selectedCategories.insert(categoryId)
|
||||
}
|
||||
if self.selectedCategories.contains(categoryId) {
|
||||
} else {
|
||||
self.selectedPeers = []
|
||||
self.selectedCategories.removeAll()
|
||||
self.selectedCategories.insert(categoryId)
|
||||
}
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
||||
},
|
||||
@ -1189,6 +1181,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
self.stateValue = state
|
||||
self.stateSubject.set(.single(state))
|
||||
self.readySubject.set(true)
|
||||
self.initialPeerIds = initialPeerIds
|
||||
case .chats:
|
||||
self.stateDisposable = (context.engine.messages.chatList(group: .root, count: 200)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] chatList in
|
||||
@ -1237,16 +1230,23 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
selectedPeers.append(peer)
|
||||
}
|
||||
}
|
||||
selectedPeers = selectedPeers.sorted(by: { lhs, rhs in
|
||||
let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: .firstLast)
|
||||
if result == .orderedSame {
|
||||
return lhs.id < rhs.id
|
||||
} else {
|
||||
return result == .orderedAscending
|
||||
}
|
||||
})
|
||||
self.initialPeerIds = Set(selectedPeers.map { $0.id })
|
||||
} else {
|
||||
for peer in contactList.peers {
|
||||
if case let .user(user) = peer, initialPeerIds.contains(user.id) {
|
||||
selectedPeers.append(peer)
|
||||
}
|
||||
}
|
||||
self.initialPeerIds = initialPeerIds
|
||||
}
|
||||
selectedPeers = selectedPeers.sorted(by: { lhs, rhs in
|
||||
let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: .firstLast)
|
||||
if result == .orderedSame {
|
||||
return lhs.id < rhs.id
|
||||
} else {
|
||||
return result == .orderedAscending
|
||||
}
|
||||
})
|
||||
|
||||
var peers: [EnginePeer] = []
|
||||
peers = contactList.peers.filter { !self.initialPeerIds.contains($0.id) && $0.id != context.account.peerId }.sorted(by: { lhs, rhs in
|
||||
|
@ -2165,8 +2165,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
completion: { [weak self] _, mediaResult, privacy, commit in
|
||||
switch mediaResult {
|
||||
case let .image(image, dimensions, caption):
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.6), case let .story(storyPrivacy, _, _) = privacy {
|
||||
let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: caption?.string ?? "", entities: [], privacy: storyPrivacy)
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.6) {
|
||||
let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: caption?.string ?? "", entities: [], privacy: privacy.privacy)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
switch result {
|
||||
case let .progress(progress):
|
||||
|
@ -358,92 +358,11 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
chatListController.scrollToStories()
|
||||
switch mediaResult {
|
||||
case let .image(image, dimensions, caption):
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.6) {
|
||||
switch privacy {
|
||||
case let .story(storyPrivacy, period, pin):
|
||||
let text = caption ?? NSAttributedString()
|
||||
let entities = generateChatInputTextEntities(text)
|
||||
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: text.string, entities: entities, pin: pin, privacy: storyPrivacy, period: period, randomId: randomId)
|
||||
|
||||
/*let _ = (self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption?.string ?? "", entities: [], pin: pin, privacy: storyPrivacy, period: period, randomId: randomId)
|
||||
|> deliverOnMainQueue).start(next: { [weak chatListController] result in
|
||||
if let chatListController {
|
||||
switch result {
|
||||
case let .progress(progress):
|
||||
chatListController.updateStoryUploadProgress(progress)
|
||||
case let .completed(id):
|
||||
if let id {
|
||||
moveStorySource(engine: context.engine, from: randomId, to: Int64(id))
|
||||
}
|
||||
Queue.mainQueue().after(0.2) {
|
||||
chatListController.updateStoryUploadProgress(nil)
|
||||
}
|
||||
|
||||
let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image, title: nil, text: "Story successfully uploaded", round: false, undoText: "View"), elevatedLayout: false, action: { action in
|
||||
switch action {
|
||||
case .undo:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return true
|
||||
})
|
||||
chatListController.present(undoOverlayController, in: .current)
|
||||
}
|
||||
}
|
||||
})*/
|
||||
Queue.mainQueue().justDispatch {
|
||||
commit({})
|
||||
}
|
||||
case let .message(peerIds, timeout):
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
let tempFilePath = NSTemporaryDirectory() + "\(randomId).jpg"
|
||||
let _ = try? imageData.write(to: URL(fileURLWithPath: tempFilePath))
|
||||
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath, randomId: randomId)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
let imageFlags: TelegramMediaImageFlags = []
|
||||
|
||||
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags)
|
||||
if let timeout, timeout > 0 && timeout <= 60 {
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timeout), countdownBeginTime: nil))
|
||||
}
|
||||
|
||||
let text = trimChatInputText(convertMarkdownToAttributes(caption ?? NSAttributedString()))
|
||||
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
|
||||
if !entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:]
|
||||
text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in
|
||||
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
if let file = value.file {
|
||||
if let packId = value.interactivelySelectedFromPackId {
|
||||
bubbleUpEmojiOrStickersetsById[file.fileId.id] = packId
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
|
||||
for entity in entities {
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
if let packId = bubbleUpEmojiOrStickersetsById[fileId] {
|
||||
if !bubbleUpEmojiOrStickersets.contains(packId) {
|
||||
bubbleUpEmojiOrStickersets.append(packId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = enqueueMessagesToMultiplePeers(
|
||||
account: self.context.account,
|
||||
peerIds: peerIds, threadIds: [:],
|
||||
messages: [.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)]).start()
|
||||
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7) {
|
||||
let text = caption ?? NSAttributedString()
|
||||
let entities = generateChatInputTextEntities(text)
|
||||
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: text.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
|
||||
Queue.mainQueue().justDispatch {
|
||||
commit({})
|
||||
}
|
||||
}
|
||||
@ -463,46 +382,11 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
case let .asset(localIdentifier):
|
||||
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
|
||||
}
|
||||
|
||||
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
|
||||
|
||||
if case let .story(storyPrivacy, period, pin) = privacy {
|
||||
let text = caption ?? NSAttributedString()
|
||||
let entities = generateChatInputTextEntities(text)
|
||||
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: text.string, entities: entities, pin: pin, privacy: storyPrivacy, period: period, randomId: randomId)
|
||||
/*let _ = (self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource), text: caption?.string ?? "", entities: [], pin: pin, privacy: storyPrivacy, period: period, randomId: randomId)
|
||||
|> deliverOnMainQueue).start(next: { [weak chatListController] result in
|
||||
if let chatListController {
|
||||
switch result {
|
||||
case let .progress(progress):
|
||||
chatListController.updateStoryUploadProgress(progress)
|
||||
case let .completed(id):
|
||||
if let id {
|
||||
moveStorySource(engine: context.engine, from: randomId, to: Int64(id))
|
||||
}
|
||||
Queue.mainQueue().after(0.2) {
|
||||
chatListController.updateStoryUploadProgress(nil)
|
||||
}
|
||||
|
||||
if let image {
|
||||
let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image, title: nil, text: "Story successfully uploaded", round: false, undoText: "View"), elevatedLayout: false, action: { action in
|
||||
switch action {
|
||||
case .undo:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return true
|
||||
})
|
||||
chatListController.present(undoOverlayController, in: .current)
|
||||
}
|
||||
}
|
||||
}
|
||||
})*/
|
||||
Queue.mainQueue().justDispatch {
|
||||
commit({})
|
||||
}
|
||||
} else {
|
||||
let text = caption ?? NSAttributedString()
|
||||
let entities = generateChatInputTextEntities(text)
|
||||
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: text.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
|
||||
Queue.mainQueue().justDispatch {
|
||||
commit({})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user