mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge commit '4e597a50f08c9f9c40c52dcea6d95a271ce71d53'
This commit is contained in:
commit
e08c3402b5
@ -5305,6 +5305,7 @@ Sorry for the inconvenience.";
|
|||||||
"Stats.LanguagesTitle" = "LANGUAGES";
|
"Stats.LanguagesTitle" = "LANGUAGES";
|
||||||
"Stats.PostsTitle" = "RECENT POSTS";
|
"Stats.PostsTitle" = "RECENT POSTS";
|
||||||
|
|
||||||
|
"Stats.MessageViews.NoViews" = "No views";
|
||||||
"Stats.MessageViews_0" = "%@ views";
|
"Stats.MessageViews_0" = "%@ views";
|
||||||
"Stats.MessageViews_1" = "%@ view";
|
"Stats.MessageViews_1" = "%@ view";
|
||||||
"Stats.MessageViews_2" = "%@ views";
|
"Stats.MessageViews_2" = "%@ views";
|
||||||
@ -10608,3 +10609,8 @@ Sorry for the inconvenience.";
|
|||||||
"Chat.RemoveWallpaper.Title" = "Remove Wallpaper";
|
"Chat.RemoveWallpaper.Title" = "Remove Wallpaper";
|
||||||
"Chat.RemoveWallpaper.Text" = "Are you sure you want to reset the wallpaper?";
|
"Chat.RemoveWallpaper.Text" = "Are you sure you want to reset the wallpaper?";
|
||||||
"Chat.RemoveWallpaper.Remove" = "Remove";
|
"Chat.RemoveWallpaper.Remove" = "Remove";
|
||||||
|
|
||||||
|
"MediaEditor.Shortcut.Image" = "Image";
|
||||||
|
"MediaEditor.Shortcut.Location" = "Location";
|
||||||
|
"MediaEditor.Shortcut.Reaction" = "Reaction";
|
||||||
|
"MediaEditor.Shortcut.Audio" = "Audio";
|
||||||
|
@ -30,7 +30,17 @@ private struct BoostState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
.storiesChannelBoost(peer: peer, boostSubject: .stories, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount, canBoostAgain: canBoostAgain),
|
.storiesChannelBoost(
|
||||||
|
peer: peer,
|
||||||
|
boostSubject: .stories,
|
||||||
|
isCurrent: isCurrent,
|
||||||
|
level: currentLevel,
|
||||||
|
currentLevelBoosts: currentLevelBoosts,
|
||||||
|
nextLevelBoosts: nextLevelBoosts,
|
||||||
|
link: nil,
|
||||||
|
myBoostCount: myBoostCount,
|
||||||
|
canBoostAgain: canBoostAgain
|
||||||
|
),
|
||||||
boosts
|
boosts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -160,9 +170,10 @@ public func PremiumBoostScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: {
|
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: {
|
||||||
let _ = combineLatest(queue: Queue.mainQueue(),
|
let _ = combineLatest(
|
||||||
context.engine.peers.getChannelBoostStatus(peerId: peerId),
|
queue: Queue.mainQueue(),
|
||||||
context.engine.peers.getMyBoostStatus()
|
context.engine.peers.getChannelBoostStatus(peerId: peerId),
|
||||||
|
context.engine.peers.getMyBoostStatus()
|
||||||
).startStandalone(next: { boostStatus, myBoostStatus in
|
).startStandalone(next: { boostStatus, myBoostStatus in
|
||||||
dismissReplaceImpl?()
|
dismissReplaceImpl?()
|
||||||
PremiumBoostScreen(context: context, contentContext: contentContext, peerId: peerId, isCurrent: isCurrent, status: boostStatus, myBoostStatus: myBoostStatus, replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), forceDark: forceDark, openPeer: openPeer, presentController: presentController, pushController: pushController, dismissed: dismissed)
|
PremiumBoostScreen(context: context, contentContext: contentContext, peerId: peerId, isCurrent: isCurrent, status: boostStatus, myBoostStatus: myBoostStatus, replacedBoosts: (Int32(slots.count), Int32(channelIds.count)), forceDark: forceDark, openPeer: openPeer, presentController: presentController, pushController: pushController, dismissed: dismissed)
|
||||||
|
@ -1446,7 +1446,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ChannelStatsContextExtractedContentSource: ContextExtractedContentSource {
|
final class ChannelStatsContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
var keepInPlace: Bool
|
var keepInPlace: Bool
|
||||||
let ignoreContentTouches: Bool = true
|
let ignoreContentTouches: Bool = true
|
||||||
let blurBackground: Bool = true
|
let blurBackground: Bool = true
|
||||||
|
@ -16,18 +16,21 @@ import PresentationDataUtils
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import GraphUI
|
import GraphUI
|
||||||
import StoryContainerScreen
|
import StoryContainerScreen
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private final class MessageStatsControllerArguments {
|
private final class MessageStatsControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
||||||
let openMessage: (EngineMessage.Id) -> Void
|
let openMessage: (EngineMessage.Id) -> Void
|
||||||
let openStory: (EnginePeer.Id, EngineStoryItem, UIView) -> Void
|
let openStory: (EnginePeer.Id, EngineStoryItem, UIView) -> Void
|
||||||
|
let storyContextAction: (EnginePeer.Id, ASDisplayNode, ContextGesture?, Bool) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (EngineMessage.Id) -> Void, openStory: @escaping (EnginePeer.Id, EngineStoryItem, UIView) -> Void) {
|
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (EngineMessage.Id) -> Void, openStory: @escaping (EnginePeer.Id, EngineStoryItem, UIView) -> Void, storyContextAction: @escaping (EnginePeer.Id, ASDisplayNode, ContextGesture?, Bool) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.loadDetailedGraph = loadDetailedGraph
|
self.loadDetailedGraph = loadDetailedGraph
|
||||||
self.openMessage = openMessage
|
self.openMessage = openMessage
|
||||||
self.openStory = openStory
|
self.openStory = openStory
|
||||||
|
self.storyContextAction = storyContextAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +174,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
var forwards: Int32 = 0
|
var forwards: Int32 = 0
|
||||||
var reactions: Int32 = 0
|
var reactions: Int32 = 0
|
||||||
|
|
||||||
|
var isStory = false
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
switch item {
|
switch item {
|
||||||
case let .message(message):
|
case let .message(message):
|
||||||
@ -191,6 +195,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
views = Int32(story.views?.seenCount ?? 0)
|
views = Int32(story.views?.seenCount ?? 0)
|
||||||
forwards = Int32(story.views?.forwardCount ?? 0)
|
forwards = Int32(story.views?.forwardCount ?? 0)
|
||||||
reactions = Int32(story.views?.reactedCount ?? 0)
|
reactions = Int32(story.views?.reactedCount ?? 0)
|
||||||
|
isStory = true
|
||||||
}
|
}
|
||||||
return StatsMessageItem(context: arguments.context, presentationData: presentationData, peer: peer, item: item, views: views, reactions: reactions, forwards: forwards, isPeer: true, sectionId: self.section, style: .blocks, action: {
|
return StatsMessageItem(context: arguments.context, presentationData: presentationData, peer: peer, item: item, views: views, reactions: reactions, forwards: forwards, isPeer: true, sectionId: self.section, style: .blocks, action: {
|
||||||
switch item {
|
switch item {
|
||||||
@ -203,7 +208,9 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
if case let .story(peer, story) = item {
|
if case let .story(peer, story) = item {
|
||||||
arguments.openStory(peer.id, story, view)
|
arguments.openStory(peer.id, story, view)
|
||||||
}
|
}
|
||||||
}, contextAction: nil)
|
}, contextAction: { node, gesture in
|
||||||
|
arguments.storyContextAction(peer.id, node, gesture, !isStory)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,6 +312,7 @@ public func messageStatsController(context: AccountContext, updatedPresentationD
|
|||||||
let dataSignal: Signal<PostStats?, NoError>
|
let dataSignal: Signal<PostStats?, NoError>
|
||||||
var loadDetailedGraphImpl: ((StatsGraph, Int64) -> Signal<StatsGraph?, NoError>)?
|
var loadDetailedGraphImpl: ((StatsGraph, Int64) -> Signal<StatsGraph?, NoError>)?
|
||||||
var openStoryImpl: ((EnginePeer.Id, EngineStoryItem, UIView) -> Void)?
|
var openStoryImpl: ((EnginePeer.Id, EngineStoryItem, UIView) -> Void)?
|
||||||
|
var storyContextActionImpl: ((EnginePeer.Id, ASDisplayNode, ContextGesture?, Bool) -> Void)?
|
||||||
|
|
||||||
var forwardsContext: StoryStatsPublicForwardsContext?
|
var forwardsContext: StoryStatsPublicForwardsContext?
|
||||||
let peerId: EnginePeer.Id
|
let peerId: EnginePeer.Id
|
||||||
@ -373,6 +381,8 @@ public func messageStatsController(context: AccountContext, updatedPresentationD
|
|||||||
navigateToMessageImpl?(messageId)
|
navigateToMessageImpl?(messageId)
|
||||||
}, openStory: { peerId, story, view in
|
}, openStory: { peerId, story, view in
|
||||||
openStoryImpl?(peerId, story, view)
|
openStoryImpl?(peerId, story, view)
|
||||||
|
}, storyContextAction: { peerId, node, gesture, isMessage in
|
||||||
|
storyContextActionImpl?(peerId, node, gesture, isMessage)
|
||||||
})
|
})
|
||||||
|
|
||||||
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
|
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
|
||||||
@ -534,5 +544,50 @@ public func messageStatsController(context: AccountContext, updatedPresentationD
|
|||||||
controller.push(storyContainerScreen)
|
controller.push(storyContainerScreen)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
storyContextActionImpl = { [weak controller] peerId, sourceNode, gesture, isMessage in
|
||||||
|
guard let controller = controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let iconName: String
|
||||||
|
if isMessage {
|
||||||
|
title = presentationData.strings.Conversation_ViewInChannel
|
||||||
|
iconName = "Chat/Context Menu/GoToMessage"
|
||||||
|
} else {
|
||||||
|
if peerId.isGroupOrChannel {
|
||||||
|
title = presentationData.strings.ChatList_ContextOpenChannel
|
||||||
|
iconName = "Chat/Context Menu/Channels"
|
||||||
|
} else {
|
||||||
|
title = presentationData.strings.Conversation_ContextMenuOpenProfile
|
||||||
|
iconName = "Chat/Context Menu/User"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: title, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: iconName), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
|
||||||
|
c.dismiss(completion: {
|
||||||
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||||
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
guard let peer = peer, let navigationController = controller?.navigationController as? NavigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case .user = peer {
|
||||||
|
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||||
|
navigationController.pushViewController(controller)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: nil))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
|
||||||
|
let contextController = ContextController(presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
|
controller.presentInGlobalOverlay(contextController)
|
||||||
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
@ -381,7 +381,13 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (viewsLayout, viewsApply) = makeViewsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_MessageViews(item.views), font: labelFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
|
let viewsString: String
|
||||||
|
if item.views == 0 {
|
||||||
|
viewsString = item.presentationData.strings.Stats_MessageViews_NoViews
|
||||||
|
} else {
|
||||||
|
viewsString = item.presentationData.strings.Stats_MessageViews(item.views)
|
||||||
|
}
|
||||||
|
let (viewsLayout, viewsApply) = makeViewsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: viewsString, font: labelFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let reactions = item.reactions > 0 ? compactNumericCountString(Int(item.reactions), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) : ""
|
let reactions = item.reactions > 0 ? compactNumericCountString(Int(item.reactions), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) : ""
|
||||||
let (reactionsLayout, reactionsApply) = makeReactionsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: reactions, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
|
let (reactionsLayout, reactionsApply) = makeReactionsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: reactions, font: labelFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 128.0, height: CGFloat.greatestFiniteMagnitude), alignment: .right, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
@ -956,9 +956,9 @@ final class CaptureControlsComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
galleryButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize)
|
galleryButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize)
|
||||||
}
|
}
|
||||||
if let galleryButtonView = self.galleryButtonView.view {
|
if let galleryButtonView = self.galleryButtonView.view as? CameraButton.View {
|
||||||
galleryButtonView.clipsToBounds = true
|
galleryButtonView.contentView.clipsToBounds = true
|
||||||
galleryButtonView.layer.cornerRadius = galleryCornerRadius
|
galleryButtonView.contentView.layer.cornerRadius = galleryCornerRadius
|
||||||
if galleryButtonView.superview == nil {
|
if galleryButtonView.superview == nil {
|
||||||
self.addSubview(galleryButtonView)
|
self.addSubview(galleryButtonView)
|
||||||
}
|
}
|
||||||
|
@ -617,8 +617,11 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
|
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
|
||||||
|
let previous = strongSelf.audioTranscriptionState
|
||||||
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
|
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
|
||||||
strongSelf.updateTranscriptionExpanded?(strongSelf.audioTranscriptionState)
|
if previous != updatedAudioTranscriptionState {
|
||||||
|
strongSelf.updateTranscriptionExpanded?(strongSelf.audioTranscriptionState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let updatedTranscriptionText = updatedTranscriptionText {
|
if let updatedTranscriptionText = updatedTranscriptionText {
|
||||||
strongSelf.audioTranscriptionText = updatedTranscriptionText
|
strongSelf.audioTranscriptionText = updatedTranscriptionText
|
||||||
|
@ -61,9 +61,24 @@ extension MediaEditorScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requestDeviceAccess() {
|
func requestDeviceAccess() {
|
||||||
DeviceAccess.authorizeAccess(to: .camera(.video), { granted in
|
guard let controller = self.controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = controller.context
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||||
|
DeviceAccess.authorizeAccess(to: .camera(.video), presentationData: presentationData, present: { c, a in
|
||||||
|
c.presentationArguments = a
|
||||||
|
context.sharedContext.mainWindow?.present(c, on: .root)
|
||||||
|
}, openSettings: {
|
||||||
|
context.sharedContext.applicationBindings.openSettings()
|
||||||
|
}, { granted in
|
||||||
if granted {
|
if granted {
|
||||||
DeviceAccess.authorizeAccess(to: .microphone(.video))
|
DeviceAccess.authorizeAccess(to: .microphone(.video), presentationData: presentationData, present: { c, a in
|
||||||
|
c.presentationArguments = a
|
||||||
|
context.sharedContext.mainWindow?.present(c, on: .root)
|
||||||
|
}, openSettings: {
|
||||||
|
context.sharedContext.applicationBindings.openSettings()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -119,6 +134,11 @@ extension MediaEditorScreen {
|
|||||||
|
|
||||||
self.cameraIsActive = false
|
self.cameraIsActive = false
|
||||||
} else {
|
} else {
|
||||||
|
if self.cameraAuthorizationStatus != .allowed || self.microphoneAuthorizationStatus != .allowed {
|
||||||
|
self.requestDeviceAccess()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard self.tooltipController == nil, let sourceView else {
|
guard self.tooltipController == nil, let sourceView else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -2534,6 +2534,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if location.x > self.view.frame.width - 44.0 && location.y > self.view.frame.height - 180.0 {
|
if location.x > self.view.frame.width - 44.0 && location.y > self.view.frame.height - 180.0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let reactionNode = self.view.subviews.last?.asyncdisplaykit_node as? ReactionContextNode {
|
||||||
|
if let hitTestResult = self.view.hitTest(location, with: nil), hitTestResult.isDescendant(of: reactionNode.view) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
@ -4413,28 +4418,28 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Image", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_Shortcut_Image, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Image"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Image"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
self?.node.presentGallery()
|
self?.node.presentGallery()
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Location", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_Shortcut_Location, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LocationSmall"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LocationSmall"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
self?.node.presentLocationPicker()
|
self?.node.presentLocationPicker()
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Reaction", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_Shortcut_Reaction, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
self?.node.addReaction()
|
self?.node.addReaction()
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Audio", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_Shortcut_Audio, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/AudioSmall"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Media Editor/AudioSmall"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
@ -327,68 +327,73 @@ public final class MessageInputActionButtonComponent: Component {
|
|||||||
self.containerNode.isUserInteractionEnabled = component.longPressAction != nil
|
self.containerNode.isUserInteractionEnabled = component.longPressAction != nil
|
||||||
|
|
||||||
if self.micButton == nil {
|
if self.micButton == nil {
|
||||||
let micButton = ChatTextInputMediaRecordingButton(
|
switch component.mode {
|
||||||
context: component.context,
|
case .videoInput, .voiceInput, .unavailableVoiceInput, .send:
|
||||||
theme: defaultDarkPresentationTheme,
|
let micButton = ChatTextInputMediaRecordingButton(
|
||||||
useDarkTheme: true,
|
context: component.context,
|
||||||
strings: component.strings,
|
theme: defaultDarkPresentationTheme,
|
||||||
presentController: component.presentController
|
useDarkTheme: true,
|
||||||
)
|
strings: component.strings,
|
||||||
self.micButton = micButton
|
presentController: component.presentController
|
||||||
micButton.statusBarHost = component.context.sharedContext.mainWindow?.statusBarHost
|
)
|
||||||
self.addSubview(micButton)
|
self.micButton = micButton
|
||||||
|
micButton.statusBarHost = component.context.sharedContext.mainWindow?.statusBarHost
|
||||||
micButton.disablesInteractiveKeyboardGestureRecognizer = true
|
self.addSubview(micButton)
|
||||||
|
|
||||||
micButton.beginRecording = { [weak self] in
|
micButton.disablesInteractiveKeyboardGestureRecognizer = true
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
micButton.beginRecording = { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch component.mode {
|
||||||
|
case .voiceInput, .videoInput:
|
||||||
|
component.action(component.mode, .down, false)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
switch component.mode {
|
micButton.stopRecording = { [weak self] in
|
||||||
case .voiceInput, .videoInput:
|
guard let self, let component = self.component else {
|
||||||
component.action(component.mode, .down, false)
|
return
|
||||||
default:
|
}
|
||||||
break
|
component.stopAndPreviewMediaRecording()
|
||||||
}
|
}
|
||||||
}
|
micButton.endRecording = { [weak self] sendMedia in
|
||||||
micButton.stopRecording = { [weak self] in
|
guard let self, let component = self.component else {
|
||||||
guard let self, let component = self.component else {
|
return
|
||||||
return
|
}
|
||||||
|
switch component.mode {
|
||||||
|
case .voiceInput, .videoInput:
|
||||||
|
component.action(component.mode, .up, sendMedia)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
component.stopAndPreviewMediaRecording()
|
micButton.updateLocked = { [weak self] _ in
|
||||||
}
|
guard let self, let component = self.component else {
|
||||||
micButton.endRecording = { [weak self] sendMedia in
|
return
|
||||||
guard let self, let component = self.component else {
|
}
|
||||||
return
|
component.lockMediaRecording()
|
||||||
}
|
}
|
||||||
switch component.mode {
|
micButton.switchMode = { [weak self] in
|
||||||
case .voiceInput, .videoInput:
|
guard let self, let component = self.component else {
|
||||||
component.action(component.mode, .up, sendMedia)
|
return
|
||||||
default:
|
}
|
||||||
break
|
if case .unavailableVoiceInput = component.mode {
|
||||||
|
component.action(component.mode, .up, false)
|
||||||
|
} else {
|
||||||
|
component.switchMediaInputMode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
micButton.updateCancelTranslation = { [weak self] in
|
||||||
micButton.updateLocked = { [weak self] _ in
|
guard let self, let micButton = self.micButton, let component = self.component else {
|
||||||
guard let self, let component = self.component else {
|
return
|
||||||
return
|
}
|
||||||
|
component.updateMediaCancelFraction(micButton.cancelTranslation)
|
||||||
}
|
}
|
||||||
component.lockMediaRecording()
|
default:
|
||||||
}
|
break
|
||||||
micButton.switchMode = { [weak self] in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if case .unavailableVoiceInput = component.mode {
|
|
||||||
component.action(component.mode, .up, false)
|
|
||||||
} else {
|
|
||||||
component.switchMediaInputMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
micButton.updateCancelTranslation = { [weak self] in
|
|
||||||
guard let self, let micButton = self.micButton, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component.updateMediaCancelFraction(micButton.cancelTranslation)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,6 +641,19 @@ public final class MessageInputActionButtonComponent: Component {
|
|||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
var result = super.hitTest(point, with: event)
|
||||||
|
if result == nil, !self.isHidden && self.alpha > 0.0 {
|
||||||
|
for view in self.button.view.subviews {
|
||||||
|
if view.point(inside: self.convert(point, to: view), with: event) {
|
||||||
|
result = self.button.view
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeView() -> View {
|
public func makeView() -> View {
|
||||||
|
@ -715,6 +715,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.audioRecorderStatusDisposable?.dispose()
|
self.audioRecorderStatusDisposable?.dispose()
|
||||||
self.audioRecorderStatusDisposable?.dispose()
|
self.audioRecorderStatusDisposable?.dispose()
|
||||||
self.videoRecorderDisposable?.dispose()
|
self.videoRecorderDisposable?.dispose()
|
||||||
|
self.updateDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func allowsExternalGestures(point: CGPoint) -> Bool {
|
func allowsExternalGestures(point: CGPoint) -> Bool {
|
||||||
@ -5161,6 +5162,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
StoryContainerScreen.openPeerStories(context: component.context, peerId: peer.id, parentController: controller, avatarNode: avatarNode)
|
StoryContainerScreen.openPeerStories(context: component.context, peerId: peer.id, parentController: controller, avatarNode: avatarNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let updateDisposable = MetaDisposable()
|
||||||
func openStoryEditing(repost: Bool = false) {
|
func openStoryEditing(repost: Bool = false) {
|
||||||
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {
|
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {
|
||||||
return
|
return
|
||||||
@ -5234,7 +5236,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
transitionOut: nil
|
transitionOut: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
let updateDisposable = MetaDisposable()
|
|
||||||
var updateProgressImpl: ((Float) -> Void)?
|
var updateProgressImpl: ((Float) -> Void)?
|
||||||
let controller = MediaEditorScreen(
|
let controller = MediaEditorScreen(
|
||||||
context: context,
|
context: context,
|
||||||
@ -5324,7 +5325,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
TempBox.shared.dispose(tempFile)
|
TempBox.shared.dispose(tempFile)
|
||||||
}
|
}
|
||||||
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
||||||
updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|
self.updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -5378,7 +5379,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|
self.updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -5441,9 +5442,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self?.state?.updated(transition: .easeInOut(duration: 0.2))
|
self?.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
}
|
}
|
||||||
self.component?.controller()?.push(controller)
|
self.component?.controller()?.push(controller)
|
||||||
updateProgressImpl = { [weak controller] progress in
|
updateProgressImpl = { [weak controller, weak self] progress in
|
||||||
controller?.updateEditProgress(progress, cancel: {
|
controller?.updateEditProgress(progress, cancel: { [weak self] in
|
||||||
updateDisposable.dispose()
|
self?.updateDisposable.set(nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,7 +404,6 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
likeStatsText = AnimatedCountLabelView(frame: CGRect())
|
likeStatsText = AnimatedCountLabelView(frame: CGRect())
|
||||||
likeStatsText.isUserInteractionEnabled = false
|
likeStatsText.isUserInteractionEnabled = false
|
||||||
self.likeStatsText = likeStatsText
|
self.likeStatsText = likeStatsText
|
||||||
self.externalContainerView.addSubview(likeStatsText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let reactionStatsLayout = likeStatsText.update(
|
let reactionStatsLayout = likeStatsText.update(
|
||||||
@ -472,7 +471,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
containerSize: CGSize(width: 33.0, height: 33.0)
|
||||||
)
|
)
|
||||||
if let likeButtonView = likeButton.view {
|
if let likeButtonView = likeButton.view as? MessageInputActionButtonComponent.View {
|
||||||
if likeButtonView.superview == nil {
|
if likeButtonView.superview == nil {
|
||||||
self.addSubview(likeButtonView)
|
self.addSubview(likeButtonView)
|
||||||
}
|
}
|
||||||
@ -491,6 +490,15 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction)
|
likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction)
|
||||||
|
|
||||||
rightContentOffset -= likeButtonSize.width + 14.0
|
rightContentOffset -= likeButtonSize.width + 14.0
|
||||||
|
|
||||||
|
if likeStatsText.superview == nil {
|
||||||
|
likeButtonView.button.view.addSubview(likeStatsText)
|
||||||
|
}
|
||||||
|
|
||||||
|
likeStatsFrame.origin.x -= likeButtonFrame.minX
|
||||||
|
likeStatsFrame.origin.y -= likeButtonFrame.minY
|
||||||
|
likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center)
|
||||||
|
likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.canShare {
|
if component.canShare {
|
||||||
@ -502,7 +510,6 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
forwardStatsText = AnimatedCountLabelView(frame: CGRect())
|
forwardStatsText = AnimatedCountLabelView(frame: CGRect())
|
||||||
forwardStatsText.isUserInteractionEnabled = false
|
forwardStatsText.isUserInteractionEnabled = false
|
||||||
self.forwardStatsText = forwardStatsText
|
self.forwardStatsText = forwardStatsText
|
||||||
self.externalContainerView.addSubview(forwardStatsText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let forwardStatsLayout = forwardStatsText.update(
|
let forwardStatsLayout = forwardStatsText.update(
|
||||||
@ -515,8 +522,6 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size)
|
var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size)
|
||||||
forwardStatsFrame.origin.y += component.expandFraction * 45.0
|
forwardStatsFrame.origin.y += component.expandFraction * 45.0
|
||||||
|
|
||||||
forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center)
|
|
||||||
forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size))
|
|
||||||
var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction)
|
var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction)
|
||||||
if forwardCount == 0 {
|
if forwardCount == 0 {
|
||||||
forwardStatsAlpha = 0.0
|
forwardStatsAlpha = 0.0
|
||||||
@ -578,18 +583,27 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
containerSize: CGSize(width: 33.0, height: 33.0)
|
||||||
)
|
)
|
||||||
if let repostButtonView = repostButton.view {
|
if let repostButtonView = repostButton.view as? MessageInputActionButtonComponent.View {
|
||||||
if repostButtonView.superview == nil {
|
if repostButtonView.superview == nil {
|
||||||
self.addSubview(repostButtonView)
|
self.addSubview(repostButtonView)
|
||||||
}
|
}
|
||||||
var repostButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - repostButtonSize.width, y: floor((size.height - repostButtonSize.height) * 0.5)), size: repostButtonSize)
|
var repostButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - repostButtonSize.width, y: floor((size.height - repostButtonSize.height) * 0.5)), size: repostButtonSize)
|
||||||
repostButtonFrame.origin.y += component.expandFraction * 45.0
|
repostButtonFrame.origin.y += component.expandFraction * 45.0
|
||||||
|
|
||||||
likeStatsTransition.setPosition(view: repostButtonView, position: repostButtonFrame.center)
|
forwardStatsTransition.setPosition(view: repostButtonView, position: repostButtonFrame.center)
|
||||||
likeStatsTransition.setBounds(view: repostButtonView, bounds: CGRect(origin: CGPoint(), size: repostButtonFrame.size))
|
forwardStatsTransition.setBounds(view: repostButtonView, bounds: CGRect(origin: CGPoint(), size: repostButtonFrame.size))
|
||||||
likeStatsTransition.setAlpha(view: repostButtonView, alpha: 1.0 - component.expandFraction)
|
forwardStatsTransition.setAlpha(view: repostButtonView, alpha: 1.0 - component.expandFraction)
|
||||||
|
|
||||||
rightContentOffset -= repostButtonSize.width + 14.0
|
rightContentOffset -= repostButtonSize.width + 14.0
|
||||||
|
|
||||||
|
if forwardStatsText.superview == nil {
|
||||||
|
repostButtonView.button.view.addSubview(forwardStatsText)
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardStatsFrame.origin.x -= repostButtonFrame.minX
|
||||||
|
forwardStatsFrame.origin.y -= repostButtonFrame.minY
|
||||||
|
forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center)
|
||||||
|
forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
let forwardButtonSize = forwardButton.update(
|
let forwardButtonSize = forwardButton.update(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app": "10.3",
|
"app": "10.3.1",
|
||||||
"bazel": "6.4.0",
|
"bazel": "6.4.0",
|
||||||
"xcode": "15.0"
|
"xcode": "15.0"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user