Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-11-30 20:24:27 +04:00
commit 2cac381736
21 changed files with 2917 additions and 2720 deletions

View File

@ -3,7 +3,7 @@
include Utils.makefile
APP_VERSION="7.2.1"
APP_VERSION="7.3"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)

View File

@ -244,7 +244,6 @@ official_apple_pay_merchants = [
"merchant.sberbank.test.ph.telegra.Telegraph",
"merchant.privatbank.test.telergramios",
"merchant.privatbank.prod.telergram",
"merchant.telegram.tranzzo.test",
]
official_bundle_ids = [

View File

@ -2493,6 +2493,9 @@ Unused sets are archived when you add more.";
"Call.CallInProgressMessage" = "Finish call with %1$@ and start a new one with %2$@?";
"Call.ExternalCallInProgressMessage" = "Please finish the current call first.";
"Call.VoiceChatInProgressTitle" = "Voice Chat in Progress";
"Call.VoiceChatInProgressMessage" = "Leave voice chat in %1$@ and start a new one with %2$@?";
"Call.Message" = "Message";
"UserInfo.TapToCall" = "Tap to make an end-to-end encrypted call";
@ -5972,3 +5975,5 @@ Sorry for the inconvenience.";
"VoiceChat.Panel.MembersSpeaking_any" = "%@ members speaking";
"ChannelInfo.CreateVoiceChat" = "Create Voice Chat";
"VoiceChat.AnonymousDisabledAlertText" = "Sorry, you can't join voice chat as an anonymous admin.";

@ -1 +1 @@
Subproject commit 2583fa0bfd6909e7936da5b30e3547ba13e198dc
Subproject commit f7f2b6d7c952f3cf6bdcedce6a0a2a40a27ff596

View File

@ -51,6 +51,7 @@ BAZEL_OPTIONS=(\
--spawn_strategy=standalone \
--strategy=SwiftCompile=standalone \
--features=swift.enable_batch_mode \
--apple_generate_dsym \
--swiftcopt=-j${CORE_COUNT_MINUS_ONE} \
)

View File

@ -81,7 +81,7 @@ COMMIT_ID="$(git rev-parse HEAD)"
COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
if [ -z "$2" ]; then
COMMIT_COUNT=$(git rev-list --count HEAD)
COMMIT_COUNT="$(($COMMIT_COUNT+1000))"
COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
BUILD_NUMBER="$COMMIT_COUNT"
else
BUILD_NUMBER="$2"

View File

@ -163,21 +163,29 @@ public struct PresentationGroupCallState: Equatable {
case connected
}
public enum DefaultParticipantMuteState {
case unmuted
case muted
}
public var networkState: NetworkState
public var canManageCall: Bool
public var adminIds: Set<PeerId>
public var muteState: GroupCallParticipantsContext.Participant.MuteState?
public var defaultParticipantMuteState: DefaultParticipantMuteState?
public init(
networkState: NetworkState,
canManageCall: Bool,
adminIds: Set<PeerId>,
muteState: GroupCallParticipantsContext.Participant.MuteState?
muteState: GroupCallParticipantsContext.Participant.MuteState?,
defaultParticipantMuteState: DefaultParticipantMuteState?
) {
self.networkState = networkState
self.canManageCall = canManageCall
self.adminIds = adminIds
self.muteState = muteState
self.defaultParticipantMuteState = defaultParticipantMuteState
}
}
@ -263,6 +271,7 @@ public protocol PresentationGroupCall: class {
func toggleIsMuted()
func setIsMuted(action: PresentationGroupCallMuteAction)
func updateDefaultParticipantsAreMuted(isMuted: Bool)
func setCurrentAudioOutput(_ output: AudioSessionOutput)
func updateMuteState(peerId: PeerId, isMuted: Bool)

View File

@ -69,8 +69,45 @@ public enum ContainedViewLayoutTransition {
}
}
public extension CGRect {
var ensuredValid: CGRect {
if !ASIsCGRectValidForLayout(CGRect(origin: CGPoint(), size: self.size)) {
return CGRect()
}
if !ASIsCGPositionValidForLayout(self.origin) {
return CGRect()
}
return self
}
}
public extension ContainedViewLayoutTransition {
func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if frame.origin.x.isNaN {
return
}
if frame.origin.y.isNaN {
return
}
if frame.size.width.isNaN {
return
}
if frame.size.width < 0.0 {
return
}
if frame.size.height.isNaN {
return
}
if frame.size.height < 0.0 {
return
}
if !ASIsCGRectValidForLayout(CGRect(origin: CGPoint(), size: frame.size)) {
return
}
if !ASIsCGPositionValidForLayout(frame.origin) {
return
}
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {

View File

@ -443,6 +443,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
if origin == nil {
self.editButton.isHidden = true
self.deleteButton.isHidden = true
self.editButton.isHidden = true
}
}

View File

@ -20,7 +20,7 @@ private enum AdjacentEntryGroupInfo {
private func getAdjacentEntryGroupInfo(_ entry: IntermediateMessageHistoryEntry?, key: Int64) -> (IntermediateMessageHistoryEntry?, AdjacentEntryGroupInfo) {
if let entry = entry {
if let groupingKey = entry.message.groupingKey {
if let groupingKey = entry.message.groupingKey, let groupInfo = entry.message.groupInfo {
if groupingKey == key {
if let groupInfo = entry.message.groupInfo {
return (entry, .sameGroup(groupInfo))

View File

@ -21,7 +21,8 @@ private extension PresentationGroupCallState {
networkState: .connecting,
canManageCall: false,
adminIds: Set(),
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
defaultParticipantMuteState: nil
)
}
}
@ -389,7 +390,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
}
case let .call(isTerminated):
case let .call(isTerminated, _):
if isTerminated {
strongSelf._canBeRemoved.set(.single(true))
}
@ -497,10 +498,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if let clientParams = joinCallResult.callInfo.clientParams {
strongSelf.updateSessionState(internalState: .estabilished(info: joinCallResult.callInfo, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
}
}, error: { _ in
}, error: { error in
guard let strongSelf = self else {
return
}
if case .anonymousNotAllowed = error {
let presentationData = strongSelf.accountContext.sharedContext.currentPresentationData.with { $0 }
strongSelf.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.VoiceChat_AnonymousDisabledAlertText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
]), on: .root, blockInteraction: false, completion: {})
}
strongSelf._canBeRemoved.set(.single(true))
}))
}))
@ -573,6 +580,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo)))
self.stateValue.canManageCall = initialState.isCreator || initialState.adminIds.contains(self.accountContext.account.peerId)
if self.stateValue.canManageCall && initialState.defaultParticipantsAreMuted.canChange {
self.stateValue.defaultParticipantMuteState = initialState.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
}
self.ssrcMapping.removeAll()
var ssrcs: [UInt32] = []
@ -634,6 +644,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf.stateValue.adminIds = state.adminIds
if (state.isCreator || state.adminIds.contains(strongSelf.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange {
strongSelf.stateValue.defaultParticipantMuteState = state.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
}
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
participantCount: state.totalCount,
topParticipants: topParticipants,
@ -690,14 +704,29 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if terminateIfPossible {
self.leaveDisposable.set((stopGroupCall(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash)
|> deliverOnMainQueue).start(completed: { [weak self] in
self?._canBeRemoved.set(.single(true))
guard let strongSelf = self else {
return
}
strongSelf.callContext?.stop()
strongSelf.callContext = nil
strongSelf._canBeRemoved.set(.single(true))
}))
} else {
self.leaveDisposable.set((leaveGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, source: localSsrc)
|> deliverOnMainQueue).start(error: { [weak self] _ in
self?._canBeRemoved.set(.single(true))
guard let strongSelf = self else {
return
}
strongSelf.callContext?.stop()
strongSelf.callContext = nil
strongSelf._canBeRemoved.set(.single(true))
}, completed: { [weak self] in
self?._canBeRemoved.set(.single(true))
guard let strongSelf = self else {
return
}
strongSelf.callContext?.stop()
strongSelf.callContext = nil
strongSelf._canBeRemoved.set(.single(true))
}))
}
} else {
@ -900,4 +929,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
}
public func updateDefaultParticipantsAreMuted(isMuted: Bool) {
self.participantsContext?.updateDefaultParticipantsAreMuted(isMuted: isMuted)
}
}

View File

@ -386,7 +386,7 @@ public final class VoiceChatController: ViewController {
}
default:
if peer.id != strongSelf.context.account.peerId {
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)), !callState.adminIds.contains(peer.id) {
if let muteState = entry.muteState, !muteState.canUnmute {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_UnmutePeer, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor)
@ -613,17 +613,44 @@ public final class VoiceChatController: ViewController {
}
var items: [ContextMenuItem] = []
/*items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
})))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionAdmin, icon: { _ in return nil}, action: { _, f in
f(.dismissWithoutContent)
})))
items.append(.separator)*/
if let callState = strongSelf.callState, callState.canManageCall, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
let isMuted = defaultParticipantMuteState == .muted
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
if isMuted {
return nil
} else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}
}, action: { _, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
strongSelf.call.updateDefaultParticipantsAreMuted(isMuted: false)
})))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionAdmin, icon: { theme in
if !isMuted {
return nil
} else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}
}, action: { _, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
strongSelf.call.updateDefaultParticipantsAreMuted(isMuted: true)
})))
}
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak self] _, f in
@ -698,7 +725,7 @@ public final class VoiceChatController: ViewController {
super.didLoad()
let titleView = VoiceChatControllerTitleView(theme: self.presentationData.theme)
titleView.set(title: "Voice Chat", subtitle: "connecting")
titleView.set(title: self.presentationData.strings.VoiceChat_Title, subtitle: self.presentationData.strings.SocksProxySetup_ProxyStatusConnecting)
self.controller?.navigationItem.titleView = titleView
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.actionButtonPressGesture(_:)))

View File

@ -2970,11 +2970,24 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
return current
}
})
switch call {
case let .groupCall(flags, _, _, _, _, _):
let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0
let defaultParticipantsAreMuted = GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: isMuted, canChange: isMuted)
updatedGroupCallParticipants.append((
info.id,
.call(isTerminated: false, defaultParticipantsAreMuted: defaultParticipantsAreMuted)
))
default:
break
}
}
case let .groupCallDiscarded(callId, _, _):
updatedGroupCallParticipants.append((
callId,
.call(isTerminated: true)
.call(isTerminated: true, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false))
))
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in

View File

@ -244,6 +244,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: Set(),
isCreator: false,
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false),
totalCount: totalCount,
version: version
)
@ -254,6 +255,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
public enum JoinGroupCallError {
case generic
case anonymousNotAllowed
}
public struct JoinGroupCallResult {
@ -267,7 +269,10 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
flags |= (1 << 0)
}
return account.network.request(Api.functions.phone.joinGroupCall(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload)))
|> mapError { _ -> JoinGroupCallError in
|> mapError { error -> JoinGroupCallError in
if error.errorDescription == "GROUP_CALL_ANONYMOUS_FORBIDDEN" {
return .anonymousNotAllowed
}
return .generic
}
|> mapToSignal { updates -> Signal<JoinGroupCallResult, JoinGroupCallError> in
@ -318,6 +323,16 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
switch update {
case let .updateGroupCall(_, call):
maybeParsedCall = GroupCallInfo(call)
switch call {
case let .groupCall(flags, _, _, _, _, _):
let isMuted = (flags & (1 << 1)) != 0
let canChange = (flags & (1 << 2)) != 0
state.defaultParticipantsAreMuted = GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: isMuted, canChange: canChange)
default:
break
}
break loop
default:
break
@ -521,10 +536,16 @@ public final class GroupCallParticipantsContext {
}
public struct State: Equatable {
public struct DefaultParticipantsAreMuted: Equatable {
public var isMuted: Bool
public var canChange: Bool
}
public var participants: [Participant]
public var nextParticipantsFetchOffset: String?
public var adminIds: Set<PeerId>
public var isCreator: Bool
public var defaultParticipantsAreMuted: DefaultParticipantsAreMuted
public var totalCount: Int
public var version: Int32
}
@ -578,7 +599,7 @@ public final class GroupCallParticipantsContext {
}
case state(update: StateUpdate)
case call(isTerminated: Bool)
case call(isTerminated: Bool, defaultParticipantsAreMuted: State.DefaultParticipantsAreMuted)
}
private let account: Account
@ -627,6 +648,8 @@ public final class GroupCallParticipantsContext {
private let updatesDisposable = MetaDisposable()
private var activitiesDisposable: Disposable?
private let updateDefaultMuteDisposable = MetaDisposable()
public init(account: Account, peerId: PeerId, id: Int64, accessHash: Int64, state: State) {
self.account = account
self.id = id
@ -699,6 +722,7 @@ public final class GroupCallParticipantsContext {
nextParticipantsFetchOffset: strongSelf.stateValue.state.nextParticipantsFetchOffset,
adminIds: strongSelf.stateValue.state.adminIds,
isCreator: strongSelf.stateValue.state.isCreator,
defaultParticipantsAreMuted: strongSelf.stateValue.state.defaultParticipantsAreMuted,
totalCount: strongSelf.stateValue.state.totalCount,
version: strongSelf.stateValue.state.version
),
@ -712,6 +736,7 @@ public final class GroupCallParticipantsContext {
self.disposable.dispose()
self.updatesDisposable.dispose()
self.activitiesDisposable?.dispose()
self.updateDefaultMuteDisposable.dispose()
}
public func addUpdates(updates: [Update]) {
@ -719,6 +744,8 @@ public final class GroupCallParticipantsContext {
for update in updates {
if case let .state(update) = update {
stateUpdates.append(update)
} else if case let .call(_, defaultParticipantsAreMuted) = update {
self.stateValue.state.defaultParticipantsAreMuted = defaultParticipantsAreMuted
}
}
@ -831,6 +858,7 @@ public final class GroupCallParticipantsContext {
let nextParticipantsFetchOffset = strongSelf.stateValue.state.nextParticipantsFetchOffset
let adminIds = strongSelf.stateValue.state.adminIds
let isCreator = strongSelf.stateValue.state.isCreator
let defaultParticipantsAreMuted = strongSelf.stateValue.state.defaultParticipantsAreMuted
updatedParticipants.sort()
for i in 0 ..< updatedParticipants.count {
@ -848,6 +876,7 @@ public final class GroupCallParticipantsContext {
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: adminIds,
isCreator: isCreator,
defaultParticipantsAreMuted: defaultParticipantsAreMuted,
totalCount: updatedTotalCount,
version: update.version
),
@ -949,6 +978,22 @@ public final class GroupCallParticipantsContext {
}
}))
}
public func updateDefaultParticipantsAreMuted(isMuted: Bool) {
if isMuted == self.stateValue.state.defaultParticipantsAreMuted.isMuted {
return
}
self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted
self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), joinMuted: isMuted ? .boolTrue : .boolFalse))
|> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else {
return
}
strongSelf.account.stateManager.addUpdates(updates)
}))
}
}
extension GroupCallParticipantsContext.Update.StateUpdate {

View File

@ -639,6 +639,11 @@ extension StoreMessage {
if (flags & (1 << 4)) != 0 {
notificationFlags.insert(.personal)
}
if (flags & (1 << 4)) != 0 {
notificationFlags.insert(.personal)
let notConsumed = (flags & (1 << 5)) != 0
attributes.append(ConsumablePersonalMentionMessageAttribute(consumed: !notConsumed, pending: false))
}
if (flags & (1 << 13)) != 0 {
notificationFlags.insert(.muted)
}

View File

@ -643,6 +643,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
if data.canEdit && !isPinnedMessages {
var mediaReference: AnyMediaReference?
for media in message.media {
if let image = media as? TelegramMediaImage, let _ = largestImageRepresentation(image.representations) {
mediaReference = ImageMediaReference.standalone(media: image).abstract
break
} else if let file = media as? TelegramMediaFile {
mediaReference = FileMediaReference.standalone(media: file).abstract
break
}
}
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
}, action: { c, f in

View File

@ -1477,7 +1477,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let mosaicRange = mosaicRange {
let maxSize = layoutConstants.image.maxDimensions.fittedToWidthOrSmaller(maximumContentWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
let (innerFramesAndPositions, innerSize) = chatMessageBubbleMosaicLayout(maxSize: maxSize, itemSizes: contentPropertiesAndLayouts[mosaicRange].map { $0.0 ?? CGSize(width: 256.0, height: 256.0) })
let (innerFramesAndPositions, innerSize) = chatMessageBubbleMosaicLayout(maxSize: maxSize, itemSizes: contentPropertiesAndLayouts[mosaicRange].map { item in
guard let size = item.0, size.width > 0.0, size.height > 0 else {
return CGSize(width: 256.0, height: 256.0)
}
return size
})
let framesAndPositions = innerFramesAndPositions.map { ($0.0.offsetBy(dx: layoutConstants.image.bubbleInsets.left, dy: layoutConstants.image.bubbleInsets.top), $0.1) }

View File

@ -680,7 +680,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments)
let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize)
let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize).ensuredValid
let imageApply = imageLayout(arguments)

View File

@ -24,6 +24,7 @@ objc_library(
copts = [
"-Dpixman_region_selfcheck(x)=1",
"-DLOTTIE_DISABLE_ARM_NEON=1",
"-DLOTTIE_THREAD_SAFE=1",
"-DLOTTIE_IMAGE_MODULE_DISABLED=1",
"-I{}".format(package_name()),
"-I{}/rlottie/inc".format(package_name()),