mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
74a4b18a17
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
|||||||
include Utils.makefile
|
include Utils.makefile
|
||||||
|
|
||||||
|
|
||||||
APP_VERSION="6.2.1"
|
APP_VERSION="6.3"
|
||||||
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
|
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
|
||||||
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)
|
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)
|
||||||
|
|
||||||
|
@ -5565,6 +5565,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Stats.GroupMessagesTitle" = "MESSAGES";
|
"Stats.GroupMessagesTitle" = "MESSAGES";
|
||||||
"Stats.GroupActionsTitle" = "ACTIONS";
|
"Stats.GroupActionsTitle" = "ACTIONS";
|
||||||
"Stats.GroupTopHoursTitle" = "TOP HOURS";
|
"Stats.GroupTopHoursTitle" = "TOP HOURS";
|
||||||
|
"Stats.GroupTopWeekdaysTitle" = "TOP DAYS OF WEEK";
|
||||||
"Stats.GroupTopPostersTitle" = "TOP MEMBERS";
|
"Stats.GroupTopPostersTitle" = "TOP MEMBERS";
|
||||||
"Stats.GroupTopAdminsTitle" = "TOP ADMINS";
|
"Stats.GroupTopAdminsTitle" = "TOP ADMINS";
|
||||||
"Stats.GroupTopInvitersTitle" = "TOP INVITERS";
|
"Stats.GroupTopInvitersTitle" = "TOP INVITERS";
|
||||||
@ -5583,6 +5584,9 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Stats.GroupTopPosterChars_many" = "%@ symbols per message";
|
"Stats.GroupTopPosterChars_many" = "%@ symbols per message";
|
||||||
"Stats.GroupTopPosterChars_any" = "%@ symbols per message";
|
"Stats.GroupTopPosterChars_any" = "%@ symbols per message";
|
||||||
|
|
||||||
|
"Stats.GroupTopPoster.History" = "History";
|
||||||
|
"Stats.GroupTopPoster.Promote" = "Promote";
|
||||||
|
|
||||||
"Stats.GroupTopAdminDeletions_0" = "%@ deletions";
|
"Stats.GroupTopAdminDeletions_0" = "%@ deletions";
|
||||||
"Stats.GroupTopAdminDeletions_1" = "%@ deletion";
|
"Stats.GroupTopAdminDeletions_1" = "%@ deletion";
|
||||||
"Stats.GroupTopAdminDeletions_2" = "%@ deletions";
|
"Stats.GroupTopAdminDeletions_2" = "%@ deletions";
|
||||||
@ -5604,9 +5608,15 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Stats.GroupTopAdminBans_many" = "%@ bans";
|
"Stats.GroupTopAdminBans_many" = "%@ bans";
|
||||||
"Stats.GroupTopAdminBans_any" = "%@ bans";
|
"Stats.GroupTopAdminBans_any" = "%@ bans";
|
||||||
|
|
||||||
|
"Stats.GroupTopAdmin.Actions" = "Actions";
|
||||||
|
"Stats.GroupTopAdmin.Promote" = "Promote";
|
||||||
|
|
||||||
"Stats.GroupTopInviterInvites_0" = "%@ invitations";
|
"Stats.GroupTopInviterInvites_0" = "%@ invitations";
|
||||||
"Stats.GroupTopInviterInvites_1" = "%@ invitation";
|
"Stats.GroupTopInviterInvites_1" = "%@ invitation";
|
||||||
"Stats.GroupTopInviterInvites_2" = "%@ invitations";
|
"Stats.GroupTopInviterInvites_2" = "%@ invitations";
|
||||||
"Stats.GroupTopInviterInvites_3_10" = "%@ invitations";
|
"Stats.GroupTopInviterInvites_3_10" = "%@ invitations";
|
||||||
"Stats.GroupTopInviterInvites_many" = "%@ invitations";
|
"Stats.GroupTopInviterInvites_many" = "%@ invitations";
|
||||||
"Stats.GroupTopInviterInvites_any" = "%@ invitations";
|
"Stats.GroupTopInviterInvites_any" = "%@ invitations";
|
||||||
|
|
||||||
|
"Stats.GroupTopInviter.History" = "History";
|
||||||
|
"Stats.GroupTopInviter.Promote" = "Promote";
|
||||||
|
@ -214,6 +214,34 @@ public final class ChatPeerNearbyData: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ChatSearchDomain: Equatable {
|
||||||
|
case everything
|
||||||
|
case members
|
||||||
|
case member(Peer)
|
||||||
|
|
||||||
|
public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case .everything:
|
||||||
|
if case .everything = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .members:
|
||||||
|
if case .members = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .member(lhsPeer):
|
||||||
|
if case let .member(rhsPeer) = rhs, lhsPeer.isEqual(rhsPeer) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public final class NavigateToChatControllerParams {
|
public final class NavigateToChatControllerParams {
|
||||||
public let navigationController: NavigationController
|
public let navigationController: NavigationController
|
||||||
public let chatController: ChatController?
|
public let chatController: ChatController?
|
||||||
@ -227,7 +255,7 @@ public final class NavigateToChatControllerParams {
|
|||||||
public let useExisting: Bool
|
public let useExisting: Bool
|
||||||
public let purposefulAction: (() -> Void)?
|
public let purposefulAction: (() -> Void)?
|
||||||
public let scrollToEndIfExists: Bool
|
public let scrollToEndIfExists: Bool
|
||||||
public let activateMessageSearch: Bool
|
public let activateMessageSearch: (ChatSearchDomain, String)?
|
||||||
public let peekData: ChatPeekTimeout?
|
public let peekData: ChatPeekTimeout?
|
||||||
public let peerNearbyData: ChatPeerNearbyData?
|
public let peerNearbyData: ChatPeerNearbyData?
|
||||||
public let animated: Bool
|
public let animated: Bool
|
||||||
@ -235,7 +263,7 @@ public final class NavigateToChatControllerParams {
|
|||||||
public let parentGroupId: PeerGroupId?
|
public let parentGroupId: PeerGroupId?
|
||||||
public let completion: (ChatController) -> Void
|
public let completion: (ChatController) -> Void
|
||||||
|
|
||||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: Bool = false, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
|
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
|
||||||
self.navigationController = navigationController
|
self.navigationController = navigationController
|
||||||
self.chatController = chatController
|
self.chatController = chatController
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -482,6 +510,7 @@ public protocol SharedAccountContext: class {
|
|||||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||||
func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
||||||
func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController?
|
func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController?
|
||||||
|
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
|
||||||
func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
|
func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
|
||||||
func makePeersNearbyController(context: AccountContext) -> ViewController
|
func makePeersNearbyController(context: AccountContext) -> ViewController
|
||||||
func makeComposeController(context: AccountContext) -> ViewController
|
func makeComposeController(context: AccountContext) -> ViewController
|
||||||
|
@ -11,15 +11,31 @@ public enum RequestCallResult {
|
|||||||
case alreadyInProgress(PeerId)
|
case alreadyInProgress(PeerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PresentationCallState: Equatable {
|
public struct PresentationCallState: Equatable {
|
||||||
case waiting
|
public enum State: Equatable {
|
||||||
case ringing
|
case waiting
|
||||||
case requesting(Bool)
|
case ringing
|
||||||
case connecting(Data?)
|
case requesting(Bool)
|
||||||
case active(Double, Int32?, Data)
|
case connecting(Data?)
|
||||||
case reconnecting(Double, Int32?, Data)
|
case active(Double, Int32?, Data)
|
||||||
case terminating
|
case reconnecting(Double, Int32?, Data)
|
||||||
case terminated(CallId?, CallSessionTerminationReason?, Bool)
|
case terminating
|
||||||
|
case terminated(CallId?, CallSessionTerminationReason?, Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VideoState: Equatable {
|
||||||
|
case notAvailable
|
||||||
|
case available(Bool)
|
||||||
|
case active
|
||||||
|
}
|
||||||
|
|
||||||
|
public var state: State
|
||||||
|
public var videoState: VideoState
|
||||||
|
|
||||||
|
public init(state: State, videoState: VideoState) {
|
||||||
|
self.state = state
|
||||||
|
self.videoState = videoState
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol PresentationCall: class {
|
public protocol PresentationCall: class {
|
||||||
@ -44,6 +60,8 @@ public protocol PresentationCall: class {
|
|||||||
|
|
||||||
func toggleIsMuted()
|
func toggleIsMuted()
|
||||||
func setIsMuted(_ value: Bool)
|
func setIsMuted(_ value: Bool)
|
||||||
|
func setEnableVideo(_ value: Bool)
|
||||||
|
func switchVideoCamera()
|
||||||
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
||||||
func debugInfo() -> Signal<(String, String), NoError>
|
func debugInfo() -> Signal<(String, String), NoError>
|
||||||
|
|
||||||
|
@ -666,6 +666,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
case .destructive:
|
case .destructive:
|
||||||
color = item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor
|
color = item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor
|
||||||
textColor = item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor
|
textColor = item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor
|
||||||
|
case .accent:
|
||||||
|
color = item.presentationData.theme.list.itemDisclosureActions.accent.fillColor
|
||||||
|
textColor = item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor
|
||||||
}
|
}
|
||||||
mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
|
mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
|
||||||
index += 1
|
index += 1
|
||||||
|
@ -30,6 +30,12 @@ public class PercentPieChartController: BaseChartController {
|
|||||||
let pieController: PieChartComponentController
|
let pieController: PieChartComponentController
|
||||||
let transitionRenderer: PercentPieAnimationRenderer
|
let transitionRenderer: PercentPieAnimationRenderer
|
||||||
|
|
||||||
|
var initiallyZoomed = false
|
||||||
|
public convenience init(chartsCollection: ChartsCollection, initiallyZoomed: Bool) {
|
||||||
|
self.init(chartsCollection: chartsCollection)
|
||||||
|
self.initiallyZoomed = initiallyZoomed
|
||||||
|
}
|
||||||
|
|
||||||
override public init(chartsCollection: ChartsCollection) {
|
override public init(chartsCollection: ChartsCollection) {
|
||||||
transitionRenderer = PercentPieAnimationRenderer()
|
transitionRenderer = PercentPieAnimationRenderer()
|
||||||
percentController = PercentChartComponentController(isZoomed: false,
|
percentController = PercentChartComponentController(isZoomed: false,
|
||||||
@ -88,14 +94,14 @@ public class PercentPieChartController: BaseChartController {
|
|||||||
pieController.previewBarChartRenderer]
|
pieController.previewBarChartRenderer]
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func initializeChart() {
|
public override func initializeChart() {
|
||||||
percentController.initialize(chartsCollection: initialChartsCollection,
|
percentController.initialize(chartsCollection: initialChartsCollection,
|
||||||
initialDate: Date(),
|
initialDate: Date(),
|
||||||
totalHorizontalRange: BaseConstants.defaultRange,
|
totalHorizontalRange: BaseConstants.defaultRange,
|
||||||
totalVerticalRange: BaseConstants.defaultRange)
|
totalVerticalRange: BaseConstants.defaultRange)
|
||||||
switchToChart(chartsCollection: percentController.chartsCollection, isZoomed: false, animated: false)
|
switchToChart(chartsCollection: percentController.chartsCollection, isZoomed: false, animated: false)
|
||||||
|
|
||||||
if let lastDate = initialChartsCollection.axisValues.last {
|
if let lastDate = initialChartsCollection.axisValues.last, self.initiallyZoomed {
|
||||||
TimeInterval.animationDurationMultipler = 0.00001
|
TimeInterval.animationDurationMultipler = 0.00001
|
||||||
self.didTapZoomIn(date: lastDate, animated: false)
|
self.didTapZoomIn(date: lastDate, animated: false)
|
||||||
TimeInterval.animationDurationMultipler = 1.0
|
TimeInterval.animationDurationMultipler = 1.0
|
||||||
|
@ -11,6 +11,7 @@ public enum ChartType {
|
|||||||
case lines
|
case lines
|
||||||
case twoAxis
|
case twoAxis
|
||||||
case pie
|
case pie
|
||||||
|
case area
|
||||||
case bars
|
case bars
|
||||||
case step
|
case step
|
||||||
case twoAxisStep
|
case twoAxisStep
|
||||||
@ -76,7 +77,9 @@ public func createChartController(_ data: String, type: ChartType, getDetailsDat
|
|||||||
controller = TwoAxisLinesChartController(chartsCollection: collection)
|
controller = TwoAxisLinesChartController(chartsCollection: collection)
|
||||||
controller.isZoomable = false
|
controller.isZoomable = false
|
||||||
case .pie:
|
case .pie:
|
||||||
controller = PercentPieChartController(chartsCollection: collection)
|
controller = PercentPieChartController(chartsCollection: collection, initiallyZoomed: true)
|
||||||
|
case .area:
|
||||||
|
controller = PercentPieChartController(chartsCollection: collection, initiallyZoomed: false)
|
||||||
case .bars:
|
case .bars:
|
||||||
controller = StackedBarsChartController(chartsCollection: collection)
|
controller = StackedBarsChartController(chartsCollection: collection)
|
||||||
controller.isZoomable = false
|
controller.isZoomable = false
|
||||||
|
@ -274,6 +274,7 @@ public enum ItemListPeerItemRevealOptionType {
|
|||||||
case neutral
|
case neutral
|
||||||
case warning
|
case warning
|
||||||
case destructive
|
case destructive
|
||||||
|
case accent
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ItemListPeerItemRevealOption {
|
public struct ItemListPeerItemRevealOption {
|
||||||
@ -624,6 +625,9 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
case .destructive:
|
case .destructive:
|
||||||
color = item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor
|
color = item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor
|
||||||
textColor = item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor
|
textColor = item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor
|
||||||
|
case .accent:
|
||||||
|
color = item.presentationData.theme.list.itemDisclosureActions.accent.fillColor
|
||||||
|
textColor = item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor
|
||||||
}
|
}
|
||||||
mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
|
mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
|
||||||
index += 1
|
index += 1
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
@property (nonatomic, copy) void (^micLevel)(CGFloat level);
|
@property (nonatomic, copy) void (^micLevel)(CGFloat level);
|
||||||
@property (nonatomic, copy) void (^onDuration)(NSTimeInterval duration);
|
@property (nonatomic, copy) void (^onDuration)(NSTimeInterval duration);
|
||||||
@property (nonatomic, copy) void(^finishedWithVideo)(NSURL *videoURL, UIImage *previewImage, NSUInteger fileSize, NSTimeInterval duration, CGSize dimensions, id liveUploadData, TGVideoEditAdjustments *adjustments, bool, int32_t);
|
@property (nonatomic, copy) void(^finishedWithVideo)(NSURL *videoURL, UIImage *previewImage, NSUInteger fileSize, NSTimeInterval duration, CGSize dimensions, id liveUploadData, TGVideoEditAdjustments *adjustments, bool, int32_t);
|
||||||
@property (nonatomic, copy) void(^onDismiss)(bool isAuto);
|
@property (nonatomic, copy) void(^onDismiss)(bool isAuto, bool isCancelled);
|
||||||
@property (nonatomic, copy) void(^onStop)(void);
|
@property (nonatomic, copy) void(^onStop)(void);
|
||||||
@property (nonatomic, copy) void(^onCancel)(void);
|
@property (nonatomic, copy) void(^onCancel)(void);
|
||||||
@property (nonatomic, copy) void(^didDismiss)(void);
|
@property (nonatomic, copy) void(^didDismiss)(void);
|
||||||
|
@ -457,6 +457,8 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
|||||||
_lockPanelWrapperView.transform = CGAffineTransformMakeTranslation(0.0f, 100.0f);
|
_lockPanelWrapperView.transform = CGAffineTransformMakeTranslation(0.0f, 100.0f);
|
||||||
_lockPanelWrapperView.alpha = 0.0f;
|
_lockPanelWrapperView.alpha = 0.0f;
|
||||||
|
|
||||||
|
_lock.transform = CGAffineTransformIdentity;
|
||||||
|
|
||||||
if (iosMajorVersion() >= 8) {
|
if (iosMajorVersion() >= 8) {
|
||||||
[UIView animateWithDuration:0.50 delay:0.0 usingSpringWithDamping:0.55f initialSpringVelocity:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
[UIView animateWithDuration:0.50 delay:0.0 usingSpringWithDamping:0.55f initialSpringVelocity:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||||
_innerCircleView.transform = CGAffineTransformIdentity;
|
_innerCircleView.transform = CGAffineTransformIdentity;
|
||||||
@ -548,6 +550,10 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)animateLock {
|
- (void)animateLock {
|
||||||
|
if (!_animatedIn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_lockView.lockness = 1.0f;
|
_lockView.lockness = 1.0f;
|
||||||
[_lock updateLockness:1.0];
|
[_lock updateLockness:1.0];
|
||||||
|
|
||||||
@ -718,10 +724,6 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else if (distanceX < -100.0 && !_xFeedbackOccured) {
|
} else if (distanceX < -100.0 && !_xFeedbackOccured) {
|
||||||
if (iosMajorVersion() >= 10) {
|
|
||||||
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
|
|
||||||
[generator impactOccurred];
|
|
||||||
}
|
|
||||||
_xFeedbackOccured = true;
|
_xFeedbackOccured = true;
|
||||||
} else if (distanceX > -100.0) {
|
} else if (distanceX > -100.0) {
|
||||||
_xFeedbackOccured = false;
|
_xFeedbackOccured = false;
|
||||||
@ -732,10 +734,6 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else if (distanceY < -60.0 && !_yFeedbackOccured) {
|
} else if (distanceY < -60.0 && !_yFeedbackOccured) {
|
||||||
if (iosMajorVersion() >= 10) {
|
|
||||||
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
|
|
||||||
[generator impactOccurred];
|
|
||||||
}
|
|
||||||
_yFeedbackOccured = true;
|
_yFeedbackOccured = true;
|
||||||
} else if (distanceY > -60.0) {
|
} else if (distanceY > -60.0) {
|
||||||
_yFeedbackOccured = false;
|
_yFeedbackOccured = false;
|
||||||
|
@ -628,7 +628,7 @@ typedef enum
|
|||||||
_dismissed = cancelled;
|
_dismissed = cancelled;
|
||||||
|
|
||||||
if (self.onDismiss != nil)
|
if (self.onDismiss != nil)
|
||||||
self.onDismiss(_automaticDismiss);
|
self.onDismiss(_automaticDismiss, cancelled);
|
||||||
|
|
||||||
if (_player != nil)
|
if (_player != nil)
|
||||||
[_player pause];
|
[_player pause];
|
||||||
|
@ -317,6 +317,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func dismiss(forceAway: Bool) {
|
private func dismiss(forceAway: Bool) {
|
||||||
|
self.animatedIn.set(false)
|
||||||
|
|
||||||
var animatedOutNode = true
|
var animatedOutNode = true
|
||||||
var animatedOutInterface = false
|
var animatedOutInterface = false
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ private enum StatsSection: Int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum StatsEntry: ItemListNodeEntry {
|
private enum StatsEntry: ItemListNodeEntry {
|
||||||
case overviewHeader(PresentationTheme, String, String)
|
case overviewTitle(PresentationTheme, String, String)
|
||||||
case overview(PresentationTheme, ChannelStats)
|
case overview(PresentationTheme, ChannelStats)
|
||||||
|
|
||||||
case growthTitle(PresentationTheme, String)
|
case growthTitle(PresentationTheme, String)
|
||||||
@ -78,7 +78,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .overviewHeader, .overview:
|
case .overviewTitle, .overview:
|
||||||
return StatsSection.overview.rawValue
|
return StatsSection.overview.rawValue
|
||||||
case .growthTitle, .growthGraph:
|
case .growthTitle, .growthGraph:
|
||||||
return StatsSection.growth.rawValue
|
return StatsSection.growth.rawValue
|
||||||
@ -105,7 +105,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var stableId: Int32 {
|
var stableId: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
case .overviewHeader:
|
case .overviewTitle:
|
||||||
return 0
|
return 0
|
||||||
case .overview:
|
case .overview:
|
||||||
return 1
|
return 1
|
||||||
@ -154,8 +154,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
|
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .overviewHeader(lhsTheme, lhsText, lhsDates):
|
case let .overviewTitle(lhsTheme, lhsText, lhsDates):
|
||||||
if case let .overviewHeader(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
if case let .overviewTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -296,7 +296,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! ChannelStatsControllerArguments
|
let arguments = arguments as! ChannelStatsControllerArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .overviewHeader(_, text, dates):
|
case let .overviewTitle(_, text, dates):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
||||||
case let .growthTitle(_, text),
|
case let .growthTitle(_, text),
|
||||||
let .followersTitle(_, text),
|
let .followersTitle(_, text),
|
||||||
@ -343,7 +343,7 @@ private func channelStatsControllerEntries(data: ChannelStats?, messages: [Messa
|
|||||||
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
|
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
|
||||||
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
|
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
|
||||||
|
|
||||||
entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) – \(maxDate)"))
|
entries.append(.overviewTitle(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) – \(maxDate)"))
|
||||||
entries.append(.overview(presentationData.theme, data))
|
entries.append(.overview(presentationData.theme, data))
|
||||||
|
|
||||||
if !data.growthGraph.isEmpty {
|
if !data.growthGraph.isEmpty {
|
||||||
|
@ -21,11 +21,23 @@ private final class GroupStatsControllerArguments {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
||||||
let openPeer: (PeerId) -> Void
|
let openPeer: (PeerId) -> Void
|
||||||
|
let openPeerHistory: (PeerId) -> Void
|
||||||
|
let openPeerAdminActions: (PeerId) -> Void
|
||||||
|
let promotePeer: (PeerId) -> Void
|
||||||
|
let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
|
let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
|
let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void) {
|
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.loadDetailedGraph = loadDetailedGraph
|
self.loadDetailedGraph = loadDetailedGraph
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
|
self.openPeerHistory = openPeerHistory
|
||||||
|
self.openPeerAdminActions = openPeerAdminActions
|
||||||
|
self.promotePeer = promotePeer
|
||||||
|
self.setPostersPeerIdWithRevealedOptions = setPostersPeerIdWithRevealedOptions
|
||||||
|
self.setAdminsPeerIdWithRevealedOptions = setAdminsPeerIdWithRevealedOptions
|
||||||
|
self.setInvitersPeerIdWithRevealedOptions = setInvitersPeerIdWithRevealedOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,13 +50,14 @@ private enum StatsSection: Int32 {
|
|||||||
case messages
|
case messages
|
||||||
case actions
|
case actions
|
||||||
case topHours
|
case topHours
|
||||||
|
case topWeekdays
|
||||||
case topPosters
|
case topPosters
|
||||||
case topAdmins
|
case topAdmins
|
||||||
case topInviters
|
case topInviters
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum StatsEntry: ItemListNodeEntry {
|
private enum StatsEntry: ItemListNodeEntry {
|
||||||
case overviewHeader(PresentationTheme, String, String)
|
case overviewTitle(PresentationTheme, String, String)
|
||||||
case overview(PresentationTheme, GroupStats)
|
case overview(PresentationTheme, GroupStats)
|
||||||
|
|
||||||
case growthTitle(PresentationTheme, String)
|
case growthTitle(PresentationTheme, String)
|
||||||
@ -68,18 +81,21 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
case topHoursTitle(PresentationTheme, String)
|
case topHoursTitle(PresentationTheme, String)
|
||||||
case topHoursGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
case topHoursGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||||
|
|
||||||
case topPostersTitle(PresentationTheme, String)
|
case topWeekdaysTitle(PresentationTheme, String)
|
||||||
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster)
|
case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||||
|
|
||||||
case topAdminsTitle(PresentationTheme, String)
|
case topPostersTitle(PresentationTheme, String, String)
|
||||||
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin)
|
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool)
|
||||||
|
|
||||||
case topInvitersTitle(PresentationTheme, String)
|
case topAdminsTitle(PresentationTheme, String, String)
|
||||||
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter)
|
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool)
|
||||||
|
|
||||||
|
case topInvitersTitle(PresentationTheme, String, String)
|
||||||
|
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .overviewHeader, .overview:
|
case .overviewTitle, .overview:
|
||||||
return StatsSection.overview.rawValue
|
return StatsSection.overview.rawValue
|
||||||
case .growthTitle, .growthGraph:
|
case .growthTitle, .growthGraph:
|
||||||
return StatsSection.growth.rawValue
|
return StatsSection.growth.rawValue
|
||||||
@ -95,6 +111,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return StatsSection.actions.rawValue
|
return StatsSection.actions.rawValue
|
||||||
case .topHoursTitle, .topHoursGraph:
|
case .topHoursTitle, .topHoursGraph:
|
||||||
return StatsSection.topHours.rawValue
|
return StatsSection.topHours.rawValue
|
||||||
|
case .topWeekdaysTitle, .topWeekdaysGraph:
|
||||||
|
return StatsSection.topWeekdays.rawValue
|
||||||
case .topPostersTitle, .topPoster:
|
case .topPostersTitle, .topPoster:
|
||||||
return StatsSection.topPosters.rawValue
|
return StatsSection.topPosters.rawValue
|
||||||
case .topAdminsTitle, .topAdmin:
|
case .topAdminsTitle, .topAdmin:
|
||||||
@ -106,7 +124,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var stableId: Int32 {
|
var stableId: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
case .overviewHeader:
|
case .overviewTitle:
|
||||||
return 0
|
return 0
|
||||||
case .overview:
|
case .overview:
|
||||||
return 1
|
return 1
|
||||||
@ -138,25 +156,29 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return 14
|
return 14
|
||||||
case .topHoursGraph:
|
case .topHoursGraph:
|
||||||
return 15
|
return 15
|
||||||
|
case .topWeekdaysTitle:
|
||||||
|
return 16
|
||||||
|
case .topWeekdaysGraph:
|
||||||
|
return 17
|
||||||
case .topPostersTitle:
|
case .topPostersTitle:
|
||||||
return 1000
|
return 1000
|
||||||
case let .topPoster(index, _, _, _, _, _):
|
case let .topPoster(index, _, _, _, _, _, _):
|
||||||
return 1001 + index
|
return 1001 + index
|
||||||
case .topAdminsTitle:
|
case .topAdminsTitle:
|
||||||
return 2000
|
return 2000
|
||||||
case let .topAdmin(index, _, _, _, _, _):
|
case let .topAdmin(index, _, _, _, _, _, _):
|
||||||
return 2001 + index
|
return 2001 + index
|
||||||
case .topInvitersTitle:
|
case .topInvitersTitle:
|
||||||
return 3000
|
return 3000
|
||||||
case let .topInviter(index, _, _, _, _, _):
|
case let .topInviter(index, _, _, _, _, _, _):
|
||||||
return 30001 + index
|
return 30001 + index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
|
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .overviewHeader(lhsTheme, lhsText, lhsDates):
|
case let .overviewTitle(lhsTheme, lhsText, lhsDates):
|
||||||
if case let .overviewHeader(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
if case let .overviewTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -251,38 +273,50 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .topPostersTitle(lhsTheme, lhsText):
|
case let .topWeekdaysTitle(lhsTheme, lhsText):
|
||||||
if case let .topPostersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .topWeekdaysTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .topWeekdaysGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||||||
|
if case let .topWeekdaysGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster):
|
case let .topPostersTitle(lhsTheme, lhsText, lhsDates):
|
||||||
if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster {
|
if case let .topPostersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .topAdminsTitle(lhsTheme, lhsText):
|
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed):
|
||||||
if case let .topAdminsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin):
|
case let .topAdminsTitle(lhsTheme, lhsText, lhsDates):
|
||||||
if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin {
|
if case let .topAdminsTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .topInvitersTitle(lhsTheme, lhsText):
|
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed):
|
||||||
if case let .topInvitersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter):
|
case let .topInvitersTitle(lhsTheme, lhsText, lhsDates):
|
||||||
if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter {
|
if case let .topInvitersTitle(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed):
|
||||||
|
if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -297,7 +331,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! GroupStatsControllerArguments
|
let arguments = arguments as! GroupStatsControllerArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .overviewHeader(_, text, dates):
|
case let .overviewTitle(_, text, dates):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
||||||
case let .growthTitle(_, text),
|
case let .growthTitle(_, text),
|
||||||
let .membersTitle(_, text),
|
let .membersTitle(_, text),
|
||||||
@ -306,10 +340,12 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
let .messagesTitle(_, text),
|
let .messagesTitle(_, text),
|
||||||
let .actionsTitle(_, text),
|
let .actionsTitle(_, text),
|
||||||
let .topHoursTitle(_, text),
|
let .topHoursTitle(_, text),
|
||||||
let .topPostersTitle(_, text),
|
let .topWeekdaysTitle(_, text):
|
||||||
let .topAdminsTitle(_, text),
|
|
||||||
let .topInvitersTitle(_, text):
|
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
|
case let .topPostersTitle(_, text, dates),
|
||||||
|
let .topAdminsTitle(_, text, dates),
|
||||||
|
let .topInvitersTitle(_, text, dates):
|
||||||
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
|
||||||
case let .overview(_, stats):
|
case let .overview(_, stats):
|
||||||
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
|
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
|
||||||
case let .growthGraph(_, _, _, graph, type),
|
case let .growthGraph(_, _, _, graph, type),
|
||||||
@ -318,9 +354,10 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
let .languagesGraph(_, _, _, graph, type),
|
let .languagesGraph(_, _, _, graph, type),
|
||||||
let .messagesGraph(_, _, _, graph, type),
|
let .messagesGraph(_, _, _, graph, type),
|
||||||
let .actionsGraph(_, _, _, graph, type),
|
let .actionsGraph(_, _, _, graph, type),
|
||||||
let .topHoursGraph(_, _, _, graph, type):
|
let .topHoursGraph(_, _, _, graph, type),
|
||||||
|
let .topWeekdaysGraph(_, _, _, graph, type):
|
||||||
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
|
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
|
||||||
case let .topPoster(_, _, strings, dateTimeFormat, peer, topPoster):
|
case let .topPoster(_, _, strings, dateTimeFormat, peer, topPoster, revealed):
|
||||||
var textComponents: [String] = []
|
var textComponents: [String] = []
|
||||||
if topPoster.messageCount > 0 {
|
if topPoster.messageCount > 0 {
|
||||||
textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount))
|
textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount))
|
||||||
@ -328,10 +365,19 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
textComponents.append(strings.Stats_GroupTopPosterChars(topPoster.averageChars))
|
textComponents.append(strings.Stats_GroupTopPosterChars(topPoster.averageChars))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
var options: [ItemListPeerItemRevealOption] = []
|
||||||
|
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
|
||||||
|
arguments.openPeerHistory(peer.id)
|
||||||
|
}))
|
||||||
|
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
|
||||||
|
arguments.promotePeer(peer.id)
|
||||||
|
}))
|
||||||
|
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
||||||
arguments.openPeer(peer.id)
|
arguments.openPeer(peer.id)
|
||||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin):
|
arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId)
|
||||||
|
}, removePeer: { _ in })
|
||||||
|
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed):
|
||||||
var textComponents: [String] = []
|
var textComponents: [String] = []
|
||||||
if topAdmin.deletedCount > 0 {
|
if topAdmin.deletedCount > 0 {
|
||||||
textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount))
|
textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount))
|
||||||
@ -342,29 +388,46 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
if topAdmin.bannedCount > 0 {
|
if topAdmin.bannedCount > 0 {
|
||||||
textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount))
|
textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount))
|
||||||
}
|
}
|
||||||
|
var options: [ItemListPeerItemRevealOption] = []
|
||||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopAdmin_Actions, action: {
|
||||||
|
arguments.openPeerAdminActions(peer.id)
|
||||||
|
}))
|
||||||
|
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopAdmin_Promote, action: {
|
||||||
|
arguments.promotePeer(peer.id)
|
||||||
|
}))
|
||||||
|
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
||||||
arguments.openPeer(peer.id)
|
arguments.openPeer(peer.id)
|
||||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter):
|
arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId)
|
||||||
|
}, removePeer: { _ in })
|
||||||
|
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed):
|
||||||
var textComponents: [String] = []
|
var textComponents: [String] = []
|
||||||
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
|
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
|
||||||
|
var options: [ItemListPeerItemRevealOption] = []
|
||||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
|
||||||
|
arguments.openPeerHistory(peer.id)
|
||||||
|
}))
|
||||||
|
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
|
||||||
|
arguments.promotePeer(peer.id)
|
||||||
|
}))
|
||||||
|
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
||||||
arguments.openPeer(peer.id)
|
arguments.openPeer(peer.id)
|
||||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
|
arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId)
|
||||||
|
}, removePeer: { _ in })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func groupStatsControllerEntries(data: GroupStats?, peers: [PeerId: Peer]?, presentationData: PresentationData) -> [StatsEntry] {
|
private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStats?, peers: [PeerId: Peer]?, presentationData: PresentationData) -> [StatsEntry] {
|
||||||
var entries: [StatsEntry] = []
|
var entries: [StatsEntry] = []
|
||||||
|
|
||||||
if let data = data {
|
if let data = data {
|
||||||
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
|
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
|
||||||
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
|
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
|
||||||
|
let dates = "\(minDate) – \(maxDate)"
|
||||||
|
|
||||||
entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) – \(maxDate)"))
|
entries.append(.overviewTitle(presentationData.theme, presentationData.strings.Stats_Overview, dates))
|
||||||
entries.append(.overview(presentationData.theme, data))
|
entries.append(.overview(presentationData.theme, data))
|
||||||
|
|
||||||
if !data.growthGraph.isEmpty {
|
if !data.growthGraph.isEmpty {
|
||||||
@ -399,36 +462,41 @@ private func groupStatsControllerEntries(data: GroupStats?, peers: [PeerId: Peer
|
|||||||
|
|
||||||
if !data.topHoursGraph.isEmpty {
|
if !data.topHoursGraph.isEmpty {
|
||||||
entries.append(.topHoursTitle(presentationData.theme, presentationData.strings.Stats_GroupTopHoursTitle))
|
entries.append(.topHoursTitle(presentationData.theme, presentationData.strings.Stats_GroupTopHoursTitle))
|
||||||
entries.append(.topHoursGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .lines))
|
entries.append(.topHoursGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .hourlyStep))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.topWeekdaysGraph.isEmpty {
|
||||||
|
entries.append(.topWeekdaysTitle(presentationData.theme, presentationData.strings.Stats_GroupTopWeekdaysTitle))
|
||||||
|
entries.append(.topWeekdaysGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topWeekdaysGraph, .area))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let peers = peers {
|
if let peers = peers {
|
||||||
if !data.topPosters.isEmpty {
|
if !data.topPosters.isEmpty {
|
||||||
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle))
|
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates))
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for topPoster in data.topPosters {
|
for topPoster in data.topPosters {
|
||||||
if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 {
|
if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 {
|
||||||
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster))
|
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !data.topAdmins.isEmpty {
|
if !data.topAdmins.isEmpty {
|
||||||
entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle))
|
entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle, dates))
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for topAdmin in data.topAdmins {
|
for topAdmin in data.topAdmins {
|
||||||
if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 {
|
if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 {
|
||||||
entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin))
|
entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !data.topInviters.isEmpty {
|
if !data.topInviters.isEmpty {
|
||||||
entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle))
|
entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle, dates))
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for topInviter in data.topInviters {
|
for topInviter in data.topInviters {
|
||||||
if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 {
|
if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 {
|
||||||
entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter))
|
entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,8 +507,55 @@ private func groupStatsControllerEntries(data: GroupStats?, peers: [PeerId: Peer
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct GroupStatsState: Equatable {
|
||||||
|
let posterPeerIdWithRevealedOptions: PeerId?
|
||||||
|
let adminPeerIdWithRevealedOptions: PeerId?
|
||||||
|
let inviterPeerIdWithRevealedOptions: PeerId?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.posterPeerIdWithRevealedOptions = nil
|
||||||
|
self.adminPeerIdWithRevealedOptions = nil
|
||||||
|
self.inviterPeerIdWithRevealedOptions = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init(posterPeerIdWithRevealedOptions: PeerId?, adminPeerIdWithRevealedOptions: PeerId?, inviterPeerIdWithRevealedOptions: PeerId?) {
|
||||||
|
self.posterPeerIdWithRevealedOptions = posterPeerIdWithRevealedOptions
|
||||||
|
self.adminPeerIdWithRevealedOptions = adminPeerIdWithRevealedOptions
|
||||||
|
self.inviterPeerIdWithRevealedOptions = inviterPeerIdWithRevealedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: GroupStatsState, rhs: GroupStatsState) -> Bool {
|
||||||
|
if lhs.posterPeerIdWithRevealedOptions != rhs.posterPeerIdWithRevealedOptions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.adminPeerIdWithRevealedOptions != rhs.adminPeerIdWithRevealedOptions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.inviterPeerIdWithRevealedOptions != rhs.inviterPeerIdWithRevealedOptions {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
|
||||||
|
return GroupStatsState(posterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
|
||||||
|
return GroupStatsState(posterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState {
|
||||||
|
return GroupStatsState(posterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func groupStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
public func groupStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
||||||
var openPeerImpl: ((PeerId) -> Void)?
|
let statePromise = ValuePromise(GroupStatsState())
|
||||||
|
let stateValue = Atomic(value: GroupStatsState())
|
||||||
|
let updateState: ((GroupStatsState) -> GroupStatsState) -> Void = { f in
|
||||||
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
|
}
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
let dataPromise = Promise<GroupStats?>(nil)
|
let dataPromise = Promise<GroupStats?>(nil)
|
||||||
@ -450,6 +565,14 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
|
|||||||
if let cachedData = cachedPeerData as? CachedChannelData {
|
if let cachedData = cachedPeerData as? CachedChannelData {
|
||||||
datacenterId = cachedData.statsDatacenterId
|
datacenterId = cachedData.statsDatacenterId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var openPeerImpl: ((PeerId) -> Void)?
|
||||||
|
var openPeerHistoryImpl: ((PeerId) -> Void)?
|
||||||
|
var openPeerAdminActionsImpl: ((PeerId) -> Void)?
|
||||||
|
var promotePeerImpl: ((PeerId) -> Void)?
|
||||||
|
|
||||||
|
let peerView = Promise<PeerView>()
|
||||||
|
peerView.set(context.account.viewTracker.peerView(peerId, updateData: true))
|
||||||
|
|
||||||
let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId)
|
let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId)
|
||||||
let dataSignal: Signal<GroupStats?, NoError> = statsContext.state
|
let dataSignal: Signal<GroupStats?, NoError> = statsContext.state
|
||||||
@ -457,7 +580,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
|
|||||||
return state.stats
|
return state.stats
|
||||||
} |> afterNext({ [weak statsContext] stats in
|
} |> afterNext({ [weak statsContext] stats in
|
||||||
if let statsContext = statsContext, let stats = stats {
|
if let statsContext = statsContext, let stats = stats {
|
||||||
if case .OnDemand = stats.newMembersBySourceGraph {
|
if case .OnDemand = stats.topWeekdaysGraph {
|
||||||
statsContext.loadGrowthGraph()
|
statsContext.loadGrowthGraph()
|
||||||
statsContext.loadMembersGraph()
|
statsContext.loadMembersGraph()
|
||||||
statsContext.loadNewMembersBySourceGraph()
|
statsContext.loadNewMembersBySourceGraph()
|
||||||
@ -465,6 +588,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
|
|||||||
statsContext.loadMessagesGraph()
|
statsContext.loadMessagesGraph()
|
||||||
statsContext.loadActionsGraph()
|
statsContext.loadActionsGraph()
|
||||||
statsContext.loadTopHoursGraph()
|
statsContext.loadTopHoursGraph()
|
||||||
|
statsContext.loadTopWeekdaysGraph()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -503,15 +627,45 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
|
|||||||
return statsContext.loadDetailedGraph(graph, x: x)
|
return statsContext.loadDetailedGraph(graph, x: x)
|
||||||
}, openPeer: { peerId in
|
}, openPeer: { peerId in
|
||||||
openPeerImpl?(peerId)
|
openPeerImpl?(peerId)
|
||||||
|
}, openPeerHistory: { peerId in
|
||||||
|
openPeerHistoryImpl?(peerId)
|
||||||
|
}, openPeerAdminActions: { peerId in
|
||||||
|
openPeerAdminActionsImpl?(peerId)
|
||||||
|
}, promotePeer: { peerId in
|
||||||
|
promotePeerImpl?(peerId)
|
||||||
|
}, setPostersPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
|
updateState { state in
|
||||||
|
if (peerId == nil && fromPeerId == state.posterPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||||
|
return state.withUpdatedPosterPeerIdWithRevealedOptions(peerId)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, setAdminsPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
|
updateState { state in
|
||||||
|
if (peerId == nil && fromPeerId == state.adminPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||||
|
return state.withUpdatedAdminPeerIdWithRevealedOptions(peerId)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, setInvitersPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||||
|
updateState { state in
|
||||||
|
if (peerId == nil && fromPeerId == state.inviterPeerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||||
|
return state.withUpdatedInviterPeerIdWithRevealedOptions(peerId)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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()))
|
||||||
|
|
||||||
let previousData = Atomic<GroupStats?>(value: nil)
|
let previousData = Atomic<GroupStats?>(value: nil)
|
||||||
|
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), peersPromise.get(), longLoadingSignal)
|
let signal = combineLatest(statePromise.get(), context.sharedContext.presentationData, dataPromise.get(), peersPromise.get(), longLoadingSignal)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData, data, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { state, presentationData, data, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
let previous = previousData.swap(data)
|
let previous = previousData.swap(data)
|
||||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||||
if data == nil {
|
if data == nil {
|
||||||
@ -523,7 +677,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(data: data, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(state: state, data: data, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -554,5 +708,35 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
openPeerHistoryImpl = { [weak controller] participantPeerId in
|
||||||
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
|
let _ = (context.account.postbox.loadedPeerWithId(participantPeerId)
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peerId), subject: nil, botStart: nil, updateTextInputState: nil, activateInput: false, keepStack: .always, useExisting: false, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: (.member(peer), ""), animated: true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openPeerAdminActionsImpl = { [weak controller] participantPeerId in
|
||||||
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer)
|
||||||
|
navigationController.pushViewController(controller)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promotePeerImpl = { [weak controller] participantPeerId in
|
||||||
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
|
let _ = (fetchChannelParticipant(account: context.account, peerId: peerId, participantId: participantPeerId)
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { participant in
|
||||||
|
if let participant = participant, let controller = context.sharedContext.makeChannelAdminController(context: context, peerId: peerId, adminId: participantPeerId, initialParticipant: participant) {
|
||||||
|
navigationController.pushViewController(controller)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
@ -544,7 +544,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) }
|
dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) }
|
||||||
dict[1957577280] = { return Api.Updates.parse_updates($0) }
|
dict[1957577280] = { return Api.Updates.parse_updates($0) }
|
||||||
dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) }
|
dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) }
|
||||||
dict[447818040] = { return Api.stats.MegagroupStats.parse_megagroupStats($0) }
|
dict[-276825834] = { return Api.stats.MegagroupStats.parse_megagroupStats($0) }
|
||||||
dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) }
|
dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) }
|
||||||
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
|
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
|
||||||
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
|
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
|
||||||
|
@ -663,13 +663,13 @@ public struct stats {
|
|||||||
|
|
||||||
}
|
}
|
||||||
public enum MegagroupStats: TypeConstructorDescription {
|
public enum MegagroupStats: TypeConstructorDescription {
|
||||||
case megagroupStats(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User])
|
case megagroupStats(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, weekdaysGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User])
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let topPosters, let topAdmins, let topInviters, let users):
|
case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let weekdaysGraph, let topPosters, let topAdmins, let topInviters, let users):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(447818040)
|
buffer.appendInt32(-276825834)
|
||||||
}
|
}
|
||||||
period.serialize(buffer, true)
|
period.serialize(buffer, true)
|
||||||
members.serialize(buffer, true)
|
members.serialize(buffer, true)
|
||||||
@ -683,6 +683,7 @@ public struct stats {
|
|||||||
messagesGraph.serialize(buffer, true)
|
messagesGraph.serialize(buffer, true)
|
||||||
actionsGraph.serialize(buffer, true)
|
actionsGraph.serialize(buffer, true)
|
||||||
topHoursGraph.serialize(buffer, true)
|
topHoursGraph.serialize(buffer, true)
|
||||||
|
weekdaysGraph.serialize(buffer, true)
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(topPosters.count))
|
buffer.appendInt32(Int32(topPosters.count))
|
||||||
for item in topPosters {
|
for item in topPosters {
|
||||||
@ -709,8 +710,8 @@ public struct stats {
|
|||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let topPosters, let topAdmins, let topInviters, let users):
|
case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let weekdaysGraph, let topPosters, let topAdmins, let topInviters, let users):
|
||||||
return ("megagroupStats", [("period", period), ("members", members), ("messages", messages), ("viewers", viewers), ("posters", posters), ("growthGraph", growthGraph), ("membersGraph", membersGraph), ("newMembersBySourceGraph", newMembersBySourceGraph), ("languagesGraph", languagesGraph), ("messagesGraph", messagesGraph), ("actionsGraph", actionsGraph), ("topHoursGraph", topHoursGraph), ("topPosters", topPosters), ("topAdmins", topAdmins), ("topInviters", topInviters), ("users", users)])
|
return ("megagroupStats", [("period", period), ("members", members), ("messages", messages), ("viewers", viewers), ("posters", posters), ("growthGraph", growthGraph), ("membersGraph", membersGraph), ("newMembersBySourceGraph", newMembersBySourceGraph), ("languagesGraph", languagesGraph), ("messagesGraph", messagesGraph), ("actionsGraph", actionsGraph), ("topHoursGraph", topHoursGraph), ("weekdaysGraph", weekdaysGraph), ("topPosters", topPosters), ("topAdmins", topAdmins), ("topInviters", topInviters), ("users", users)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,21 +764,25 @@ public struct stats {
|
|||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||||
}
|
}
|
||||||
var _13: [Api.StatsGroupTopPoster]?
|
var _13: Api.StatsGraph?
|
||||||
if let _ = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self)
|
_13 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||||
}
|
}
|
||||||
var _14: [Api.StatsGroupTopAdmin]?
|
var _14: [Api.StatsGroupTopPoster]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self)
|
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self)
|
||||||
}
|
}
|
||||||
var _15: [Api.StatsGroupTopInviter]?
|
var _15: [Api.StatsGroupTopAdmin]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self)
|
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self)
|
||||||
}
|
}
|
||||||
var _16: [Api.User]?
|
var _16: [Api.StatsGroupTopInviter]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
_16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self)
|
||||||
|
}
|
||||||
|
var _17: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
}
|
}
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
@ -795,8 +800,9 @@ public struct stats {
|
|||||||
let _c14 = _14 != nil
|
let _c14 = _14 != nil
|
||||||
let _c15 = _15 != nil
|
let _c15 = _15 != nil
|
||||||
let _c16 = _16 != nil
|
let _c16 = _16 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
|
let _c17 = _17 != nil
|
||||||
return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, topPosters: _13!, topAdmins: _14!, topInviters: _15!, users: _16!)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
|
||||||
|
return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -178,6 +178,10 @@ public final class CallController: ViewController {
|
|||||||
let _ = self?.call.hangUp()
|
let _ = self?.call.hangUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.controllerNode.toggleVideo = { [weak self] in
|
||||||
|
let _ = self?.call.setEnableVideo(true)
|
||||||
|
}
|
||||||
|
|
||||||
self.controllerNode.back = { [weak self] in
|
self.controllerNode.back = { [weak self] in
|
||||||
let _ = self?.dismiss()
|
let _ = self?.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ enum CallControllerButtonType {
|
|||||||
case accept
|
case accept
|
||||||
case speaker
|
case speaker
|
||||||
case bluetooth
|
case bluetooth
|
||||||
|
case video
|
||||||
}
|
}
|
||||||
|
|
||||||
private let buttonSize = CGSize(width: 75.0, height: 75.0)
|
private let buttonSize = CGSize(width: 75.0, height: 75.0)
|
||||||
@ -123,6 +124,11 @@ final class CallControllerButtonNode: HighlightTrackingButtonNode {
|
|||||||
regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear)
|
regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear)
|
||||||
highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill)
|
highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill)
|
||||||
filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true)
|
filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true)
|
||||||
|
case .video:
|
||||||
|
let patternImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconVideo"), color: .white)
|
||||||
|
regularImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: .clear)
|
||||||
|
highlightedImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: emptyHighlightedFill)
|
||||||
|
filledImage = generateEmptyButtonImage(icon: patternImage, strokeColor: nil, fillColor: invertedFill, knockout: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.regularImage = regularImage
|
self.regularImage = regularImage
|
||||||
@ -209,6 +215,11 @@ final class CallControllerButtonNode: HighlightTrackingButtonNode {
|
|||||||
regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear)
|
regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear)
|
||||||
highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill)
|
highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill)
|
||||||
filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true)
|
filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true)
|
||||||
|
case .video:
|
||||||
|
let patternImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconVideo"), color: .white)
|
||||||
|
regularImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: .clear)
|
||||||
|
highlightedImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: emptyHighlightedFill)
|
||||||
|
filledImage = generateEmptyButtonImage(icon: patternImage, strokeColor: nil, fillColor: invertedFill, knockout: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.regularImage = regularImage
|
self.regularImage = regularImage
|
||||||
|
@ -15,7 +15,13 @@ enum CallControllerButtonsSpeakerMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum CallControllerButtonsMode: Equatable {
|
enum CallControllerButtonsMode: Equatable {
|
||||||
case active(CallControllerButtonsSpeakerMode)
|
enum VideoState: Equatable {
|
||||||
|
case notAvailable
|
||||||
|
case available(Bool)
|
||||||
|
case active
|
||||||
|
}
|
||||||
|
|
||||||
|
case active(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState)
|
||||||
case incoming
|
case incoming
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +33,8 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
private let endButton: CallControllerButtonNode
|
private let endButton: CallControllerButtonNode
|
||||||
private let speakerButton: CallControllerButtonNode
|
private let speakerButton: CallControllerButtonNode
|
||||||
|
|
||||||
|
private let videoButton: CallControllerButtonNode
|
||||||
|
|
||||||
private var mode: CallControllerButtonsMode?
|
private var mode: CallControllerButtonsMode?
|
||||||
|
|
||||||
private var validLayout: CGFloat?
|
private var validLayout: CGFloat?
|
||||||
@ -41,6 +49,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
var mute: (() -> Void)?
|
var mute: (() -> Void)?
|
||||||
var end: (() -> Void)?
|
var end: (() -> Void)?
|
||||||
var speaker: (() -> Void)?
|
var speaker: (() -> Void)?
|
||||||
|
var toggleVideo: (() -> Void)?
|
||||||
|
|
||||||
init(strings: PresentationStrings) {
|
init(strings: PresentationStrings) {
|
||||||
self.acceptButton = CallControllerButtonNode(type: .accept, label: strings.Call_Accept)
|
self.acceptButton = CallControllerButtonNode(type: .accept, label: strings.Call_Accept)
|
||||||
@ -55,6 +64,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil)
|
self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil)
|
||||||
self.speakerButton.alpha = 0.0
|
self.speakerButton.alpha = 0.0
|
||||||
|
|
||||||
|
self.videoButton = CallControllerButtonNode(type: .video, label: nil)
|
||||||
|
self.videoButton.alpha = 0.0
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.acceptButton)
|
self.addSubnode(self.acceptButton)
|
||||||
@ -62,12 +74,14 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.muteButton)
|
self.addSubnode(self.muteButton)
|
||||||
self.addSubnode(self.endButton)
|
self.addSubnode(self.endButton)
|
||||||
self.addSubnode(self.speakerButton)
|
self.addSubnode(self.speakerButton)
|
||||||
|
self.addSubnode(self.videoButton)
|
||||||
|
|
||||||
self.acceptButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
self.acceptButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
||||||
self.declineButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
self.declineButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
||||||
self.muteButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
self.muteButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
||||||
self.endButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
self.endButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
||||||
self.speakerButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
self.speakerButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
||||||
|
self.videoButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) {
|
func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -107,6 +121,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
var origin = CGPoint(x: floor((width - threeButtonsWidth) / 2.0), y: 0.0)
|
var origin = CGPoint(x: floor((width - threeButtonsWidth) / 2.0), y: 0.0)
|
||||||
for button in [self.muteButton, self.endButton, self.speakerButton] {
|
for button in [self.muteButton, self.endButton, self.speakerButton] {
|
||||||
transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize))
|
transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize))
|
||||||
|
|
||||||
|
if button === self.endButton {
|
||||||
|
transition.updateFrame(node: self.videoButton, frame: CGRect(origin: CGPoint(x: origin.x, y: origin.y - buttonSize.height - 20.0), size: buttonSize))
|
||||||
|
}
|
||||||
|
|
||||||
origin.x += buttonSize.width + threeButtonSpacing
|
origin.x += buttonSize.width + threeButtonSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,10 +140,10 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
for button in [self.declineButton, self.acceptButton] {
|
for button in [self.declineButton, self.acceptButton] {
|
||||||
button.alpha = 1.0
|
button.alpha = 1.0
|
||||||
}
|
}
|
||||||
for button in [self.muteButton, self.endButton, self.speakerButton] {
|
for button in [self.muteButton, self.endButton, self.speakerButton, self.videoButton] {
|
||||||
button.alpha = 0.0
|
button.alpha = 0.0
|
||||||
}
|
}
|
||||||
case let .active(speakerMode):
|
case let .active(speakerMode, videoState):
|
||||||
for button in [self.muteButton, self.speakerButton] {
|
for button in [self.muteButton, self.speakerButton] {
|
||||||
if animated && button.alpha.isZero {
|
if animated && button.alpha.isZero {
|
||||||
button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
@ -152,6 +171,23 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
self.endButton.alpha = 1.0
|
self.endButton.alpha = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch videoState {
|
||||||
|
case .notAvailable:
|
||||||
|
self.videoButton.alpha = 0.0
|
||||||
|
case let .available(isEnabled):
|
||||||
|
self.videoButton.isUserInteractionEnabled = isEnabled
|
||||||
|
if animated {
|
||||||
|
self.videoButton.alpha = isEnabled ? 1.0 : 0.5
|
||||||
|
self.videoButton.layer.animateAlpha(from: 0.0, to: self.videoButton.alpha, duration: 0.2)
|
||||||
|
} else {
|
||||||
|
self.videoButton.alpha = isEnabled ? 1.0 : 0.5
|
||||||
|
}
|
||||||
|
case .active:
|
||||||
|
self.videoButton.isUserInteractionEnabled = true
|
||||||
|
self.videoButton.alpha = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if !self.declineButton.alpha.isZero {
|
if !self.declineButton.alpha.isZero {
|
||||||
if animated {
|
if animated {
|
||||||
self.declineButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
self.declineButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||||
@ -187,6 +223,26 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
|||||||
self.speaker?()
|
self.speaker?()
|
||||||
} else if button === self.acceptButton {
|
} else if button === self.acceptButton {
|
||||||
self.accept?()
|
self.accept?()
|
||||||
|
} else if button === self.videoButton {
|
||||||
|
self.toggleVideo?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
let buttons = [
|
||||||
|
self.acceptButton,
|
||||||
|
self.declineButton,
|
||||||
|
self.muteButton,
|
||||||
|
self.endButton,
|
||||||
|
self.speakerButton,
|
||||||
|
self.videoButton
|
||||||
|
]
|
||||||
|
for button in buttons {
|
||||||
|
if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,49 @@ import LocalizedPeerData
|
|||||||
import PhotoResources
|
import PhotoResources
|
||||||
import CallsEmoji
|
import CallsEmoji
|
||||||
|
|
||||||
|
private final class IncomingVideoNode: ASDisplayNode {
|
||||||
|
private let videoView: UIView
|
||||||
|
|
||||||
|
init(videoView: UIView) {
|
||||||
|
self.videoView = videoView
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.view.addSubview(self.videoView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize) {
|
||||||
|
self.videoView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class OutgoingVideoNode: ASDisplayNode {
|
||||||
|
private let videoView: UIView
|
||||||
|
private let switchCameraButton: HighlightableButtonNode
|
||||||
|
private let switchCamera: () -> Void
|
||||||
|
|
||||||
|
init(videoView: UIView, switchCamera: @escaping () -> Void) {
|
||||||
|
self.videoView = videoView
|
||||||
|
self.switchCameraButton = HighlightableButtonNode()
|
||||||
|
self.switchCamera = switchCamera
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.view.addSubview(self.videoView)
|
||||||
|
self.addSubnode(self.switchCameraButton)
|
||||||
|
self.switchCameraButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.switchCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize) {
|
||||||
|
self.videoView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.switchCameraButton.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class CallControllerNode: ASDisplayNode {
|
final class CallControllerNode: ASDisplayNode {
|
||||||
private let sharedContext: SharedAccountContext
|
private let sharedContext: SharedAccountContext
|
||||||
private let account: Account
|
private let account: Account
|
||||||
@ -31,8 +74,8 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
private var incomingVideoView: UIView?
|
private var incomingVideoNode: IncomingVideoNode?
|
||||||
private var outgoingVideoView: UIView?
|
private var outgoingVideoNode: OutgoingVideoNode?
|
||||||
private var videoViewsRequested: Bool = false
|
private var videoViewsRequested: Bool = false
|
||||||
private let backButtonArrowNode: ASImageNode
|
private let backButtonArrowNode: ASImageNode
|
||||||
private let backButtonNode: HighlightableButtonNode
|
private let backButtonNode: HighlightableButtonNode
|
||||||
@ -63,6 +106,7 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
var beginAudioOuputSelection: (() -> Void)?
|
var beginAudioOuputSelection: (() -> Void)?
|
||||||
var acceptCall: (() -> Void)?
|
var acceptCall: (() -> Void)?
|
||||||
var endCall: (() -> Void)?
|
var endCall: (() -> Void)?
|
||||||
|
var toggleVideo: (() -> Void)?
|
||||||
var back: (() -> Void)?
|
var back: (() -> Void)?
|
||||||
var presentCallRating: ((CallId) -> Void)?
|
var presentCallRating: ((CallId) -> Void)?
|
||||||
var callEnded: ((Bool) -> Void)?
|
var callEnded: ((Bool) -> Void)?
|
||||||
@ -151,6 +195,10 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
self?.acceptCall?()
|
self?.acceptCall?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.buttonsNode.toggleVideo = { [weak self] in
|
||||||
|
self?.toggleVideo?()
|
||||||
|
}
|
||||||
|
|
||||||
self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside)
|
self.keyButtonNode.addTarget(self, action: #selector(self.keyPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
|
self.backButtonNode.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
|
||||||
@ -205,7 +253,58 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
let statusValue: CallControllerStatusValue
|
let statusValue: CallControllerStatusValue
|
||||||
var statusReception: Int32?
|
var statusReception: Int32?
|
||||||
switch callState {
|
|
||||||
|
switch callState.videoState {
|
||||||
|
case .active:
|
||||||
|
if !self.videoViewsRequested {
|
||||||
|
self.videoViewsRequested = true
|
||||||
|
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let incomingVideoView = incomingVideoView {
|
||||||
|
strongSelf.setCurrentAudioOutput?(.speaker)
|
||||||
|
let incomingVideoNode = IncomingVideoNode(videoView: incomingVideoView)
|
||||||
|
strongSelf.incomingVideoNode = incomingVideoNode
|
||||||
|
strongSelf.containerNode.insertSubnode(incomingVideoNode, aboveSubnode: strongSelf.dimNode)
|
||||||
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let outgoingVideoView = outgoingVideoView {
|
||||||
|
outgoingVideoView.backgroundColor = .black
|
||||||
|
outgoingVideoView.clipsToBounds = true
|
||||||
|
outgoingVideoView.layer.cornerRadius = 16.0
|
||||||
|
strongSelf.setCurrentAudioOutput?(.speaker)
|
||||||
|
let outgoingVideoNode = OutgoingVideoNode(videoView: outgoingVideoView, switchCamera: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.call.switchVideoCamera()
|
||||||
|
})
|
||||||
|
strongSelf.outgoingVideoNode = outgoingVideoNode
|
||||||
|
if let incomingVideoNode = strongSelf.incomingVideoNode {
|
||||||
|
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: incomingVideoNode)
|
||||||
|
} else {
|
||||||
|
strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode)
|
||||||
|
}
|
||||||
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch callState.state {
|
||||||
case .waiting, .connecting:
|
case .waiting, .connecting:
|
||||||
statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
|
statusValue = .text(self.presentationData.strings.Call_StatusConnecting)
|
||||||
case let .requesting(ringing):
|
case let .requesting(ringing):
|
||||||
@ -241,7 +340,7 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
case .active(let timestamp, let reception, let keyVisualHash), .reconnecting(let timestamp, let reception, let keyVisualHash):
|
case .active(let timestamp, let reception, let keyVisualHash), .reconnecting(let timestamp, let reception, let keyVisualHash):
|
||||||
let strings = self.presentationData.strings
|
let strings = self.presentationData.strings
|
||||||
var isReconnecting = false
|
var isReconnecting = false
|
||||||
if case .reconnecting = callState {
|
if case .reconnecting = callState.state {
|
||||||
isReconnecting = true
|
isReconnecting = true
|
||||||
}
|
}
|
||||||
statusValue = .timer({ value in
|
statusValue = .timer({ value in
|
||||||
@ -266,45 +365,8 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
statusReception = reception
|
statusReception = reception
|
||||||
if !self.videoViewsRequested {
|
|
||||||
self.videoViewsRequested = true
|
|
||||||
self.call.makeIncomingVideoView(completion: { [weak self] incomingVideoView in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let incomingVideoView = incomingVideoView {
|
|
||||||
strongSelf.setCurrentAudioOutput?(.speaker)
|
|
||||||
strongSelf.incomingVideoView = incomingVideoView
|
|
||||||
strongSelf.containerNode.view.insertSubview(incomingVideoView, aboveSubview: strongSelf.dimNode.view)
|
|
||||||
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let outgoingVideoView = outgoingVideoView {
|
|
||||||
outgoingVideoView.backgroundColor = .black
|
|
||||||
outgoingVideoView.clipsToBounds = true
|
|
||||||
outgoingVideoView.layer.cornerRadius = 16.0
|
|
||||||
strongSelf.setCurrentAudioOutput?(.speaker)
|
|
||||||
strongSelf.outgoingVideoView = outgoingVideoView
|
|
||||||
if let incomingVideoView = strongSelf.incomingVideoView {
|
|
||||||
strongSelf.containerNode.view.insertSubview(outgoingVideoView, aboveSubview: incomingVideoView)
|
|
||||||
} else {
|
|
||||||
strongSelf.containerNode.view.insertSubview(outgoingVideoView, aboveSubview: strongSelf.dimNode.view)
|
|
||||||
}
|
|
||||||
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switch callState {
|
switch callState.state {
|
||||||
case .terminated, .terminating:
|
case .terminated, .terminating:
|
||||||
if !self.statusNode.alpha.isEqual(to: 0.5) {
|
if !self.statusNode.alpha.isEqual(to: 0.5) {
|
||||||
self.statusNode.alpha = 0.5
|
self.statusNode.alpha = 0.5
|
||||||
@ -327,7 +389,7 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.shouldStayHiddenUntilConnection {
|
if self.shouldStayHiddenUntilConnection {
|
||||||
switch callState {
|
switch callState.state {
|
||||||
case .connecting, .active:
|
case .connecting, .active:
|
||||||
self.containerNode.alpha = 1.0
|
self.containerNode.alpha = 1.0
|
||||||
default:
|
default:
|
||||||
@ -339,7 +401,7 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.updateButtonsMode()
|
self.updateButtonsMode()
|
||||||
|
|
||||||
if case let .terminated(id, _, reportRating) = callState, let callId = id {
|
if case let .terminated(id, _, reportRating) = callState.state, let callId = id {
|
||||||
let presentRating = reportRating || self.forceReportRating
|
let presentRating = reportRating || self.forceReportRating
|
||||||
if presentRating {
|
if presentRating {
|
||||||
self.presentCallRating?(callId)
|
self.presentCallRating?(callId)
|
||||||
@ -353,7 +415,7 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch callState {
|
switch callState.state {
|
||||||
case .ringing:
|
case .ringing:
|
||||||
self.buttonsNode.updateMode(.incoming)
|
self.buttonsNode.updateMode(.incoming)
|
||||||
default:
|
default:
|
||||||
@ -373,7 +435,16 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
mode = .none
|
mode = .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.buttonsNode.updateMode(.active(mode))
|
let mappedVideoState: CallControllerButtonsMode.VideoState
|
||||||
|
switch callState.videoState {
|
||||||
|
case .notAvailable:
|
||||||
|
mappedVideoState = .notAvailable
|
||||||
|
case .available:
|
||||||
|
mappedVideoState = .available(true)
|
||||||
|
case .active:
|
||||||
|
mappedVideoState = .active
|
||||||
|
}
|
||||||
|
self.buttonsNode.updateMode(.active(speakerMode: mode, videoState: mappedVideoState))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,12 +537,15 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
let buttonsOriginY: CGFloat = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom
|
let buttonsOriginY: CGFloat = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom
|
||||||
transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight)))
|
transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight)))
|
||||||
|
|
||||||
if let incomingVideoView = self.incomingVideoView {
|
if let incomingVideoNode = self.incomingVideoNode {
|
||||||
incomingVideoView.frame = CGRect(origin: CGPoint(), size: layout.size)
|
incomingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
incomingVideoNode.updateLayout(size: layout.size)
|
||||||
}
|
}
|
||||||
if let outgoingVideoView = self.outgoingVideoView {
|
if let outgoingVideoNode = self.outgoingVideoNode {
|
||||||
let outgoingSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0))
|
let outgoingSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0))
|
||||||
outgoingVideoView.frame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - outgoingSize.width, y: buttonsOriginY - 32.0 - outgoingSize.height), size: outgoingSize)
|
let outgoingFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - outgoingSize.width, y: buttonsOriginY - 32.0 - outgoingSize.height), size: outgoingSize)
|
||||||
|
outgoingVideoNode.frame = outgoingFrame
|
||||||
|
outgoingVideoNode.updateLayout(size: outgoingFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyTextSize = self.keyButtonNode.frame.size
|
let keyTextSize = self.keyButtonNode.frame.size
|
||||||
|
@ -188,7 +188,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
|
|
||||||
private var sessionStateDisposable: Disposable?
|
private var sessionStateDisposable: Disposable?
|
||||||
|
|
||||||
private let statePromise = ValuePromise<PresentationCallState>(.waiting, ignoreRepeated: true)
|
private let statePromise = ValuePromise<PresentationCallState>(PresentationCallState(state: .waiting, videoState: .notAvailable), ignoreRepeated: true)
|
||||||
public var state: Signal<PresentationCallState, NoError> {
|
public var state: Signal<PresentationCallState, NoError> {
|
||||||
return self.statePromise.get()
|
return self.statePromise.get()
|
||||||
}
|
}
|
||||||
@ -402,7 +402,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
|
|
||||||
switch sessionState.state {
|
switch sessionState.state {
|
||||||
case .ringing:
|
case .ringing:
|
||||||
presentationState = .ringing
|
presentationState = PresentationCallState(state: .ringing, videoState: .notAvailable)
|
||||||
if previous == nil || previousControl == nil {
|
if previous == nil || previousControl == nil {
|
||||||
if !self.reportedIncomingCall {
|
if !self.reportedIncomingCall {
|
||||||
self.reportedIncomingCall = true
|
self.reportedIncomingCall = true
|
||||||
@ -429,19 +429,28 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
}
|
}
|
||||||
case .accepting:
|
case .accepting:
|
||||||
self.callWasActive = true
|
self.callWasActive = true
|
||||||
presentationState = .connecting(nil)
|
presentationState = PresentationCallState(state: .connecting(nil), videoState: .notAvailable)
|
||||||
case .dropping:
|
case .dropping:
|
||||||
presentationState = .terminating
|
presentationState = PresentationCallState(state: .terminating, videoState: .notAvailable)
|
||||||
case let .terminated(id, reason, options):
|
case let .terminated(id, reason, options):
|
||||||
presentationState = .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating))
|
presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: .notAvailable)
|
||||||
case let .requesting(ringing):
|
case let .requesting(ringing):
|
||||||
presentationState = .requesting(ringing)
|
presentationState = PresentationCallState(state: .requesting(ringing), videoState: .notAvailable)
|
||||||
case let .active(_, _, keyVisualHash, _, _, _, _):
|
case let .active(_, _, keyVisualHash, _, _, _, _):
|
||||||
self.callWasActive = true
|
self.callWasActive = true
|
||||||
if let callContextState = callContextState {
|
if let callContextState = callContextState {
|
||||||
switch callContextState {
|
let mappedVideoState: PresentationCallState.VideoState
|
||||||
|
switch callContextState.videoState {
|
||||||
|
case .notAvailable:
|
||||||
|
mappedVideoState = .notAvailable
|
||||||
|
case let .available(enabled):
|
||||||
|
mappedVideoState = .available(enabled)
|
||||||
|
case .active:
|
||||||
|
mappedVideoState = .active
|
||||||
|
}
|
||||||
|
switch callContextState.state {
|
||||||
case .initializing:
|
case .initializing:
|
||||||
presentationState = .connecting(keyVisualHash)
|
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState)
|
||||||
case .failed:
|
case .failed:
|
||||||
presentationState = nil
|
presentationState = nil
|
||||||
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil))
|
||||||
@ -453,7 +462,7 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
timestamp = CFAbsoluteTimeGetCurrent()
|
timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
self.activeTimestamp = timestamp
|
self.activeTimestamp = timestamp
|
||||||
}
|
}
|
||||||
presentationState = .active(timestamp, reception, keyVisualHash)
|
presentationState = PresentationCallState(state: .active(timestamp, reception, keyVisualHash), videoState: mappedVideoState)
|
||||||
case .reconnecting:
|
case .reconnecting:
|
||||||
let timestamp: Double
|
let timestamp: Double
|
||||||
if let activeTimestamp = self.activeTimestamp {
|
if let activeTimestamp = self.activeTimestamp {
|
||||||
@ -462,10 +471,10 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
timestamp = CFAbsoluteTimeGetCurrent()
|
timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
self.activeTimestamp = timestamp
|
self.activeTimestamp = timestamp
|
||||||
}
|
}
|
||||||
presentationState = .reconnecting(timestamp, reception, keyVisualHash)
|
presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
presentationState = .connecting(keyVisualHash)
|
presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: .notAvailable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,12 +564,12 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
|
|
||||||
private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) {
|
private func updateTone(_ state: PresentationCallState, callContextState: OngoingCallContextState?, previous: CallSession?) {
|
||||||
var tone: PresentationCallTone?
|
var tone: PresentationCallTone?
|
||||||
if let callContextState = callContextState, case .reconnecting = callContextState {
|
if let callContextState = callContextState, case .reconnecting = callContextState.state {
|
||||||
tone = .connecting
|
tone = .connecting
|
||||||
} else if let previous = previous {
|
} else if let previous = previous {
|
||||||
switch previous.state {
|
switch previous.state {
|
||||||
case .accepting, .active, .dropping, .requesting:
|
case .accepting, .active, .dropping, .requesting:
|
||||||
switch state {
|
switch state.state {
|
||||||
case .connecting:
|
case .connecting:
|
||||||
if case .requesting = previous.state {
|
if case .requesting = previous.state {
|
||||||
tone = .ringing
|
tone = .ringing
|
||||||
@ -652,6 +661,14 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
self.ongoingContext?.setIsMuted(self.isMutedValue)
|
self.ongoingContext?.setIsMuted(self.isMutedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setEnableVideo(_ value: Bool) {
|
||||||
|
self.ongoingContext?.setEnableVideo(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func switchVideoCamera() {
|
||||||
|
self.ongoingContext?.switchVideoCamera()
|
||||||
|
}
|
||||||
|
|
||||||
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
||||||
guard self.currentAudioOutputValue != output else {
|
guard self.currentAudioOutputValue != output else {
|
||||||
return
|
return
|
||||||
|
@ -553,11 +553,12 @@ public final class GroupStats: Equatable {
|
|||||||
public let messagesGraph: StatsGraph
|
public let messagesGraph: StatsGraph
|
||||||
public let actionsGraph: StatsGraph
|
public let actionsGraph: StatsGraph
|
||||||
public let topHoursGraph: StatsGraph
|
public let topHoursGraph: StatsGraph
|
||||||
|
public let topWeekdaysGraph: StatsGraph
|
||||||
public let topPosters: [GroupStatsTopPoster]
|
public let topPosters: [GroupStatsTopPoster]
|
||||||
public let topAdmins: [GroupStatsTopAdmin]
|
public let topAdmins: [GroupStatsTopAdmin]
|
||||||
public let topInviters: [GroupStatsTopInviter]
|
public let topInviters: [GroupStatsTopInviter]
|
||||||
|
|
||||||
init(period: StatsDateRange, members: StatsValue, messages: StatsValue, viewers: StatsValue, posters: StatsValue, growthGraph: StatsGraph, membersGraph: StatsGraph, newMembersBySourceGraph: StatsGraph, languagesGraph: StatsGraph, messagesGraph: StatsGraph, actionsGraph: StatsGraph, topHoursGraph: StatsGraph, topPosters: [GroupStatsTopPoster], topAdmins: [GroupStatsTopAdmin], topInviters: [GroupStatsTopInviter]) {
|
init(period: StatsDateRange, members: StatsValue, messages: StatsValue, viewers: StatsValue, posters: StatsValue, growthGraph: StatsGraph, membersGraph: StatsGraph, newMembersBySourceGraph: StatsGraph, languagesGraph: StatsGraph, messagesGraph: StatsGraph, actionsGraph: StatsGraph, topHoursGraph: StatsGraph, topWeekdaysGraph: StatsGraph, topPosters: [GroupStatsTopPoster], topAdmins: [GroupStatsTopAdmin], topInviters: [GroupStatsTopInviter]) {
|
||||||
self.period = period
|
self.period = period
|
||||||
self.members = members
|
self.members = members
|
||||||
self.messages = messages
|
self.messages = messages
|
||||||
@ -570,6 +571,7 @@ public final class GroupStats: Equatable {
|
|||||||
self.messagesGraph = messagesGraph
|
self.messagesGraph = messagesGraph
|
||||||
self.actionsGraph = actionsGraph
|
self.actionsGraph = actionsGraph
|
||||||
self.topHoursGraph = topHoursGraph
|
self.topHoursGraph = topHoursGraph
|
||||||
|
self.topWeekdaysGraph = topWeekdaysGraph
|
||||||
self.topPosters = topPosters
|
self.topPosters = topPosters
|
||||||
self.topAdmins = topAdmins
|
self.topAdmins = topAdmins
|
||||||
self.topInviters = topInviters
|
self.topInviters = topInviters
|
||||||
@ -612,6 +614,9 @@ public final class GroupStats: Equatable {
|
|||||||
if lhs.topHoursGraph != rhs.topHoursGraph {
|
if lhs.topHoursGraph != rhs.topHoursGraph {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.topWeekdaysGraph != rhs.topWeekdaysGraph {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.topPosters != rhs.topPosters {
|
if lhs.topPosters != rhs.topPosters {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -625,31 +630,35 @@ public final class GroupStats: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedGrowthGraph(_ growthGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedGrowthGraph(_ growthGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedMembersGraph(_ membersGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedMembersGraph(_ membersGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedNewMembersBySourceGraph(_ newMembersBySourceGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedNewMembersBySourceGraph(_ newMembersBySourceGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedLanguagesGraph(_ languagesGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedLanguagesGraph(_ languagesGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedMessagesGraph(_ messagesGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedMessagesGraph(_ messagesGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedActionsGraph(_ actionsGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedActionsGraph(_ actionsGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: actionsGraph, topHoursGraph: self.topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedTopHoursGraph(_ topHoursGraph: StatsGraph) -> GroupStats {
|
public func withUpdatedTopHoursGraph(_ topHoursGraph: StatsGraph) -> GroupStats {
|
||||||
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: topHoursGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: topHoursGraph, topWeekdaysGraph: self.topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func withUpdatedTopWeekdaysGraph(_ topWeekdaysGraph: StatsGraph) -> GroupStats {
|
||||||
|
return GroupStats(period: self.period, members: self.members, messages: self.messages, viewers: self.viewers, posters: self.posters, growthGraph: self.growthGraph, membersGraph: self.membersGraph, newMembersBySourceGraph: self.newMembersBySourceGraph, languagesGraph: self.languagesGraph, messagesGraph: self.messagesGraph, actionsGraph: self.actionsGraph, topHoursGraph: self.topHoursGraph, topWeekdaysGraph: topWeekdaysGraph, topPosters: self.topPosters, topAdmins: self.topAdmins, topInviters: self.topInviters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,7 +693,7 @@ private func requestGroupStats(postbox: Postbox, network: Network, datacenterId:
|
|||||||
return signal
|
return signal
|
||||||
|> mapToSignal { result -> Signal<GroupStats?, MTRpcError> in
|
|> mapToSignal { result -> Signal<GroupStats?, MTRpcError> in
|
||||||
return postbox.transaction { transaction -> GroupStats? in
|
return postbox.transaction { transaction -> GroupStats? in
|
||||||
if case let .megagroupStats(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, users) = result {
|
if case let .megagroupStats(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, users) = result {
|
||||||
var parsedUsers: [Peer] = []
|
var parsedUsers: [Peer] = []
|
||||||
for user in users {
|
for user in users {
|
||||||
parsedUsers.append(TelegramUser(user: user))
|
parsedUsers.append(TelegramUser(user: user))
|
||||||
@ -858,6 +867,21 @@ private final class GroupStatsContextImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadTopWeekdaysGraph() {
|
||||||
|
guard let stats = self._state.stats else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case let .OnDemand(token) = stats.topWeekdaysGraph {
|
||||||
|
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] graph in
|
||||||
|
if let strongSelf = self, let graph = graph {
|
||||||
|
strongSelf._state = GroupStatsContextState(stats: strongSelf._state.stats?.withUpdatedTopWeekdaysGraph(graph))
|
||||||
|
strongSelf._statePromise.set(.single(strongSelf._state))
|
||||||
|
}
|
||||||
|
}), forKey: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
||||||
if let token = graph.token {
|
if let token = graph.token {
|
||||||
return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x)
|
return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x)
|
||||||
@ -930,6 +954,12 @@ public final class GroupStatsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func loadTopWeekdaysGraph() {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.loadTopWeekdaysGraph()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
public func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal<StatsGraph?, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
@ -973,7 +1003,11 @@ extension StatsGraph {
|
|||||||
case let .statsGraphError(error):
|
case let .statsGraphError(error):
|
||||||
self = .Failed(error: error)
|
self = .Failed(error: error)
|
||||||
case let .statsGraphAsync(token):
|
case let .statsGraphAsync(token):
|
||||||
self = .OnDemand(token: token)
|
if !token.isEmpty {
|
||||||
|
self = .OnDemand(token: token)
|
||||||
|
} else {
|
||||||
|
self = .Failed(error: "An error occured. Please try again later.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1056,11 +1090,11 @@ extension GroupStatsTopInviter {
|
|||||||
extension GroupStats {
|
extension GroupStats {
|
||||||
convenience init(apiMegagroupStats: Api.stats.MegagroupStats) {
|
convenience init(apiMegagroupStats: Api.stats.MegagroupStats) {
|
||||||
switch apiMegagroupStats {
|
switch apiMegagroupStats {
|
||||||
case let .megagroupStats(period, members, messages, viewers, posters, apiGrowthGraph, apiMembersGraph, apiNewMembersBySourceGraph, apiLanguagesGraph, apiMessagesGraph, apiActionsGraph, apiTopHoursGraph, topPosters, topAdmins, topInviters, users):
|
case let .megagroupStats(period, members, messages, viewers, posters, apiGrowthGraph, apiMembersGraph, apiNewMembersBySourceGraph, apiLanguagesGraph, apiMessagesGraph, apiActionsGraph, apiTopHoursGraph, apiTopWeekdaysGraph, topPosters, topAdmins, topInviters, users):
|
||||||
let growthGraph = StatsGraph(apiStatsGraph: apiGrowthGraph)
|
let growthGraph = StatsGraph(apiStatsGraph: apiGrowthGraph)
|
||||||
let isEmpty = growthGraph.isEmpty
|
let isEmpty = growthGraph.isEmpty
|
||||||
|
|
||||||
self.init(period: StatsDateRange(apiStatsDateRangeDays: period), members: StatsValue(apiStatsAbsValueAndPrev: members), messages: StatsValue(apiStatsAbsValueAndPrev: messages), viewers: StatsValue(apiStatsAbsValueAndPrev: viewers), posters: StatsValue(apiStatsAbsValueAndPrev: posters), growthGraph: growthGraph, membersGraph: StatsGraph(apiStatsGraph: apiMembersGraph), newMembersBySourceGraph: StatsGraph(apiStatsGraph: apiNewMembersBySourceGraph), languagesGraph: StatsGraph(apiStatsGraph: apiLanguagesGraph), messagesGraph: StatsGraph(apiStatsGraph: apiMessagesGraph), actionsGraph: StatsGraph(apiStatsGraph: apiActionsGraph), topHoursGraph: StatsGraph(apiStatsGraph: apiTopHoursGraph), topPosters: topPosters.map { GroupStatsTopPoster(apiStatsGroupTopPoster: $0) }, topAdmins: topAdmins.map { GroupStatsTopAdmin(apiStatsGroupTopAdmin: $0) }, topInviters: topInviters.map { GroupStatsTopInviter(apiStatsGroupTopInviter: $0) })
|
self.init(period: StatsDateRange(apiStatsDateRangeDays: period), members: StatsValue(apiStatsAbsValueAndPrev: members), messages: StatsValue(apiStatsAbsValueAndPrev: messages), viewers: StatsValue(apiStatsAbsValueAndPrev: viewers), posters: StatsValue(apiStatsAbsValueAndPrev: posters), growthGraph: growthGraph, membersGraph: StatsGraph(apiStatsGraph: apiMembersGraph), newMembersBySourceGraph: StatsGraph(apiStatsGraph: apiNewMembersBySourceGraph), languagesGraph: StatsGraph(apiStatsGraph: apiLanguagesGraph), messagesGraph: StatsGraph(apiStatsGraph: apiMessagesGraph), actionsGraph: StatsGraph(apiStatsGraph: apiActionsGraph), topHoursGraph: StatsGraph(apiStatsGraph: apiTopHoursGraph), topWeekdaysGraph: StatsGraph(apiStatsGraph: apiTopWeekdaysGraph), topPosters: topPosters.map { GroupStatsTopPoster(apiStatsGroupTopPoster: $0) }, topAdmins: topAdmins.map { GroupStatsTopAdmin(apiStatsGroupTopAdmin: $0) }, topInviters: topInviters.map { GroupStatsTopInviter(apiStatsGroupTopInviter: $0) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -314,7 +314,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
private var isDismissed = false
|
private var isDismissed = false
|
||||||
|
|
||||||
private var focusOnSearchAfterAppearance: Bool = false
|
private var focusOnSearchAfterAppearance: (ChatSearchDomain, String)?
|
||||||
|
|
||||||
private let keepPeerInfoScreenDataHotDisposable = MetaDisposable()
|
private let keepPeerInfoScreenDataHotDisposable = MetaDisposable()
|
||||||
|
|
||||||
@ -2550,7 +2550,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
$0.updatedInputTextPanelState { panelState in
|
$0.updatedInputTextPanelState { panelState in
|
||||||
if let videoRecorder = videoRecorder {
|
if let videoRecorder = videoRecorder {
|
||||||
if panelState.mediaRecordingState == nil {
|
if panelState.mediaRecordingState == nil {
|
||||||
return panelState.withUpdatedMediaRecordingState(.video(status: .recording(videoRecorder.audioStatus), isLocked: false))
|
return panelState.withUpdatedMediaRecordingState(.video(status: .recording(videoRecorder.audioStatus), isLocked: strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return panelState.withUpdatedMediaRecordingState(nil)
|
return panelState.withUpdatedMediaRecordingState(nil)
|
||||||
@ -2562,12 +2562,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let videoRecorder = videoRecorder {
|
if let videoRecorder = videoRecorder {
|
||||||
strongSelf.recorderFeedback?.impact(.light)
|
strongSelf.recorderFeedback?.impact(.light)
|
||||||
|
|
||||||
videoRecorder.onDismiss = {
|
videoRecorder.onDismiss = { [weak self] isCancelled in
|
||||||
if let strongSelf = self {
|
self?.chatDisplayNode.updateRecordedMediaDeleted(isCancelled)
|
||||||
strongSelf.beginMediaRecordingRequestId += 1
|
self?.beginMediaRecordingRequestId += 1
|
||||||
strongSelf.lockMediaRecordingRequestId = nil
|
self?.lockMediaRecordingRequestId = nil
|
||||||
strongSelf.videoRecorder.set(.single(nil))
|
self?.videoRecorder.set(.single(nil))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
videoRecorder.onStop = {
|
videoRecorder.onStop = {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -4738,9 +4737,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
self.interfaceInteraction = interfaceInteraction
|
self.interfaceInteraction = interfaceInteraction
|
||||||
|
|
||||||
if self.focusOnSearchAfterAppearance {
|
if let search = self.focusOnSearchAfterAppearance {
|
||||||
self.focusOnSearchAfterAppearance = false
|
self.focusOnSearchAfterAppearance = nil
|
||||||
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
self.interfaceInteraction?.beginMessageSearch(search.0, search.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.chatDisplayNode.interfaceInteraction = interfaceInteraction
|
self.chatDisplayNode.interfaceInteraction = interfaceInteraction
|
||||||
@ -5005,8 +5004,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.focusOnSearchAfterAppearance {
|
if let _ = self.focusOnSearchAfterAppearance {
|
||||||
self.focusOnSearchAfterAppearance = false
|
self.focusOnSearchAfterAppearance = nil
|
||||||
if let searchNode = self.navigationBar?.contentNode as? ChatSearchNavigationContentNode {
|
if let searchNode = self.navigationBar?.contentNode as? ChatSearchNavigationContentNode {
|
||||||
searchNode.activate()
|
searchNode.activate()
|
||||||
}
|
}
|
||||||
@ -9253,9 +9252,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func activateSearch() {
|
func activateSearch(domain: ChatSearchDomain = .everything, query: String = "") {
|
||||||
self.focusOnSearchAfterAppearance = true
|
self.focusOnSearchAfterAppearance = (domain, query)
|
||||||
self.interfaceInteraction?.beginMessageSearch(.everything, "")
|
self.interfaceInteraction?.beginMessageSearch(domain, query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,35 +128,6 @@ struct ChatSearchResultsState: Equatable {
|
|||||||
let totalCount: Int32
|
let totalCount: Int32
|
||||||
let completed: Bool
|
let completed: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChatSearchDomain: Equatable {
|
|
||||||
case everything
|
|
||||||
case members
|
|
||||||
case member(Peer)
|
|
||||||
|
|
||||||
static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool {
|
|
||||||
switch lhs {
|
|
||||||
case .everything:
|
|
||||||
if case .everything = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .members:
|
|
||||||
if case .members = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .member(lhsPeer):
|
|
||||||
if case let .member(rhsPeer) = rhs, lhsPeer.isEqual(rhsPeer) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ChatSearchDomainSuggestionContext: Equatable {
|
enum ChatSearchDomainSuggestionContext: Equatable {
|
||||||
case none
|
case none
|
||||||
|
@ -1011,9 +1011,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
if !deltaOffset.isZero {
|
if !deltaOffset.isZero {
|
||||||
audioRecordingCancelIndicator.layer.animatePosition(from: CGPoint(x: deltaOffset, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
|
audioRecordingCancelIndicator.layer.animatePosition(from: CGPoint(x: deltaOffset, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
|
||||||
}
|
}
|
||||||
} else if audioRecordingCancelIndicator.layer.animation(forKey: "slide_juggle") == nil {
|
} else if audioRecordingCancelIndicator.layer.animation(forKey: "slide_juggle") == nil, baseWidth > 320 {
|
||||||
let slideJuggleAnimation = CABasicAnimation(keyPath: "transform")
|
let slideJuggleAnimation = CABasicAnimation(keyPath: "transform")
|
||||||
slideJuggleAnimation.toValue = CATransform3DMakeTranslation(-6, 0, 0)
|
slideJuggleAnimation.toValue = CATransform3DMakeTranslation(6, 0, 0)
|
||||||
slideJuggleAnimation.duration = 1
|
slideJuggleAnimation.duration = 1
|
||||||
slideJuggleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
slideJuggleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||||
slideJuggleAnimation.autoreverses = true
|
slideJuggleAnimation.autoreverses = true
|
||||||
@ -1023,11 +1023,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
let audioRecordingTimeSize = audioRecordingTimeNode.measure(CGSize(width: 200.0, height: 100.0))
|
let audioRecordingTimeSize = audioRecordingTimeNode.measure(CGSize(width: 200.0, height: 100.0))
|
||||||
|
|
||||||
let cancelMinX = audioRecordingCancelIndicator.alpha > 0.5 ? audioRecordingCancelIndicator.frame.minX : width
|
|
||||||
|
|
||||||
audioRecordingInfoContainerNode.frame = CGRect(
|
audioRecordingInfoContainerNode.frame = CGRect(
|
||||||
origin: CGPoint(
|
origin: CGPoint(
|
||||||
x: min(leftInset, cancelMinX - audioRecordingTimeSize.width - 8.0 - 28.0),
|
x: min(leftInset, width - audioRecordingTimeSize.width - 8.0 - 28.0),
|
||||||
y: 0.0
|
y: 0.0
|
||||||
),
|
),
|
||||||
size: CGSize(width: baseWidth, height: panelHeight)
|
size: CGSize(width: baseWidth, height: panelHeight)
|
||||||
@ -1051,7 +1049,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.addSubnode(audioRecordingDotNode)
|
self.addSubnode(audioRecordingDotNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
animateDotAppearing = transition.isAnimated && !isLocked && !hideInfo
|
animateDotAppearing = transition.isAnimated && !hideInfo
|
||||||
|
|
||||||
audioRecordingDotNode.frame = CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40))
|
audioRecordingDotNode.frame = CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40))
|
||||||
if animateDotAppearing {
|
if animateDotAppearing {
|
||||||
@ -1074,9 +1072,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hideInfo {
|
if hideInfo {
|
||||||
audioRecordingDotNode.layer.animateAlpha(from: audioRecordingDotNode.alpha, to: 0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
audioRecordingDotNode.layer.removeAllAnimations()
|
||||||
audioRecordingTimeNode.layer.animateAlpha(from: audioRecordingTimeNode.alpha, to: 0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1), to: 0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
||||||
audioRecordingCancelIndicator.layer.animateAlpha(from: audioRecordingCancelIndicator.alpha, to: 0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
audioRecordingTimeNode.layer.animateAlpha(from: CGFloat(audioRecordingTimeNode.layer.presentation()?.opacity ?? 1), to: 0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
||||||
|
audioRecordingCancelIndicator.layer.animateAlpha(from: CGFloat(audioRecordingCancelIndicator.layer.presentation()?.opacity ?? 1), to: 0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.actionButtons.micButton.audioRecorder = nil
|
self.actionButtons.micButton.audioRecorder = nil
|
||||||
@ -1091,8 +1090,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
if let audioRecordingInfoContainerNode = self.audioRecordingInfoContainerNode {
|
if let audioRecordingInfoContainerNode = self.audioRecordingInfoContainerNode {
|
||||||
self.audioRecordingInfoContainerNode = nil
|
self.audioRecordingInfoContainerNode = nil
|
||||||
//transition.updateTransformScale(node: audioRecordingInfoContainerNode, scale: 0)
|
|
||||||
//transition.updatePosition(node: audioRecordingInfoContainerNode, position: CGPoint(x: audioRecordingInfoContainerNode.position.x - 10, y: audioRecordingInfoContainerNode.position.y))
|
|
||||||
transition.updateAlpha(node: audioRecordingInfoContainerNode, alpha: 0) { [weak audioRecordingInfoContainerNode] _ in
|
transition.updateAlpha(node: audioRecordingInfoContainerNode, alpha: 0) { [weak audioRecordingInfoContainerNode] _ in
|
||||||
audioRecordingInfoContainerNode?.removeFromSupernode()
|
audioRecordingInfoContainerNode?.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -1104,7 +1101,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
self?.audioRecordingDotNode = nil
|
self?.audioRecordingDotNode = nil
|
||||||
|
|
||||||
audioRecordingDotNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, delay: 0, removeOnCompletion: false)
|
audioRecordingDotNode.layer.animateScale(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1), to: 0.3, duration: 0.15, delay: 0, removeOnCompletion: false)
|
||||||
audioRecordingDotNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, delay: 0, removeOnCompletion: false) { [weak audioRecordingDotNode] _ in
|
audioRecordingDotNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, delay: 0, removeOnCompletion: false) { [weak audioRecordingDotNode] _ in
|
||||||
audioRecordingDotNode?.removeFromSupernode()
|
audioRecordingDotNode?.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ final class InstantVideoControllerRecordingStatus {
|
|||||||
final class InstantVideoController: LegacyController, StandalonePresentableController {
|
final class InstantVideoController: LegacyController, StandalonePresentableController {
|
||||||
private var captureController: TGVideoMessageCaptureController?
|
private var captureController: TGVideoMessageCaptureController?
|
||||||
|
|
||||||
var onDismiss: (() -> Void)?
|
var onDismiss: ((Bool) -> Void)?
|
||||||
var onStop: (() -> Void)?
|
var onStop: (() -> Void)?
|
||||||
|
|
||||||
private let micLevelValue = ValuePromise<Float>(0.0)
|
private let micLevelValue = ValuePromise<Float>(0.0)
|
||||||
@ -59,8 +59,8 @@ final class InstantVideoController: LegacyController, StandalonePresentableContr
|
|||||||
captureController.onDuration = { [weak self] duration in
|
captureController.onDuration = { [weak self] duration in
|
||||||
self?.durationValue.set(duration)
|
self?.durationValue.set(duration)
|
||||||
}
|
}
|
||||||
captureController.onDismiss = { [weak self] _ in
|
captureController.onDismiss = { [weak self] _, isCancelled in
|
||||||
self?.onDismiss?()
|
self?.onDismiss?(isCancelled)
|
||||||
}
|
}
|
||||||
captureController.onStop = { [weak self] in
|
captureController.onStop = { [weak self] in
|
||||||
self?.onStop?()
|
self?.onStop?()
|
||||||
|
@ -34,8 +34,8 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
controller.scrollToEndOfHistory()
|
controller.scrollToEndOfHistory()
|
||||||
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
|
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
|
||||||
params.completion(controller)
|
params.completion(controller)
|
||||||
} else if params.activateMessageSearch {
|
} else if let search = params.activateMessageSearch {
|
||||||
controller.activateSearch()
|
controller.activateSearch(domain: search.0, query: search.1)
|
||||||
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
|
let _ = params.navigationController.popToViewController(controller, animated: params.animated)
|
||||||
params.completion(controller)
|
params.completion(controller)
|
||||||
} else {
|
} else {
|
||||||
@ -70,8 +70,8 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData)
|
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData)
|
||||||
}
|
}
|
||||||
controller.purposefulAction = params.purposefulAction
|
controller.purposefulAction = params.purposefulAction
|
||||||
if params.activateMessageSearch {
|
if let search = params.activateMessageSearch {
|
||||||
controller.activateSearch()
|
controller.activateSearch(domain: search.0, query: search.1)
|
||||||
}
|
}
|
||||||
let resolvedKeepStack: Bool
|
let resolvedKeepStack: Bool
|
||||||
switch params.keepStack {
|
switch params.keepStack {
|
||||||
|
@ -2410,7 +2410,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
|
|
||||||
private func openChatWithMessageSearch() {
|
private func openChatWithMessageSearch() {
|
||||||
if let navigationController = (self.controller?.navigationController as? NavigationController) {
|
if let navigationController = (self.controller?.navigationController as? NavigationController) {
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), activateMessageSearch: true))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), activateMessageSearch: (.everything, "")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,7 +597,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let resolvedText: CallStatusText
|
let resolvedText: CallStatusText
|
||||||
if let state = state {
|
if let state = state {
|
||||||
switch state {
|
switch state.state {
|
||||||
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
||||||
resolvedText = .inProgress(nil)
|
resolvedText = .inProgress(nil)
|
||||||
case .terminated:
|
case .terminated:
|
||||||
@ -1014,6 +1014,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? {
|
||||||
|
let controller = channelAdminController(context: context, peerId: peerId, adminId: adminId, initialParticipant: initialParticipant, updated: { _ in }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
public func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) {
|
public func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) {
|
||||||
openExternalUrlImpl(context: context, urlContext: urlContext, url: url, forceExternal: forceExternal, presentationData: presentationData, navigationController: navigationController, dismissInput: dismissInput)
|
openExternalUrlImpl(context: context, urlContext: urlContext, url: url, forceExternal: forceExternal, presentationData: presentationData, navigationController: navigationController, dismissInput: dismissInput)
|
||||||
}
|
}
|
||||||
|
@ -496,7 +496,7 @@ public final class SharedNotificationManager {
|
|||||||
if isIntegratedWithCallKit {
|
if isIntegratedWithCallKit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if case .ringing = state {
|
if case .ringing = state.state {
|
||||||
return (peer, internalId)
|
return (peer, internalId)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -93,11 +93,20 @@ private let setupLogs: Bool = {
|
|||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public enum OngoingCallContextState {
|
public struct OngoingCallContextState: Equatable {
|
||||||
case initializing
|
public enum State {
|
||||||
case connected
|
case initializing
|
||||||
case reconnecting
|
case connected
|
||||||
case failed
|
case reconnecting
|
||||||
|
case failed
|
||||||
|
}
|
||||||
|
public enum VideoState: Equatable {
|
||||||
|
case notAvailable
|
||||||
|
case available(Bool)
|
||||||
|
case active
|
||||||
|
}
|
||||||
|
public let state: State
|
||||||
|
public let videoState: VideoState
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
|
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
|
||||||
@ -226,6 +235,8 @@ private func ongoingDataSavingForTypeWebrtc(_ type: VoiceCallDataSaving) -> Ongo
|
|||||||
private protocol OngoingCallThreadLocalContextProtocol: class {
|
private protocol OngoingCallThreadLocalContextProtocol: class {
|
||||||
func nativeSetNetworkType(_ type: NetworkType)
|
func nativeSetNetworkType(_ type: NetworkType)
|
||||||
func nativeSetIsMuted(_ value: Bool)
|
func nativeSetIsMuted(_ value: Bool)
|
||||||
|
func nativeSetVideoEnabled(_ value: Bool)
|
||||||
|
func nativeSwitchVideoCamera()
|
||||||
func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void)
|
func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void)
|
||||||
func nativeDebugInfo() -> String
|
func nativeDebugInfo() -> String
|
||||||
func nativeVersion() -> String
|
func nativeVersion() -> String
|
||||||
@ -253,6 +264,12 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
|
|||||||
self.setIsMuted(value)
|
self.setIsMuted(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nativeSetVideoEnabled(_ value: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func nativeSwitchVideoCamera() {
|
||||||
|
}
|
||||||
|
|
||||||
func nativeDebugInfo() -> String {
|
func nativeDebugInfo() -> String {
|
||||||
return self.debugInfo() ?? ""
|
return self.debugInfo() ?? ""
|
||||||
}
|
}
|
||||||
@ -279,6 +296,14 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
|
|||||||
self.setIsMuted(value)
|
self.setIsMuted(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nativeSetVideoEnabled(_ value: Bool) {
|
||||||
|
self.setVideoEnabled(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nativeSwitchVideoCamera() {
|
||||||
|
self.switchVideoCamera()
|
||||||
|
}
|
||||||
|
|
||||||
func nativeDebugInfo() -> String {
|
func nativeDebugInfo() -> String {
|
||||||
return self.debugInfo() ?? ""
|
return self.debugInfo() ?? ""
|
||||||
}
|
}
|
||||||
@ -318,7 +343,7 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
private extension OngoingCallContextState {
|
private extension OngoingCallContextState.State {
|
||||||
init(_ state: OngoingCallState) {
|
init(_ state: OngoingCallState) {
|
||||||
switch state {
|
switch state {
|
||||||
case .initializing:
|
case .initializing:
|
||||||
@ -335,7 +360,7 @@ private extension OngoingCallContextState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension OngoingCallContextState {
|
private extension OngoingCallContextState.State {
|
||||||
init(_ state: OngoingCallStateWebrtc) {
|
init(_ state: OngoingCallStateWebrtc) {
|
||||||
switch state {
|
switch state {
|
||||||
case .initializing:
|
case .initializing:
|
||||||
@ -471,8 +496,25 @@ public final class OngoingCallContext {
|
|||||||
})
|
})
|
||||||
|
|
||||||
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
||||||
context.stateChanged = { state in
|
context.stateChanged = { state, videoState in
|
||||||
self?.contextState.set(.single(OngoingCallContextState(state)))
|
queue.async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let mappedState = OngoingCallContextState.State(state)
|
||||||
|
let mappedVideoState: OngoingCallContextState.VideoState
|
||||||
|
switch videoState {
|
||||||
|
case .inactive:
|
||||||
|
mappedVideoState = .available(true)
|
||||||
|
case .active:
|
||||||
|
mappedVideoState = .active
|
||||||
|
case .invited, .requesting:
|
||||||
|
mappedVideoState = .available(false)
|
||||||
|
@unknown default:
|
||||||
|
mappedVideoState = .available(false)
|
||||||
|
}
|
||||||
|
strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
context.signalBarsChanged = { signalBars in
|
context.signalBarsChanged = { signalBars in
|
||||||
self?.receptionPromise.set(.single(signalBars))
|
self?.receptionPromise.set(.single(signalBars))
|
||||||
@ -498,7 +540,7 @@ public final class OngoingCallContext {
|
|||||||
|
|
||||||
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
||||||
context.stateChanged = { state in
|
context.stateChanged = { state in
|
||||||
self?.contextState.set(.single(OngoingCallContextState(state)))
|
self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable)))
|
||||||
}
|
}
|
||||||
context.signalBarsChanged = { signalBars in
|
context.signalBarsChanged = { signalBars in
|
||||||
self?.receptionPromise.set(.single(signalBars))
|
self?.receptionPromise.set(.single(signalBars))
|
||||||
@ -588,6 +630,18 @@ public final class OngoingCallContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setEnableVideo(_ value: Bool) {
|
||||||
|
self.withContext { context in
|
||||||
|
context.nativeSetVideoEnabled(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func switchVideoCamera() {
|
||||||
|
self.withContext { context in
|
||||||
|
context.nativeSwitchVideoCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func debugInfo() -> Signal<(String, String), NoError> {
|
public func debugInfo() -> Signal<(String, String), NoError> {
|
||||||
let poll = Signal<(String, String), NoError> { subscriber in
|
let poll = Signal<(String, String), NoError> { subscriber in
|
||||||
self.withContext { context in
|
self.withContext { context in
|
||||||
|
@ -29,6 +29,7 @@ objc_library(
|
|||||||
"-Ithird-party/webrtc/webrtc-ios/src/sdk/objc/components/renderer/metal",
|
"-Ithird-party/webrtc/webrtc-ios/src/sdk/objc/components/renderer/metal",
|
||||||
"-Ithird-party/webrtc/webrtc-ios/src/sdk/objc/components/video_codec",
|
"-Ithird-party/webrtc/webrtc-ios/src/sdk/objc/components/video_codec",
|
||||||
"-Ithird-party/webrtc/webrtc-ios/src/third_party/libyuv/include",
|
"-Ithird-party/webrtc/webrtc-ios/src/third_party/libyuv/include",
|
||||||
|
"-Ithird-party/webrtc/webrtc-ios/src/sdk/objc/api/video_codec",
|
||||||
"-DWEBRTC_IOS",
|
"-DWEBRTC_IOS",
|
||||||
"-DWEBRTC_MAC",
|
"-DWEBRTC_MAC",
|
||||||
"-DWEBRTC_POSIX",
|
"-DWEBRTC_POSIX",
|
||||||
|
@ -18,8 +18,9 @@ public:
|
|||||||
void configurePlatformAudio();
|
void configurePlatformAudio();
|
||||||
std::unique_ptr<webrtc::VideoEncoderFactory> makeVideoEncoderFactory();
|
std::unique_ptr<webrtc::VideoEncoderFactory> makeVideoEncoderFactory();
|
||||||
std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory();
|
std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory();
|
||||||
|
bool supportsH265Encoding();
|
||||||
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Thread *signalingThread, rtc::Thread *workerThread);
|
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Thread *signalingThread, rtc::Thread *workerThread);
|
||||||
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source);
|
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera);
|
||||||
|
|
||||||
#ifdef TGVOIP_NAMESPACE
|
#ifdef TGVOIP_NAMESPACE
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
#import "VideoCameraCapturer.h"
|
#import "VideoCameraCapturer.h"
|
||||||
|
|
||||||
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
|
||||||
@interface VideoCapturerInterfaceImplReference : NSObject {
|
@interface VideoCapturerInterfaceImplReference : NSObject {
|
||||||
VideoCameraCapturer *_videoCapturer;
|
VideoCameraCapturer *_videoCapturer;
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@
|
|||||||
|
|
||||||
@implementation VideoCapturerInterfaceImplReference
|
@implementation VideoCapturerInterfaceImplReference
|
||||||
|
|
||||||
- (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source {
|
- (instancetype)initWithSource:(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface>)source useFrontCamera:(bool)useFrontCamera {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
assert([NSThread isMainThread]);
|
assert([NSThread isMainThread]);
|
||||||
@ -46,18 +48,27 @@
|
|||||||
_videoCapturer = [[VideoCameraCapturer alloc] initWithSource:source];
|
_videoCapturer = [[VideoCameraCapturer alloc] initWithSource:source];
|
||||||
|
|
||||||
AVCaptureDevice *frontCamera = nil;
|
AVCaptureDevice *frontCamera = nil;
|
||||||
|
AVCaptureDevice *backCamera = nil;
|
||||||
for (AVCaptureDevice *device in [VideoCameraCapturer captureDevices]) {
|
for (AVCaptureDevice *device in [VideoCameraCapturer captureDevices]) {
|
||||||
if (device.position == AVCaptureDevicePositionFront) {
|
if (device.position == AVCaptureDevicePositionFront) {
|
||||||
frontCamera = device;
|
frontCamera = device;
|
||||||
break;
|
} else if (device.position == AVCaptureDevicePositionBack) {
|
||||||
|
backCamera = device;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frontCamera == nil) {
|
AVCaptureDevice *selectedCamera = nil;
|
||||||
|
if (useFrontCamera && frontCamera != nil) {
|
||||||
|
selectedCamera = frontCamera;
|
||||||
|
} else {
|
||||||
|
selectedCamera = backCamera;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCamera == nil) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray<AVCaptureDeviceFormat *> *sortedFormats = [[VideoCameraCapturer supportedFormatsForDevice:frontCamera] sortedArrayUsingComparator:^NSComparisonResult(AVCaptureDeviceFormat* lhs, AVCaptureDeviceFormat *rhs) {
|
NSArray<AVCaptureDeviceFormat *> *sortedFormats = [[VideoCameraCapturer supportedFormatsForDevice:selectedCamera] sortedArrayUsingComparator:^NSComparisonResult(AVCaptureDeviceFormat* lhs, AVCaptureDeviceFormat *rhs) {
|
||||||
int32_t width1 = CMVideoFormatDescriptionGetDimensions(lhs.formatDescription).width;
|
int32_t width1 = CMVideoFormatDescriptionGetDimensions(lhs.formatDescription).width;
|
||||||
int32_t width2 = CMVideoFormatDescriptionGetDimensions(rhs.formatDescription).width;
|
int32_t width2 = CMVideoFormatDescriptionGetDimensions(rhs.formatDescription).width;
|
||||||
return width1 < width2 ? NSOrderedAscending : NSOrderedDescending;
|
return width1 < width2 ? NSOrderedAscending : NSOrderedDescending;
|
||||||
@ -90,7 +101,7 @@
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
[_videoCapturer startCaptureWithDevice:frontCamera format:bestFormat fps:30];
|
[_videoCapturer startCaptureWithDevice:selectedCamera format:bestFormat fps:30];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -119,12 +130,12 @@ namespace TGVOIP_NAMESPACE {
|
|||||||
|
|
||||||
class VideoCapturerInterfaceImpl: public VideoCapturerInterface {
|
class VideoCapturerInterfaceImpl: public VideoCapturerInterface {
|
||||||
public:
|
public:
|
||||||
VideoCapturerInterfaceImpl(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source) :
|
VideoCapturerInterfaceImpl(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera) :
|
||||||
_source(source) {
|
_source(source) {
|
||||||
_implReference = [[VideoCapturerInterfaceImplHolder alloc] init];
|
_implReference = [[VideoCapturerInterfaceImplHolder alloc] init];
|
||||||
VideoCapturerInterfaceImplHolder *implReference = _implReference;
|
VideoCapturerInterfaceImplHolder *implReference = _implReference;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
VideoCapturerInterfaceImplReference *value = [[VideoCapturerInterfaceImplReference alloc] initWithSource:source];
|
VideoCapturerInterfaceImplReference *value = [[VideoCapturerInterfaceImplReference alloc] initWithSource:source useFrontCamera:useFrontCamera];
|
||||||
if (value != nil) {
|
if (value != nil) {
|
||||||
implReference.reference = (void *)CFBridgingRetain(value);
|
implReference.reference = (void *)CFBridgingRetain(value);
|
||||||
}
|
}
|
||||||
@ -161,13 +172,21 @@ std::unique_ptr<webrtc::VideoDecoderFactory> makeVideoDecoderFactory() {
|
|||||||
return webrtc::ObjCToNativeVideoDecoderFactory([[TGRTCDefaultVideoDecoderFactory alloc] init]);
|
return webrtc::ObjCToNativeVideoDecoderFactory([[TGRTCDefaultVideoDecoderFactory alloc] init]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool supportsH265Encoding() {
|
||||||
|
if (@available(iOS 11.0, *)) {
|
||||||
|
return [[AVAssetExportSession allExportPresets] containsObject:AVAssetExportPresetHEVCHighestQuality];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Thread *signalingThread, rtc::Thread *workerThread) {
|
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> makeVideoSource(rtc::Thread *signalingThread, rtc::Thread *workerThread) {
|
||||||
rtc::scoped_refptr<webrtc::ObjCVideoTrackSource> objCVideoTrackSource(new rtc::RefCountedObject<webrtc::ObjCVideoTrackSource>());
|
rtc::scoped_refptr<webrtc::ObjCVideoTrackSource> objCVideoTrackSource(new rtc::RefCountedObject<webrtc::ObjCVideoTrackSource>());
|
||||||
return webrtc::VideoTrackSourceProxy::Create(signalingThread, workerThread, objCVideoTrackSource);
|
return webrtc::VideoTrackSourceProxy::Create(signalingThread, workerThread, objCVideoTrackSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source) {
|
std::unique_ptr<VideoCapturerInterface> makeVideoCapturer(rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source, bool useFrontCamera) {
|
||||||
return std::make_unique<VideoCapturerInterfaceImpl>(source);
|
return std::make_unique<VideoCapturerInterfaceImpl>(source, useFrontCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef TGVOIP_NAMESPACE
|
#ifdef TGVOIP_NAMESPACE
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "Manager.h"
|
#include "Manager.h"
|
||||||
|
|
||||||
|
#include "rtc_base/byte_buffer.h"
|
||||||
|
|
||||||
#ifdef TGVOIP_NAMESPACE
|
#ifdef TGVOIP_NAMESPACE
|
||||||
namespace TGVOIP_NAMESPACE {
|
namespace TGVOIP_NAMESPACE {
|
||||||
#endif
|
#endif
|
||||||
@ -35,13 +37,16 @@ Manager::Manager(
|
|||||||
TgVoipEncryptionKey encryptionKey,
|
TgVoipEncryptionKey encryptionKey,
|
||||||
bool enableP2P,
|
bool enableP2P,
|
||||||
std::function<void (const TgVoipState &)> stateUpdated,
|
std::function<void (const TgVoipState &)> stateUpdated,
|
||||||
|
std::function<void (bool)> videoStateUpdated,
|
||||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||||
) :
|
) :
|
||||||
_thread(thread),
|
_thread(thread),
|
||||||
_encryptionKey(encryptionKey),
|
_encryptionKey(encryptionKey),
|
||||||
_enableP2P(enableP2P),
|
_enableP2P(enableP2P),
|
||||||
_stateUpdated(stateUpdated),
|
_stateUpdated(stateUpdated),
|
||||||
_signalingDataEmitted(signalingDataEmitted) {
|
_videoStateUpdated(videoStateUpdated),
|
||||||
|
_signalingDataEmitted(signalingDataEmitted),
|
||||||
|
_isVideoRequested(false) {
|
||||||
assert(_thread->IsCurrent());
|
assert(_thread->IsCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +92,14 @@ void Manager::start() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
[signalingDataEmitted](const std::vector<uint8_t> &data) {
|
[signalingDataEmitted](const std::vector<uint8_t> &data) {
|
||||||
signalingDataEmitted(data);
|
rtc::CopyOnWriteBuffer buffer;
|
||||||
|
uint8_t mode = 3;
|
||||||
|
buffer.AppendData(&mode, 1);
|
||||||
|
buffer.AppendData(data.data(), data.size());
|
||||||
|
std::vector<uint8_t> augmentedData;
|
||||||
|
augmentedData.resize(buffer.size());
|
||||||
|
memcpy(augmentedData.data(), buffer.data(), buffer.size());
|
||||||
|
signalingDataEmitted(augmentedData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
@ -112,8 +124,59 @@ void Manager::start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Manager::receiveSignalingData(const std::vector<uint8_t> &data) {
|
void Manager::receiveSignalingData(const std::vector<uint8_t> &data) {
|
||||||
_networkManager->perform([data](NetworkManager *networkManager) {
|
rtc::CopyOnWriteBuffer buffer;
|
||||||
networkManager->receiveSignalingData(data);
|
buffer.AppendData(data.data(), data.size());
|
||||||
|
|
||||||
|
if (buffer.size() < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc::ByteBufferReader reader((const char *)buffer.data(), buffer.size());
|
||||||
|
uint8_t mode = 0;
|
||||||
|
if (!reader.ReadUInt8(&mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == 1) {
|
||||||
|
_mediaManager->perform([](MediaManager *mediaManager) {
|
||||||
|
mediaManager->setSendVideo(true);
|
||||||
|
});
|
||||||
|
_videoStateUpdated(true);
|
||||||
|
} else if (mode == 2) {
|
||||||
|
} else if (mode == 3) {
|
||||||
|
auto candidatesData = buffer.Slice(1, buffer.size() - 1);
|
||||||
|
_networkManager->perform([candidatesData](NetworkManager *networkManager) {
|
||||||
|
networkManager->receiveSignalingData(candidatesData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Manager::setSendVideo(bool sendVideo) {
|
||||||
|
if (sendVideo) {
|
||||||
|
if (!_isVideoRequested) {
|
||||||
|
_isVideoRequested = true;
|
||||||
|
|
||||||
|
rtc::CopyOnWriteBuffer buffer;
|
||||||
|
uint8_t mode = 1;
|
||||||
|
buffer.AppendData(&mode, 1);
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.resize(buffer.size());
|
||||||
|
memcpy(data.data(), buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
_signalingDataEmitted(data);
|
||||||
|
|
||||||
|
_mediaManager->perform([](MediaManager *mediaManager) {
|
||||||
|
mediaManager->setSendVideo(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
_videoStateUpdated(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Manager::switchVideoCamera() {
|
||||||
|
_mediaManager->perform([](MediaManager *mediaManager) {
|
||||||
|
mediaManager->switchVideoCamera();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,12 +17,15 @@ public:
|
|||||||
TgVoipEncryptionKey encryptionKey,
|
TgVoipEncryptionKey encryptionKey,
|
||||||
bool enableP2P,
|
bool enableP2P,
|
||||||
std::function<void (const TgVoipState &)> stateUpdated,
|
std::function<void (const TgVoipState &)> stateUpdated,
|
||||||
|
std::function<void (bool)> videoStateUpdated,
|
||||||
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
std::function<void (const std::vector<uint8_t> &)> signalingDataEmitted
|
||||||
);
|
);
|
||||||
~Manager();
|
~Manager();
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void receiveSignalingData(const std::vector<uint8_t> &data);
|
void receiveSignalingData(const std::vector<uint8_t> &data);
|
||||||
|
void setSendVideo(bool sendVideo);
|
||||||
|
void switchVideoCamera();
|
||||||
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
||||||
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
||||||
|
|
||||||
@ -31,9 +34,11 @@ private:
|
|||||||
TgVoipEncryptionKey _encryptionKey;
|
TgVoipEncryptionKey _encryptionKey;
|
||||||
bool _enableP2P;
|
bool _enableP2P;
|
||||||
std::function<void (const TgVoipState &)> _stateUpdated;
|
std::function<void (const TgVoipState &)> _stateUpdated;
|
||||||
|
std::function<void (bool)> _videoStateUpdated;
|
||||||
std::function<void (const std::vector<uint8_t> &)> _signalingDataEmitted;
|
std::function<void (const std::vector<uint8_t> &)> _signalingDataEmitted;
|
||||||
std::unique_ptr<ThreadLocalObject<NetworkManager>> _networkManager;
|
std::unique_ptr<ThreadLocalObject<NetworkManager>> _networkManager;
|
||||||
std::unique_ptr<ThreadLocalObject<MediaManager>> _mediaManager;
|
std::unique_ptr<ThreadLocalObject<MediaManager>> _mediaManager;
|
||||||
|
bool _isVideoRequested;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
#include "api/video/video_bitrate_allocation.h"
|
#include "api/video/video_bitrate_allocation.h"
|
||||||
#include "call/call.h"
|
#include "call/call.h"
|
||||||
|
|
||||||
|
#include "api/video_codecs/builtin_video_encoder_factory.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
|
|
||||||
#include "CodecsApple.h"
|
#include "CodecsApple.h"
|
||||||
@ -108,27 +110,50 @@ static std::vector<cricket::VideoCodec> AssignPayloadTypesAndDefaultCodecs(std::
|
|||||||
return output_codecs;
|
return output_codecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sendCodecPriority(const cricket::VideoCodec &codec) {
|
||||||
|
int priotity = 0;
|
||||||
|
if (codec.name == cricket::kAv1CodecName) {
|
||||||
|
return priotity;
|
||||||
|
}
|
||||||
|
priotity++;
|
||||||
|
if (codec.name == cricket::kH265CodecName) {
|
||||||
|
if (supportsH265Encoding()) {
|
||||||
|
return priotity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
priotity++;
|
||||||
|
if (codec.name == cricket::kH264CodecName) {
|
||||||
|
return priotity;
|
||||||
|
}
|
||||||
|
priotity++;
|
||||||
|
if (codec.name == cricket::kVp9CodecName) {
|
||||||
|
return priotity;
|
||||||
|
}
|
||||||
|
priotity++;
|
||||||
|
if (codec.name == cricket::kVp8CodecName) {
|
||||||
|
return priotity;
|
||||||
|
}
|
||||||
|
priotity++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static absl::optional<cricket::VideoCodec> selectVideoCodec(std::vector<cricket::VideoCodec> &codecs) {
|
static absl::optional<cricket::VideoCodec> selectVideoCodec(std::vector<cricket::VideoCodec> &codecs) {
|
||||||
bool useVP9 = false;
|
std::vector<cricket::VideoCodec> sortedCodecs;
|
||||||
bool useH265 = true;
|
|
||||||
|
|
||||||
for (auto &codec : codecs) {
|
for (auto &codec : codecs) {
|
||||||
if (useVP9) {
|
if (sendCodecPriority(codec) != -1) {
|
||||||
if (codec.name == cricket::kVp9CodecName) {
|
sortedCodecs.push_back(codec);
|
||||||
return absl::optional<cricket::VideoCodec>(codec);
|
|
||||||
}
|
|
||||||
} else if (useH265) {
|
|
||||||
if (codec.name == cricket::kH265CodecName) {
|
|
||||||
return absl::optional<cricket::VideoCodec>(codec);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (codec.name == cricket::kH264CodecName) {
|
|
||||||
return absl::optional<cricket::VideoCodec>(codec);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return absl::optional<cricket::VideoCodec>();
|
std::sort(sortedCodecs.begin(), sortedCodecs.end(), [](const cricket::VideoCodec &lhs, const cricket::VideoCodec &rhs) {
|
||||||
|
return sendCodecPriority(lhs) < sendCodecPriority(rhs);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sortedCodecs.size() != 0) {
|
||||||
|
return sortedCodecs[0];
|
||||||
|
} else {
|
||||||
|
return absl::nullopt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static rtc::Thread *makeWorkerThread() {
|
static rtc::Thread *makeWorkerThread() {
|
||||||
@ -162,6 +187,14 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
|||||||
_ssrcVideo.fecIncoming = isOutgoing ? ssrcVideoFecIncoming : ssrcVideoFecOutgoing;
|
_ssrcVideo.fecIncoming = isOutgoing ? ssrcVideoFecIncoming : ssrcVideoFecOutgoing;
|
||||||
_ssrcVideo.fecOutgoing = (!isOutgoing) ? ssrcVideoFecIncoming : ssrcVideoFecOutgoing;
|
_ssrcVideo.fecOutgoing = (!isOutgoing) ? ssrcVideoFecIncoming : ssrcVideoFecOutgoing;
|
||||||
|
|
||||||
|
_isConnected = false;
|
||||||
|
|
||||||
|
auto videoEncoderFactory = makeVideoEncoderFactory();
|
||||||
|
_videoCodecs = AssignPayloadTypesAndDefaultCodecs(videoEncoderFactory->GetSupportedFormats());
|
||||||
|
|
||||||
|
_isSendingVideo = false;
|
||||||
|
_useFrontCamera = true;
|
||||||
|
|
||||||
_audioNetworkInterface = std::unique_ptr<MediaManager::NetworkInterfaceImpl>(new MediaManager::NetworkInterfaceImpl(this, false));
|
_audioNetworkInterface = std::unique_ptr<MediaManager::NetworkInterfaceImpl>(new MediaManager::NetworkInterfaceImpl(this, false));
|
||||||
_videoNetworkInterface = std::unique_ptr<MediaManager::NetworkInterfaceImpl>(new MediaManager::NetworkInterfaceImpl(this, true));
|
_videoNetworkInterface = std::unique_ptr<MediaManager::NetworkInterfaceImpl>(new MediaManager::NetworkInterfaceImpl(this, true));
|
||||||
|
|
||||||
@ -182,9 +215,6 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
|||||||
mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus>();
|
mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus>();
|
||||||
mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus>();
|
mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus>();
|
||||||
|
|
||||||
auto videoEncoderFactory = makeVideoEncoderFactory();
|
|
||||||
std::vector<cricket::VideoCodec> videoCodecs = AssignPayloadTypesAndDefaultCodecs(videoEncoderFactory->GetSupportedFormats());
|
|
||||||
|
|
||||||
mediaDeps.video_encoder_factory = makeVideoEncoderFactory();
|
mediaDeps.video_encoder_factory = makeVideoEncoderFactory();
|
||||||
mediaDeps.video_decoder_factory = makeVideoDecoderFactory();
|
mediaDeps.video_decoder_factory = makeVideoDecoderFactory();
|
||||||
|
|
||||||
@ -211,7 +241,6 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
|||||||
const uint8_t opusMaxBitrateKbps = 32;
|
const uint8_t opusMaxBitrateKbps = 32;
|
||||||
const uint8_t opusStartBitrateKbps = 6;
|
const uint8_t opusStartBitrateKbps = 6;
|
||||||
const uint8_t opusPTimeMs = 120;
|
const uint8_t opusPTimeMs = 120;
|
||||||
const int extensionSequenceOne = 1;
|
|
||||||
|
|
||||||
cricket::AudioCodec opusCodec(opusSdpPayload, opusSdpName, opusClockrate, opusSdpBitrate, opusSdpChannels);
|
cricket::AudioCodec opusCodec(opusSdpPayload, opusSdpName, opusClockrate, opusSdpBitrate, opusSdpChannels);
|
||||||
opusCodec.AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamTransportCc));
|
opusCodec.AddFeedbackParam(cricket::FeedbackParam(cricket::kRtcpFbParamTransportCc));
|
||||||
@ -223,7 +252,7 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
|||||||
|
|
||||||
cricket::AudioSendParameters audioSendPrameters;
|
cricket::AudioSendParameters audioSendPrameters;
|
||||||
audioSendPrameters.codecs.push_back(opusCodec);
|
audioSendPrameters.codecs.push_back(opusCodec);
|
||||||
audioSendPrameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, extensionSequenceOne);
|
audioSendPrameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, 1);
|
||||||
audioSendPrameters.options.echo_cancellation = false;
|
audioSendPrameters.options.echo_cancellation = false;
|
||||||
//audioSendPrameters.options.experimental_ns = false;
|
//audioSendPrameters.options.experimental_ns = false;
|
||||||
audioSendPrameters.options.noise_suppression = false;
|
audioSendPrameters.options.noise_suppression = false;
|
||||||
@ -238,7 +267,7 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
|||||||
|
|
||||||
cricket::AudioRecvParameters audioRecvParameters;
|
cricket::AudioRecvParameters audioRecvParameters;
|
||||||
audioRecvParameters.codecs.emplace_back(opusSdpPayload, opusSdpName, opusClockrate, opusSdpBitrate, opusSdpChannels);
|
audioRecvParameters.codecs.emplace_back(opusSdpPayload, opusSdpName, opusClockrate, opusSdpBitrate, opusSdpChannels);
|
||||||
audioRecvParameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, extensionSequenceOne);
|
audioRecvParameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, 1);
|
||||||
audioRecvParameters.rtcp.reduced_size = true;
|
audioRecvParameters.rtcp.reduced_size = true;
|
||||||
audioRecvParameters.rtcp.remote_estimate = true;
|
audioRecvParameters.rtcp.remote_estimate = true;
|
||||||
|
|
||||||
@ -246,75 +275,9 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) {
|
|||||||
_audioChannel->AddRecvStream(cricket::StreamParams::CreateLegacy(_ssrcAudio.incoming));
|
_audioChannel->AddRecvStream(cricket::StreamParams::CreateLegacy(_ssrcAudio.incoming));
|
||||||
_audioChannel->SetPlayout(true);
|
_audioChannel->SetPlayout(true);
|
||||||
|
|
||||||
cricket::StreamParams videoSendStreamParams;
|
_videoChannel->SetInterface(_videoNetworkInterface.get(), webrtc::MediaTransportConfig());
|
||||||
cricket::SsrcGroup videoSendSsrcGroup(cricket::kFecFrSsrcGroupSemantics, {_ssrcVideo.outgoing, _ssrcVideo.fecOutgoing});
|
|
||||||
videoSendStreamParams.ssrcs = {_ssrcVideo.outgoing};
|
|
||||||
videoSendStreamParams.ssrc_groups.push_back(videoSendSsrcGroup);
|
|
||||||
videoSendStreamParams.cname = "cname";
|
|
||||||
_videoChannel->AddSendStream(videoSendStreamParams);
|
|
||||||
|
|
||||||
auto videoCodec = selectVideoCodec(videoCodecs);
|
_nativeVideoSource = makeVideoSource(_thread, getWorkerThread());
|
||||||
if (videoCodec.has_value()) {
|
|
||||||
_nativeVideoSource = makeVideoSource(_thread, getWorkerThread());
|
|
||||||
|
|
||||||
auto codec = videoCodec.value();
|
|
||||||
|
|
||||||
codec.SetParam(cricket::kCodecParamMinBitrate, 64);
|
|
||||||
codec.SetParam(cricket::kCodecParamStartBitrate, 512);
|
|
||||||
codec.SetParam(cricket::kCodecParamMaxBitrate, 2500);
|
|
||||||
|
|
||||||
_videoCapturer = makeVideoCapturer(_nativeVideoSource);
|
|
||||||
|
|
||||||
cricket::VideoSendParameters videoSendParameters;
|
|
||||||
videoSendParameters.codecs.push_back(codec);
|
|
||||||
|
|
||||||
for (auto &c : videoCodecs) {
|
|
||||||
if (c.name == cricket::kFlexfecCodecName) {
|
|
||||||
videoSendParameters.codecs.push_back(c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
videoSendParameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, extensionSequenceOne);
|
|
||||||
//send_parameters.max_bandwidth_bps = 800000;
|
|
||||||
//send_parameters.rtcp.reduced_size = true;
|
|
||||||
//videoSendParameters.rtcp.remote_estimate = true;
|
|
||||||
_videoChannel->SetSendParameters(videoSendParameters);
|
|
||||||
|
|
||||||
_videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, _nativeVideoSource.get());
|
|
||||||
_videoChannel->SetVideoSend(_ssrcVideo.fecOutgoing, NULL, nullptr);
|
|
||||||
|
|
||||||
_videoChannel->SetInterface(_videoNetworkInterface.get(), webrtc::MediaTransportConfig());
|
|
||||||
|
|
||||||
cricket::VideoRecvParameters videoRecvParameters;
|
|
||||||
videoRecvParameters.codecs.emplace_back(codec);
|
|
||||||
|
|
||||||
for (auto &c : videoCodecs) {
|
|
||||||
if (c.name == cricket::kFlexfecCodecName) {
|
|
||||||
videoRecvParameters.codecs.push_back(c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
videoRecvParameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, extensionSequenceOne);
|
|
||||||
//recv_parameters.rtcp.reduced_size = true;
|
|
||||||
videoRecvParameters.rtcp.remote_estimate = true;
|
|
||||||
|
|
||||||
cricket::StreamParams videoRecvStreamParams;
|
|
||||||
cricket::SsrcGroup videoRecvSsrcGroup(cricket::kFecFrSsrcGroupSemantics, {_ssrcVideo.incoming, _ssrcVideo.fecIncoming});
|
|
||||||
videoRecvStreamParams.ssrcs = {_ssrcVideo.incoming};
|
|
||||||
videoRecvStreamParams.ssrc_groups.push_back(videoRecvSsrcGroup);
|
|
||||||
videoRecvStreamParams.cname = "cname";
|
|
||||||
|
|
||||||
_videoChannel->AddRecvStream(videoRecvStreamParams);
|
|
||||||
_videoChannel->SetRecvParameters(videoRecvParameters);
|
|
||||||
|
|
||||||
/*webrtc::FlexfecReceiveStream::Config config(_videoNetworkInterface.get());
|
|
||||||
config.payload_type = 118;
|
|
||||||
config.protected_media_ssrcs = {1324234};
|
|
||||||
webrtc::FlexfecReceiveStream* stream;
|
|
||||||
std::list<webrtc::FlexfecReceiveStream *> streams;*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaManager::~MediaManager() {
|
MediaManager::~MediaManager() {
|
||||||
@ -334,18 +297,16 @@ MediaManager::~MediaManager() {
|
|||||||
|
|
||||||
_audioChannel->SetInterface(nullptr, webrtc::MediaTransportConfig());
|
_audioChannel->SetInterface(nullptr, webrtc::MediaTransportConfig());
|
||||||
|
|
||||||
_videoChannel->RemoveRecvStream(_ssrcVideo.incoming);
|
setSendVideo(false);
|
||||||
_videoChannel->RemoveRecvStream(_ssrcVideo.fecIncoming);
|
|
||||||
_videoChannel->RemoveSendStream(_ssrcVideo.outgoing);
|
|
||||||
_videoChannel->RemoveSendStream(_ssrcVideo.fecOutgoing);
|
|
||||||
|
|
||||||
_videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, nullptr);
|
|
||||||
_videoChannel->SetVideoSend(_ssrcVideo.fecOutgoing, NULL, nullptr);
|
|
||||||
_videoChannel->SetInterface(nullptr, webrtc::MediaTransportConfig());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaManager::setIsConnected(bool isConnected) {
|
void MediaManager::setIsConnected(bool isConnected) {
|
||||||
if (isConnected) {
|
if (_isConnected == isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isConnected = isConnected;
|
||||||
|
|
||||||
|
if (_isConnected) {
|
||||||
_call->SignalChannelNetworkState(webrtc::MediaType::AUDIO, webrtc::kNetworkUp);
|
_call->SignalChannelNetworkState(webrtc::MediaType::AUDIO, webrtc::kNetworkUp);
|
||||||
_call->SignalChannelNetworkState(webrtc::MediaType::VIDEO, webrtc::kNetworkUp);
|
_call->SignalChannelNetworkState(webrtc::MediaType::VIDEO, webrtc::kNetworkUp);
|
||||||
} else {
|
} else {
|
||||||
@ -353,13 +314,13 @@ void MediaManager::setIsConnected(bool isConnected) {
|
|||||||
_call->SignalChannelNetworkState(webrtc::MediaType::VIDEO, webrtc::kNetworkDown);
|
_call->SignalChannelNetworkState(webrtc::MediaType::VIDEO, webrtc::kNetworkDown);
|
||||||
}
|
}
|
||||||
if (_audioChannel) {
|
if (_audioChannel) {
|
||||||
_audioChannel->OnReadyToSend(isConnected);
|
_audioChannel->OnReadyToSend(_isConnected);
|
||||||
_audioChannel->SetSend(isConnected);
|
_audioChannel->SetSend(_isConnected);
|
||||||
_audioChannel->SetAudioSend(_ssrcAudio.outgoing, isConnected, nullptr, &_audioSource);
|
_audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected, nullptr, &_audioSource);
|
||||||
}
|
}
|
||||||
if (_videoChannel) {
|
if (_isSendingVideo && _videoChannel) {
|
||||||
_videoChannel->OnReadyToSend(isConnected);
|
_videoChannel->OnReadyToSend(_isConnected);
|
||||||
_videoChannel->SetSend(isConnected);
|
_videoChannel->SetSend(_isConnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,14 +347,113 @@ void MediaManager::notifyPacketSent(const rtc::SentPacket &sentPacket) {
|
|||||||
_call->OnSentPacket(sentPacket);
|
_call->OnSentPacket(sentPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaManager::setSendVideo(bool sendVideo) {
|
||||||
|
if (_isSendingVideo == sendVideo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isSendingVideo = sendVideo;
|
||||||
|
|
||||||
|
if (_isSendingVideo) {
|
||||||
|
auto videoCodec = selectVideoCodec(_videoCodecs);
|
||||||
|
if (videoCodec.has_value()) {
|
||||||
|
auto codec = videoCodec.value();
|
||||||
|
|
||||||
|
codec.SetParam(cricket::kCodecParamMinBitrate, 64);
|
||||||
|
codec.SetParam(cricket::kCodecParamStartBitrate, 512);
|
||||||
|
codec.SetParam(cricket::kCodecParamMaxBitrate, 2500);
|
||||||
|
|
||||||
|
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera);
|
||||||
|
|
||||||
|
cricket::VideoSendParameters videoSendParameters;
|
||||||
|
videoSendParameters.codecs.push_back(codec);
|
||||||
|
|
||||||
|
for (auto &c : _videoCodecs) {
|
||||||
|
if (c.name == cricket::kFlexfecCodecName) {
|
||||||
|
videoSendParameters.codecs.push_back(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videoSendParameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, 1);
|
||||||
|
//send_parameters.max_bandwidth_bps = 800000;
|
||||||
|
//send_parameters.rtcp.reduced_size = true;
|
||||||
|
//videoSendParameters.rtcp.remote_estimate = true;
|
||||||
|
_videoChannel->SetSendParameters(videoSendParameters);
|
||||||
|
|
||||||
|
cricket::StreamParams videoSendStreamParams;
|
||||||
|
cricket::SsrcGroup videoSendSsrcGroup(cricket::kFecFrSsrcGroupSemantics, {_ssrcVideo.outgoing, _ssrcVideo.fecOutgoing});
|
||||||
|
videoSendStreamParams.ssrcs = {_ssrcVideo.outgoing};
|
||||||
|
videoSendStreamParams.ssrc_groups.push_back(videoSendSsrcGroup);
|
||||||
|
videoSendStreamParams.cname = "cname";
|
||||||
|
_videoChannel->AddSendStream(videoSendStreamParams);
|
||||||
|
|
||||||
|
_videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, _nativeVideoSource.get());
|
||||||
|
_videoChannel->SetVideoSend(_ssrcVideo.fecOutgoing, NULL, nullptr);
|
||||||
|
|
||||||
|
cricket::VideoRecvParameters videoRecvParameters;
|
||||||
|
|
||||||
|
for (auto &c : _videoCodecs) {
|
||||||
|
if (c.name == cricket::kFlexfecCodecName) {
|
||||||
|
videoRecvParameters.codecs.push_back(c);
|
||||||
|
} else if (c.name == cricket::kH264CodecName) {
|
||||||
|
videoRecvParameters.codecs.push_back(c);
|
||||||
|
} else if (c.name == cricket::kH265CodecName) {
|
||||||
|
videoRecvParameters.codecs.push_back(c);
|
||||||
|
} else if (c.name == cricket::kVp8CodecName) {
|
||||||
|
videoRecvParameters.codecs.push_back(c);
|
||||||
|
} else if (c.name == cricket::kVp9CodecName) {
|
||||||
|
videoRecvParameters.codecs.push_back(c);
|
||||||
|
} else if (c.name == cricket::kAv1CodecName) {
|
||||||
|
videoRecvParameters.codecs.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videoRecvParameters.extensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, 1);
|
||||||
|
//recv_parameters.rtcp.reduced_size = true;
|
||||||
|
videoRecvParameters.rtcp.remote_estimate = true;
|
||||||
|
|
||||||
|
cricket::StreamParams videoRecvStreamParams;
|
||||||
|
cricket::SsrcGroup videoRecvSsrcGroup(cricket::kFecFrSsrcGroupSemantics, {_ssrcVideo.incoming, _ssrcVideo.fecIncoming});
|
||||||
|
videoRecvStreamParams.ssrcs = {_ssrcVideo.incoming};
|
||||||
|
videoRecvStreamParams.ssrc_groups.push_back(videoRecvSsrcGroup);
|
||||||
|
videoRecvStreamParams.cname = "cname";
|
||||||
|
|
||||||
|
_videoChannel->AddRecvStream(videoRecvStreamParams);
|
||||||
|
_videoChannel->SetRecvParameters(videoRecvParameters);
|
||||||
|
|
||||||
|
if (_isSendingVideo && _videoChannel) {
|
||||||
|
_videoChannel->OnReadyToSend(_isConnected);
|
||||||
|
_videoChannel->SetSend(_isConnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, nullptr);
|
||||||
|
_videoChannel->SetVideoSend(_ssrcVideo.fecOutgoing, NULL, nullptr);
|
||||||
|
|
||||||
|
_videoCapturer.reset();
|
||||||
|
|
||||||
|
_videoChannel->RemoveRecvStream(_ssrcVideo.incoming);
|
||||||
|
_videoChannel->RemoveRecvStream(_ssrcVideo.fecIncoming);
|
||||||
|
_videoChannel->RemoveSendStream(_ssrcVideo.outgoing);
|
||||||
|
_videoChannel->RemoveSendStream(_ssrcVideo.fecOutgoing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaManager::switchVideoCamera() {
|
||||||
|
if (_isSendingVideo) {
|
||||||
|
_useFrontCamera = !_useFrontCamera;
|
||||||
|
_videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MediaManager::setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
void MediaManager::setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
||||||
_currentIncomingVideoSink = sink;
|
_currentIncomingVideoSink = sink;
|
||||||
_videoChannel->SetSink(_ssrcVideo.incoming, sink.get());
|
_videoChannel->SetSink(_ssrcVideo.incoming, _currentIncomingVideoSink.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaManager::setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
void MediaManager::setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
||||||
_currentOutgoingVideoSink = sink;
|
_currentOutgoingVideoSink = sink;
|
||||||
_nativeVideoSource->AddOrUpdateSink(sink.get(), rtc::VideoSinkWants());
|
_nativeVideoSource->AddOrUpdateSink(_currentOutgoingVideoSink.get(), rtc::VideoSinkWants());
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaManager::NetworkInterfaceImpl::NetworkInterfaceImpl(MediaManager *mediaManager, bool isVideo) :
|
MediaManager::NetworkInterfaceImpl::NetworkInterfaceImpl(MediaManager *mediaManager, bool isVideo) :
|
||||||
|
@ -64,6 +64,8 @@ public:
|
|||||||
void setIsConnected(bool isConnected);
|
void setIsConnected(bool isConnected);
|
||||||
void receivePacket(const rtc::CopyOnWriteBuffer &packet);
|
void receivePacket(const rtc::CopyOnWriteBuffer &packet);
|
||||||
void notifyPacketSent(const rtc::SentPacket &sentPacket);
|
void notifyPacketSent(const rtc::SentPacket &sentPacket);
|
||||||
|
void setSendVideo(bool sendVideo);
|
||||||
|
void switchVideoCamera();
|
||||||
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
void setIncomingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
||||||
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
void setOutgoingVideoOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
|
||||||
|
|
||||||
@ -78,6 +80,12 @@ private:
|
|||||||
SSRC _ssrcAudio;
|
SSRC _ssrcAudio;
|
||||||
SSRC _ssrcVideo;
|
SSRC _ssrcVideo;
|
||||||
|
|
||||||
|
bool _isConnected;
|
||||||
|
|
||||||
|
std::vector<cricket::VideoCodec> _videoCodecs;
|
||||||
|
bool _isSendingVideo;
|
||||||
|
bool _useFrontCamera;
|
||||||
|
|
||||||
std::unique_ptr<cricket::MediaEngineInterface> _mediaEngine;
|
std::unique_ptr<cricket::MediaEngineInterface> _mediaEngine;
|
||||||
std::unique_ptr<webrtc::Call> _call;
|
std::unique_ptr<webrtc::Call> _call;
|
||||||
webrtc::FieldTrialBasedConfig _fieldTrials;
|
webrtc::FieldTrialBasedConfig _fieldTrials;
|
||||||
|
@ -175,18 +175,17 @@ _signalingDataEmitted(signalingDataEmitted) {
|
|||||||
flags |= cricket::PORTALLOCATOR_DISABLE_UDP;
|
flags |= cricket::PORTALLOCATOR_DISABLE_UDP;
|
||||||
flags |= cricket::PORTALLOCATOR_DISABLE_STUN;
|
flags |= cricket::PORTALLOCATOR_DISABLE_STUN;
|
||||||
}
|
}
|
||||||
//flags |= cricket::PORTALLOCATOR_DISABLE_UDP;
|
|
||||||
_portAllocator->set_flags(_portAllocator->flags() | flags);
|
_portAllocator->set_flags(_portAllocator->flags() | flags);
|
||||||
_portAllocator->Initialize();
|
_portAllocator->Initialize();
|
||||||
|
|
||||||
rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("hlgkfjdrtjfykgulhijkljhulyo.uksouth.cloudapp.azure.com", 3478);
|
rtc::SocketAddress defaultStunAddress = rtc::SocketAddress("134.122.52.178", 3478);
|
||||||
cricket::ServerAddresses stunServers;
|
cricket::ServerAddresses stunServers;
|
||||||
stunServers.insert(defaultStunAddress);
|
stunServers.insert(defaultStunAddress);
|
||||||
std::vector<cricket::RelayServerConfig> turnServers;
|
std::vector<cricket::RelayServerConfig> turnServers;
|
||||||
turnServers.push_back(cricket::RelayServerConfig(
|
turnServers.push_back(cricket::RelayServerConfig(
|
||||||
rtc::SocketAddress("hlgkfjdrtjfykgulhijkljhulyo.uksouth.cloudapp.azure.com", 3478),
|
rtc::SocketAddress("134.122.52.178", 3478),
|
||||||
"user",
|
"openrelay",
|
||||||
"root",
|
"openrelay",
|
||||||
cricket::PROTO_UDP
|
cricket::PROTO_UDP
|
||||||
));
|
));
|
||||||
_portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE);
|
_portAllocator->SetConfiguration(stunServers, turnServers, 2, webrtc::NO_PRUNE);
|
||||||
@ -233,7 +232,7 @@ NetworkManager::~NetworkManager() {
|
|||||||
_socketFactory.reset();
|
_socketFactory.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkManager::receiveSignalingData(const std::vector<uint8_t> &data) {
|
void NetworkManager::receiveSignalingData(const rtc::CopyOnWriteBuffer &data) {
|
||||||
rtc::ByteBufferReader reader((const char *)data.data(), data.size());
|
rtc::ByteBufferReader reader((const char *)data.data(), data.size());
|
||||||
uint32_t candidateCount = 0;
|
uint32_t candidateCount = 0;
|
||||||
if (!reader.ReadUInt32(&candidateCount)) {
|
if (!reader.ReadUInt32(&candidateCount)) {
|
||||||
|
@ -47,7 +47,7 @@ public:
|
|||||||
);
|
);
|
||||||
~NetworkManager();
|
~NetworkManager();
|
||||||
|
|
||||||
void receiveSignalingData(const std::vector<uint8_t> &data);
|
void receiveSignalingData(const rtc::CopyOnWriteBuffer &data);
|
||||||
void sendPacket(const rtc::CopyOnWriteBuffer &packet);
|
void sendPacket(const rtc::CopyOnWriteBuffer &packet);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -138,6 +138,7 @@ public:
|
|||||||
TgVoipNetworkType initialNetworkType,
|
TgVoipNetworkType initialNetworkType,
|
||||||
TgVoipEncryptionKey const &encryptionKey,
|
TgVoipEncryptionKey const &encryptionKey,
|
||||||
std::function<void(TgVoipState)> stateUpdated,
|
std::function<void(TgVoipState)> stateUpdated,
|
||||||
|
std::function<void(bool)> videoStateUpdated,
|
||||||
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -158,6 +159,8 @@ public:
|
|||||||
virtual TgVoipPersistentState getPersistentState() = 0;
|
virtual TgVoipPersistentState getPersistentState() = 0;
|
||||||
|
|
||||||
virtual void receiveSignalingData(const std::vector<uint8_t> &data) = 0;
|
virtual void receiveSignalingData(const std::vector<uint8_t> &data) = 0;
|
||||||
|
virtual void setSendVideo(bool sendVideo) = 0;
|
||||||
|
virtual void switchVideoCamera() = 0;
|
||||||
|
|
||||||
virtual TgVoipFinalState stop() = 0;
|
virtual TgVoipFinalState stop() = 0;
|
||||||
};
|
};
|
||||||
|
@ -143,6 +143,7 @@ public:
|
|||||||
TgVoipEncryptionKey const &encryptionKey,
|
TgVoipEncryptionKey const &encryptionKey,
|
||||||
TgVoipNetworkType initialNetworkType,
|
TgVoipNetworkType initialNetworkType,
|
||||||
std::function<void(TgVoipState)> stateUpdated,
|
std::function<void(TgVoipState)> stateUpdated,
|
||||||
|
std::function<void(bool)> videoStateUpdated,
|
||||||
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
||||||
) :
|
) :
|
||||||
_stateUpdated(stateUpdated),
|
_stateUpdated(stateUpdated),
|
||||||
@ -156,7 +157,7 @@ public:
|
|||||||
|
|
||||||
bool enableP2P = config.enableP2P;
|
bool enableP2P = config.enableP2P;
|
||||||
|
|
||||||
_manager.reset(new ThreadLocalObject<Manager>(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, stateUpdated, signalingDataEmitted](){
|
_manager.reset(new ThreadLocalObject<Manager>(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, stateUpdated, videoStateUpdated, signalingDataEmitted](){
|
||||||
return new Manager(
|
return new Manager(
|
||||||
getManagerThread(),
|
getManagerThread(),
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
@ -164,6 +165,9 @@ public:
|
|||||||
[stateUpdated](const TgVoipState &state) {
|
[stateUpdated](const TgVoipState &state) {
|
||||||
stateUpdated(state);
|
stateUpdated(state);
|
||||||
},
|
},
|
||||||
|
[videoStateUpdated](bool isActive) {
|
||||||
|
videoStateUpdated(isActive);
|
||||||
|
},
|
||||||
[signalingDataEmitted](const std::vector<uint8_t> &data) {
|
[signalingDataEmitted](const std::vector<uint8_t> &data) {
|
||||||
signalingDataEmitted(data);
|
signalingDataEmitted(data);
|
||||||
}
|
}
|
||||||
@ -183,6 +187,18 @@ public:
|
|||||||
manager->receiveSignalingData(data);
|
manager->receiveSignalingData(data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void setSendVideo(bool sendVideo) override {
|
||||||
|
_manager->perform([sendVideo](Manager *manager) {
|
||||||
|
manager->setSendVideo(sendVideo);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
void switchVideoCamera() override {
|
||||||
|
_manager->perform([](Manager *manager) {
|
||||||
|
manager->switchVideoCamera();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void setNetworkType(TgVoipNetworkType networkType) override {
|
void setNetworkType(TgVoipNetworkType networkType) override {
|
||||||
/*message::NetworkType mappedType;
|
/*message::NetworkType mappedType;
|
||||||
@ -361,6 +377,7 @@ TgVoip *TgVoip::makeInstance(
|
|||||||
TgVoipNetworkType initialNetworkType,
|
TgVoipNetworkType initialNetworkType,
|
||||||
TgVoipEncryptionKey const &encryptionKey,
|
TgVoipEncryptionKey const &encryptionKey,
|
||||||
std::function<void(TgVoipState)> stateUpdated,
|
std::function<void(TgVoipState)> stateUpdated,
|
||||||
|
std::function<void(bool)> videoStateUpdated,
|
||||||
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
std::function<void(const std::vector<uint8_t> &)> signalingDataEmitted
|
||||||
) {
|
) {
|
||||||
return new TgVoipImpl(
|
return new TgVoipImpl(
|
||||||
@ -371,6 +388,7 @@ TgVoip *TgVoip::makeInstance(
|
|||||||
encryptionKey,
|
encryptionKey,
|
||||||
initialNetworkType,
|
initialNetworkType,
|
||||||
stateUpdated,
|
stateUpdated,
|
||||||
|
videoStateUpdated,
|
||||||
signalingDataEmitted
|
signalingDataEmitted
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,13 @@ typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) {
|
|||||||
OngoingCallStateReconnecting
|
OngoingCallStateReconnecting
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) {
|
||||||
|
OngoingCallVideoStateInactive,
|
||||||
|
OngoingCallVideoStateRequesting,
|
||||||
|
OngoingCallVideoStateInvited,
|
||||||
|
OngoingCallVideoStateActive
|
||||||
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) {
|
typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) {
|
||||||
OngoingCallNetworkTypeWifi,
|
OngoingCallNetworkTypeWifi,
|
||||||
OngoingCallNetworkTypeCellularGprs,
|
OngoingCallNetworkTypeCellularGprs,
|
||||||
@ -62,7 +69,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
+ (int32_t)maxLayer;
|
+ (int32_t)maxLayer;
|
||||||
+ (NSString * _Nonnull)version;
|
+ (NSString * _Nonnull)version;
|
||||||
|
|
||||||
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc);
|
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc);
|
||||||
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
|
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData;
|
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData;
|
||||||
@ -75,6 +82,8 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
- (NSData * _Nonnull)getDerivedState;
|
- (NSData * _Nonnull)getDerivedState;
|
||||||
|
|
||||||
- (void)setIsMuted:(bool)isMuted;
|
- (void)setIsMuted:(bool)isMuted;
|
||||||
|
- (void)setVideoEnabled:(bool)videoEnabled;
|
||||||
|
- (void)switchVideoCamera;
|
||||||
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType;
|
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType;
|
||||||
- (void)makeIncomingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion;
|
- (void)makeIncomingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion;
|
||||||
- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion;
|
- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion;
|
||||||
|
@ -34,6 +34,8 @@ using namespace TGVOIP_NAMESPACE;
|
|||||||
TgVoip *_tgVoip;
|
TgVoip *_tgVoip;
|
||||||
|
|
||||||
OngoingCallStateWebrtc _state;
|
OngoingCallStateWebrtc _state;
|
||||||
|
OngoingCallVideoStateWebrtc _videoState;
|
||||||
|
|
||||||
int32_t _signalBars;
|
int32_t _signalBars;
|
||||||
NSData *_lastDerivedState;
|
NSData *_lastDerivedState;
|
||||||
|
|
||||||
@ -127,6 +129,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
_callPacketTimeout = 10.0;
|
_callPacketTimeout = 10.0;
|
||||||
_networkType = networkType;
|
_networkType = networkType;
|
||||||
_sendSignalingData = [sendSignalingData copy];
|
_sendSignalingData = [sendSignalingData copy];
|
||||||
|
_videoState = OngoingCallVideoStateInactive;
|
||||||
|
|
||||||
std::vector<uint8_t> derivedStateValue;
|
std::vector<uint8_t> derivedStateValue;
|
||||||
derivedStateValue.resize(derivedState.length);
|
derivedStateValue.resize(derivedState.length);
|
||||||
@ -172,7 +175,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
.initializationTimeout = _callConnectTimeout,
|
.initializationTimeout = _callConnectTimeout,
|
||||||
.receiveTimeout = _callPacketTimeout,
|
.receiveTimeout = _callPacketTimeout,
|
||||||
.dataSaving = callControllerDataSavingForType(dataSaving),
|
.dataSaving = callControllerDataSavingForType(dataSaving),
|
||||||
.enableP2P = allowP2P,
|
.enableP2P = (bool)allowP2P,
|
||||||
.enableAEC = false,
|
.enableAEC = false,
|
||||||
.enableNS = true,
|
.enableNS = true,
|
||||||
.enableAGC = true,
|
.enableAGC = true,
|
||||||
@ -206,6 +209,25 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
|
[weakSelf, queue](bool isActive) {
|
||||||
|
[queue dispatch:^{
|
||||||
|
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
|
||||||
|
if (strongSelf) {
|
||||||
|
OngoingCallVideoStateWebrtc videoState;
|
||||||
|
if (isActive) {
|
||||||
|
videoState = OngoingCallVideoStateActive;
|
||||||
|
} else {
|
||||||
|
videoState = OngoingCallVideoStateInactive;
|
||||||
|
}
|
||||||
|
if (strongSelf->_videoState != videoState) {
|
||||||
|
strongSelf->_videoState = videoState;
|
||||||
|
if (strongSelf->_stateChanged) {
|
||||||
|
strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
},
|
||||||
[weakSelf, queue](const std::vector<uint8_t> &data) {
|
[weakSelf, queue](const std::vector<uint8_t> &data) {
|
||||||
NSData *mappedData = [[NSData alloc] initWithBytes:data.data() length:data.size()];
|
NSData *mappedData = [[NSData alloc] initWithBytes:data.data() length:data.size()];
|
||||||
[queue dispatch:^{
|
[queue dispatch:^{
|
||||||
@ -300,7 +322,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
_state = callState;
|
_state = callState;
|
||||||
|
|
||||||
if (_stateChanged) {
|
if (_stateChanged) {
|
||||||
_stateChanged(callState);
|
_stateChanged(_state, _videoState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,6 +358,18 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setVideoEnabled:(bool)videoEnabled {
|
||||||
|
if (_tgVoip) {
|
||||||
|
_tgVoip->setSendVideo(videoEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)switchVideoCamera {
|
||||||
|
if (_tgVoip) {
|
||||||
|
_tgVoip->switchVideoCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType {
|
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType {
|
||||||
if (_networkType != networkType) {
|
if (_networkType != networkType) {
|
||||||
_networkType = networkType;
|
_networkType = networkType;
|
||||||
|
32
third-party/webrtc/BUCK
vendored
32
third-party/webrtc/BUCK
vendored
@ -15,16 +15,38 @@ genrule(
|
|||||||
set -x
|
set -x
|
||||||
echo "SRCDIR=$SRCDIR"
|
echo "SRCDIR=$SRCDIR"
|
||||||
|
|
||||||
OUT_DIR="ios"
|
|
||||||
|
|
||||||
BUILD_ARCH=arm64
|
BUILD_ARCH=arm64
|
||||||
|
|
||||||
BUILD_DIR="$SRCDIR/$BUILD_ARCH"
|
BUILD_DIR_ARM64="$SRCDIR/$BUILD_ARCH"
|
||||||
|
|
||||||
|
BUILD_DIR="$BUILD_DIR_ARM64"
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
|
mkdir -p "$BUILD_DIR/webrtc-ios"
|
||||||
|
cp -R "$SRCDIR/webrtc-ios/src" "$BUILD_DIR/webrtc-ios/src"
|
||||||
|
|
||||||
|
DEPOT_TOOLS_PATH="$(location //third-party:depot_tools_sources)"
|
||||||
|
|
||||||
|
rm -rf "$BUILD_DIR/depot_tools"
|
||||||
|
cp -R "$DEPOT_TOOLS_PATH" "$BUILD_DIR/"
|
||||||
|
|
||||||
|
rm -rf "$BUILD_DIR/openssl"
|
||||||
|
cp -R "$(location //submodules/openssl:openssl_build_merged)" "$BUILD_DIR/openssl/"
|
||||||
|
cp -R "$(location //submodules/openssl:openssl_libssl_merged)" "$BUILD_DIR/libssl/"
|
||||||
|
|
||||||
|
sh $SRCDIR/build-webrtc-buck.sh "$BUILD_DIR" $BUILD_ARCH
|
||||||
|
|
||||||
|
BUILD_ARCH=arm
|
||||||
|
|
||||||
|
BUILD_DIR_ARMV7="$SRCDIR/$BUILD_ARCH"
|
||||||
|
|
||||||
|
BUILD_DIR="$BUILD_DIR_ARMV7"
|
||||||
|
|
||||||
rm -rf "$BUILD_DIR"
|
rm -rf "$BUILD_DIR"
|
||||||
mkdir -p "$BUILD_DIR"
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
mkdir -p "$BUILD_DIR/webrtc-ios"
|
mkdir -p "$BUILD_DIR/webrtc-ios"
|
||||||
cp -R "$SRCDIR/webrtc-ios/.git" "$BUILD_DIR/webrtc-ios/.git"
|
|
||||||
cp -R "$SRCDIR/webrtc-ios/src" "$BUILD_DIR/webrtc-ios/src"
|
cp -R "$SRCDIR/webrtc-ios/src" "$BUILD_DIR/webrtc-ios/src"
|
||||||
|
|
||||||
DEPOT_TOOLS_PATH="$(location //third-party:depot_tools_sources)"
|
DEPOT_TOOLS_PATH="$(location //third-party:depot_tools_sources)"
|
||||||
@ -39,7 +61,7 @@ genrule(
|
|||||||
sh $SRCDIR/build-webrtc-buck.sh "$BUILD_DIR" $BUILD_ARCH
|
sh $SRCDIR/build-webrtc-buck.sh "$BUILD_DIR" $BUILD_ARCH
|
||||||
|
|
||||||
mkdir -p "$OUT"
|
mkdir -p "$OUT"
|
||||||
cp "$BUILD_DIR/webrtc-ios/src/out/$OUT_DIR/obj/sdk/libframework_objc_static.a" "$OUT/"
|
lipo -create "$BUILD_DIR_ARMV7/webrtc-ios/src/out/ios/obj/sdk/libframework_objc_static.a" "$BUILD_DIR_ARM64/webrtc-ios/src/out/ios_64/obj/sdk/libframework_objc_static.a" -output "$OUT/libframework_objc_static.a"
|
||||||
""",
|
""",
|
||||||
out = "libwebrtc",
|
out = "libwebrtc",
|
||||||
visibility = ["PUBLIC"]
|
visibility = ["PUBLIC"]
|
||||||
|
2
third-party/webrtc/BUILD
vendored
2
third-party/webrtc/BUILD
vendored
@ -35,7 +35,7 @@ genrule(
|
|||||||
echo "Unsupported architecture $(TARGET_CPU)"
|
echo "Unsupported architecture $(TARGET_CPU)"
|
||||||
fi
|
fi
|
||||||
BUILD_DIR="$(RULEDIR)/$$BUILD_ARCH"
|
BUILD_DIR="$(RULEDIR)/$$BUILD_ARCH"
|
||||||
rm -rf "$$BUILD_DIR"
|
#rm -rf "$$BUILD_DIR"
|
||||||
mkdir -p "$$BUILD_DIR"
|
mkdir -p "$$BUILD_DIR"
|
||||||
|
|
||||||
SOURCE_PATH="third-party/webrtc/webrtc-ios/src"
|
SOURCE_PATH="third-party/webrtc/webrtc-ios/src"
|
||||||
|
4
third-party/webrtc/build-webrtc-buck.sh
vendored
4
third-party/webrtc/build-webrtc-buck.sh
vendored
@ -21,7 +21,9 @@ mv openssl/lib/libcrypto.a openssl/
|
|||||||
mv libssl/lib/libssl.a openssl/
|
mv libssl/lib/libssl.a openssl/
|
||||||
|
|
||||||
OUT_DIR="ios"
|
OUT_DIR="ios"
|
||||||
if [ "$ARCH" == "x64" ]; then
|
if [ "$ARCH" == "arm64" ]; then
|
||||||
|
OUT_DIR="ios_64"
|
||||||
|
elif [ "$ARCH" == "x64" ]; then
|
||||||
OUT_DIR="ios_sim"
|
OUT_DIR="ios_sim"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user