mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video stream UI improvements
This commit is contained in:
parent
e95f84232e
commit
fd233a4657
18
submodules/Components/ActivityIndicatorComponent/BUILD
Normal file
18
submodules/Components/ActivityIndicatorComponent/BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ActivityIndicatorComponent",
|
||||
module_name = "ActivityIndicatorComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
|
||||
public final class ActivityIndicatorComponent: Component {
|
||||
public init(
|
||||
) {
|
||||
}
|
||||
|
||||
public static func ==(lhs: ActivityIndicatorComponent, rhs: ActivityIndicatorComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIActivityIndicatorView {
|
||||
public init() {
|
||||
super.init(style: .whiteLarge)
|
||||
}
|
||||
|
||||
required public init(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ActivityIndicatorComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
if !self.isAnimating {
|
||||
self.startAnimating()
|
||||
}
|
||||
|
||||
return CGSize(width: 22.0, height: 22.0)
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ swift_library(
|
||||
"//third-party/libyuv:LibYuvBinding",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
"//submodules/Components/ActivityIndicatorComponent:ActivityIndicatorComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -588,10 +588,10 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
let call = context.component.call
|
||||
let controller = environment.controller
|
||||
|
||||
let video = Condition(context.state.hasVideo) {
|
||||
return video.update(
|
||||
let video = video.update(
|
||||
component: MediaStreamVideoComponent(
|
||||
call: context.component.call,
|
||||
hasVideo: context.state.hasVideo,
|
||||
activatePictureInPicture: activatePictureInPicture,
|
||||
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
|
||||
guard let call = call else {
|
||||
@ -607,10 +607,9 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
}
|
||||
|
||||
var navigationRightItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
if context.state.isPictureInPictureSupported, let _ = video {
|
||||
if context.state.isPictureInPictureSupported, context.state.hasVideo {
|
||||
navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Media Gallery/PictureInPictureButton",
|
||||
@ -663,7 +662,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
topInset: environment.statusBarHeight,
|
||||
sideInset: environment.safeInsets.left,
|
||||
leftItem: AnyComponent(Button(
|
||||
content: AnyComponent(NavigationBackButtonComponent(text: environment.strings.Common_Back, color: .white)),
|
||||
content: AnyComponent(NavigationBackButtonComponent(text: environment.strings.Common_Close, color: .white)),
|
||||
action: { [weak call] in
|
||||
let _ = call?.leave(terminateIfPossible: false)
|
||||
})
|
||||
@ -754,11 +753,9 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
})
|
||||
)
|
||||
|
||||
if let video = video {
|
||||
context.add(video
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0 + context.state.dismissOffset))
|
||||
)
|
||||
}
|
||||
|
||||
context.add(navigationBar
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0))
|
||||
@ -776,6 +773,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController {
|
||||
private let context: AccountContext
|
||||
public let call: PresentationGroupCall
|
||||
public private(set) var currentOverlayController: VoiceChatOverlayController? = nil
|
||||
public var parentNavigationController: NavigationController?
|
||||
@ -785,13 +783,19 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
|
||||
private var initialOrientation: UIInterfaceOrientation?
|
||||
|
||||
private let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
|
||||
|
||||
public init(call: PresentationGroupCall) {
|
||||
self.context = call.accountContext
|
||||
self.call = call
|
||||
|
||||
super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl))
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
self.inviteLinksPromise.set(.single(nil)
|
||||
|> then(call.inviteLinks))
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -858,6 +862,41 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
|
||||
func presentShare() {
|
||||
let _ = (self.inviteLinksPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] inviteLinks in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let callPeerId = strongSelf.call.peerId
|
||||
let _ = (strongSelf.call.accountContext.account.postbox.transaction { transaction -> GroupCallInviteLinks? in
|
||||
if let inviteLinks = inviteLinks {
|
||||
return inviteLinks
|
||||
} else if let peer = transaction.getPeer(callPeerId), let addressName = peer.addressName, !addressName.isEmpty {
|
||||
return GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)?voicechat", speakerLink: nil)
|
||||
} else if let cachedData = transaction.getPeerCachedData(peerId: callPeerId) {
|
||||
if let cachedData = cachedData as? CachedChannelData, let link = cachedData.exportedInvitation?.link {
|
||||
return GroupCallInviteLinks(listenerLink: link, speakerLink: nil)
|
||||
} else if let cachedData = cachedData as? CachedGroupData, let link = cachedData.exportedInvitation?.link {
|
||||
return GroupCallInviteLinks(listenerLink: link, speakerLink: nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { links in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let links = links {
|
||||
strongSelf.presentShare(links: links)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func presentShare(links inviteLinks: GroupCallInviteLinks) {
|
||||
let formatSendTitle: (String) -> String = { string in
|
||||
var string = string
|
||||
if string.contains("[") && string.contains("]") {
|
||||
@ -869,34 +908,29 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
return string
|
||||
}
|
||||
let _ = formatSendTitle
|
||||
|
||||
let _ = (combineLatest(self.call.accountContext.account.postbox.loadedPeerWithId(self.call.peerId), self.call.state |> take(1))
|
||||
let _ = (combineLatest(queue: .mainQueue(), self.context.account.postbox.loadedPeerWithId(self.call.peerId), self.call.state |> take(1))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer, callState in
|
||||
if let strongSelf = self {
|
||||
var maybeInviteLinks: GroupCallInviteLinks? = nil
|
||||
var inviteLinks = inviteLinks
|
||||
|
||||
if let peer = peer as? TelegramChannel, let addressName = peer.addressName {
|
||||
maybeInviteLinks = GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)", speakerLink: nil)
|
||||
if let peer = peer as? TelegramChannel, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
|
||||
let isMuted = defaultParticipantMuteState == .muted
|
||||
|
||||
if !isMuted {
|
||||
inviteLinks = GroupCallInviteLinks(listenerLink: inviteLinks.listenerLink, speakerLink: nil)
|
||||
}
|
||||
}
|
||||
|
||||
guard let inviteLinks = maybeInviteLinks else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var segmentedValues: [ShareControllerSegmentedValue]?
|
||||
if let speakerLink = inviteLinks.speakerLink {
|
||||
segmentedValues = [ShareControllerSegmentedValue(title: presentationData.strings.VoiceChat_InviteLink_Speaker, subject: .url(speakerLink), actionTitle: presentationData.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(presentationData.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
|
||||
}), ShareControllerSegmentedValue(title: presentationData.strings.VoiceChat_InviteLink_Listener, subject: .url(inviteLinks.listenerLink), actionTitle: presentationData.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(presentationData.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
|
||||
})]
|
||||
}
|
||||
let shareController = ShareController(context: strongSelf.call.accountContext, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: defaultDarkColorPresentationTheme, forcedActionTitle: presentationData.strings.VoiceChat_CopyInviteLink)
|
||||
segmentedValues = nil
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: defaultDarkPresentationTheme, forcedActionTitle: presentationData.strings.VoiceChat_CopyInviteLink)
|
||||
shareController.completed = { [weak self] peerIds in
|
||||
if let strongSelf = self {
|
||||
let _ = (strongSelf.call.accountContext.account.postbox.transaction { transaction -> [Peer] in
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
|
||||
var peers: [Peer] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
@ -904,19 +938,19 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
}
|
||||
return peers
|
||||
} |> deliverOnMainQueue).start(next: { peers in
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
var isSavedMessages = false
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
isSavedMessages = peer.id == strongSelf.call.accountContext.account.peerId
|
||||
let peerName = peer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
isSavedMessages = peer.id == strongSelf.context.account.peerId
|
||||
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.VoiceChat_ForwardTooltip_Chat(peerName).string
|
||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||
let firstPeerName = firstPeer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let secondPeerName = secondPeer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
text = presentationData.strings.VoiceChat_ForwardTooltip_TwoChats(firstPeerName, secondPeerName).string
|
||||
} else if let peer = peers.first {
|
||||
let peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
@ -932,7 +966,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
import ActivityIndicatorComponent
|
||||
import AccountContext
|
||||
import AVKit
|
||||
|
||||
final class MediaStreamVideoComponent: Component {
|
||||
let call: PresentationGroupCallImpl
|
||||
let hasVideo: Bool
|
||||
let activatePictureInPicture: ActionSlot<Action<Void>>
|
||||
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
|
||||
|
||||
init(call: PresentationGroupCallImpl, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) {
|
||||
init(call: PresentationGroupCallImpl, hasVideo: Bool, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) {
|
||||
self.call = call
|
||||
self.hasVideo = hasVideo
|
||||
self.activatePictureInPicture = activatePictureInPicture
|
||||
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
|
||||
}
|
||||
@ -19,6 +22,9 @@ final class MediaStreamVideoComponent: Component {
|
||||
if lhs.call !== rhs.call {
|
||||
return false
|
||||
}
|
||||
if lhs.hasVideo != rhs.hasVideo {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -38,6 +44,7 @@ final class MediaStreamVideoComponent: Component {
|
||||
private var videoView: VideoRenderingView?
|
||||
private let blurTintView: UIView
|
||||
private var videoBlurView: VideoRenderingView?
|
||||
private var activityIndicatorView: ComponentHostView<Empty>?
|
||||
|
||||
private var pictureInPictureController: AVPictureInPictureController?
|
||||
|
||||
@ -60,7 +67,7 @@ final class MediaStreamVideoComponent: Component {
|
||||
}
|
||||
|
||||
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
|
||||
if self.videoView == nil {
|
||||
if component.hasVideo, self.videoView == nil {
|
||||
if let input = component.call.video(endpointId: "unified") {
|
||||
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
|
||||
self.videoBlurView = videoBlurView
|
||||
@ -83,7 +90,14 @@ final class MediaStreamVideoComponent: Component {
|
||||
videoView.setOnOrientationUpdated { [weak state] _, _ in
|
||||
state?.updated(transition: .immediate)
|
||||
}
|
||||
videoView.setOnFirstFrameReceived { [weak state] _ in
|
||||
videoView.setOnFirstFrameReceived { [weak self, weak state] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.activityIndicatorView?.removeFromSuperview()
|
||||
strongSelf.activityIndicatorView = nil
|
||||
|
||||
state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
@ -106,6 +120,25 @@ final class MediaStreamVideoComponent: Component {
|
||||
videoBlurView.updateIsEnabled(true)
|
||||
transition.withAnimation(.none).setFrame(view: videoBlurView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - blurredVideoSize.width) / 2.0), y: floor((availableSize.height - blurredVideoSize.height) / 2.0)), size: blurredVideoSize), completion: nil)
|
||||
}
|
||||
} else {
|
||||
var activityIndicatorTransition = transition
|
||||
let activityIndicatorView: ComponentHostView<Empty>
|
||||
if let current = self.activityIndicatorView {
|
||||
activityIndicatorView = current
|
||||
} else {
|
||||
activityIndicatorTransition = transition.withAnimation(.none)
|
||||
activityIndicatorView = ComponentHostView<Empty>()
|
||||
self.activityIndicatorView = activityIndicatorView
|
||||
self.addSubview(activityIndicatorView)
|
||||
}
|
||||
|
||||
let activityIndicatorSize = activityIndicatorView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ActivityIndicatorComponent()),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
activityIndicatorTransition.setFrame(view: activityIndicatorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - activityIndicatorSize.width) / 2.0), y: floor((availableSize.height - activityIndicatorSize.height) / 2.0)), size: activityIndicatorSize), completion: nil)
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
@ -229,7 +229,14 @@ public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCach
|
||||
}
|
||||
|
||||
public func leaveInBackground(engine: TelegramEngine, id: Int64, accessHash: Int64, source: UInt32) {
|
||||
let disposable = engine.calls.leaveGroupCall(callId: id, accessHash: accessHash, source: source).start()
|
||||
let disposable = engine.calls.leaveGroupCall(callId: id, accessHash: accessHash, source: source).start(completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let context = strongSelf.contexts[id] {
|
||||
context.context.participantsContext?.removeLocalPeerId()
|
||||
}
|
||||
})
|
||||
self.leaveDisposables.add(disposable)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user