diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index c1281c8448..89559498fc 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -995,6 +995,11 @@ public enum SendInviteLinkScreenSubject { case groupCall(link: String) } +public enum StarsWithdrawalScreenSubject { + case withdraw + case enterAmount(current: StarsAmount) +} + public protocol SharedAccountContext: AnyObject { var sharedContainerPath: String { get } var basePath: String { get } @@ -1179,7 +1184,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController - func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController + func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject, completion: @escaping (Int64) -> Void) -> ViewController func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index dfbb86235a..a47146d274 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -38,9 +38,10 @@ private final class ChannelPermissionsControllerArguments { let updateSlowmode: (Int32) -> Void let updateUnrestrictBoosters: (Int32) -> Void let updateStarsAmount: (StarsAmount?, Bool) -> Void + let openSetCustomStarsAmount: () -> Void let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void - init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, updateUnrestrictBoosters: @escaping (Int32) -> Void, updateStarsAmount: @escaping (StarsAmount?, Bool) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { + init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, updateUnrestrictBoosters: @escaping (Int32) -> Void, updateStarsAmount: @escaping (StarsAmount?, Bool) -> Void, openSetCustomStarsAmount: @escaping () -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { self.context = context self.updatePermission = updatePermission self.addPeer = addPeer @@ -55,6 +56,7 @@ private final class ChannelPermissionsControllerArguments { self.updateSlowmode = updateSlowmode self.updateUnrestrictBoosters = updateUnrestrictBoosters self.updateStarsAmount = updateStarsAmount + self.openSetCustomStarsAmount = openSetCustomStarsAmount self.toggleIsOptionExpanded = toggleIsOptionExpanded } } @@ -427,7 +429,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case let .messagePrice(_, value, maxValue, price): return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, apply in arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0), apply) - }) + }, openSetCustom: nil) case let .messagePriceInfo(_, value): return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section) case let .unrestrictBoostersSwitch(_, title, value): @@ -1267,6 +1269,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent |> deliverOnMainQueue).start()) }) } + }, openSetCustomStarsAmount: { }, toggleIsOptionExpanded: { flags in updateState { state in var state = state diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 4f5ddfc578..6db96282fd 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -20,6 +20,7 @@ private final class IncomingMessagePrivacyScreenArguments { let infoLinkAction: () -> Void let openExceptions: () -> Void let openPremiumInfo: () -> Void + let openSetCustomStarsAmount: () -> Void init( context: AccountContext, @@ -27,7 +28,8 @@ private final class IncomingMessagePrivacyScreenArguments { disabledValuePressed: @escaping () -> Void, infoLinkAction: @escaping () -> Void, openExceptions: @escaping () -> Void, - openPremiumInfo: @escaping () -> Void + openPremiumInfo: @escaping () -> Void, + openSetCustomStarsAmount: @escaping () -> Void ) { self.context = context self.updateValue = updateValue @@ -35,6 +37,7 @@ private final class IncomingMessagePrivacyScreenArguments { self.infoLinkAction = infoLinkAction self.openExceptions = openExceptions self.openPremiumInfo = openPremiumInfo + self.openSetCustomStarsAmount = openSetCustomStarsAmount } } @@ -151,6 +154,8 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case let .price(value, maxValue, price, isEnabled): return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, _ in arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0))) + }, openSetCustom: { + arguments.openSetCustomStarsAmount() }, openPremiumInfo: { arguments.openPremiumInfo() }) @@ -365,6 +370,20 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP controller?.replace(with: c) } pushControllerImpl?(controller) + }, + openSetCustomStarsAmount: { + var currentAmount: StarsAmount = StarsAmount(value: 1, nanos: 0) + if case let .paidMessages(value) = stateValue.with({ $0 }).updatedValue { + currentAmount = value + } + let starsScreen = context.sharedContext.makeStarsWithdrawalScreen(context: context, subject: .enterAmount(current: currentAmount), completion: { amount in + updateState { state in + var state = state + state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0)) + return state + } + }) + pushControllerImpl?(starsScreen) } ) diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index e3c6c1fe19..ea7eb8b5e9 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -121,6 +121,7 @@ swift_library( "//submodules/FastBlur", "//submodules/InviteLinksUI", "//third-party/td:TdBinding", + "//submodules/TelegramUI/Components/AnimatedTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 909485deb0..3f2216a746 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -26,6 +26,7 @@ import TooltipUI import BlurredBackgroundComponent import CallsEmoji import InviteLinksUI +import AnimatedTextComponent extension VideoChatCall { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { @@ -262,6 +263,9 @@ final class VideoChatScreenComponent: Component { var invitedPeers: [InvitedPeer] = [] var invitedPeersDisposable: Disposable? + var lastTitleEvent: String? + var lastTitleEventTimer: Foundation.Timer? + var speakingParticipantPeers: [EnginePeer] = [] var visibleParticipants: Set = Set() @@ -320,6 +324,7 @@ final class VideoChatScreenComponent: Component { self.inviteDisposable.dispose() self.conferenceCallStateDisposable?.dispose() self.encryptionKeyEmojiDisposable?.dispose() + self.lastTitleEventTimer?.invalidate() } func animateIn() { @@ -1600,12 +1605,13 @@ final class VideoChatScreenComponent: Component { }) self.memberEventsDisposable?.dispose() - if groupCall.peerId != nil { - self.memberEventsDisposable = (groupCall.memberEvents - |> deliverOnMainQueue).start(next: { [weak self] event in - guard let self, let members = self.members, let environment = self.environment, case let .group(groupCall) = self.currentCall else { - return - } + self.memberEventsDisposable = (groupCall.memberEvents + |> deliverOnMainQueue).start(next: { [weak self] event in + guard let self, let members = self.members, let environment = self.environment, case let .group(groupCall) = self.currentCall else { + return + } + + if groupCall.peerId != nil { if event.joined { var displayEvent = false if case let .channel(channel) = self.peer, case .broadcast = channel.info { @@ -1624,8 +1630,30 @@ final class VideoChatScreenComponent: Component { self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) } } - }) - } + } else { + if event.joined { + self.lastTitleEvent = "\(event.peer.compactDisplayTitle) joined" + } else { + self.lastTitleEvent = "\(event.peer.compactDisplayTitle) left" + } + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + + self.lastTitleEventTimer?.invalidate() + self.lastTitleEventTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.5, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + self.lastTitleEventTimer = nil + self.lastTitleEvent = nil + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) + } + }) case let .conferenceSource(conferenceSource): self.membersDisposable?.dispose() self.membersDisposable = (View.groupCallMembersForConferenceSource(conferenceSource: conferenceSource) @@ -1887,14 +1915,17 @@ final class VideoChatScreenComponent: Component { let maxSingleColumnWidth: CGFloat = 620.0 let isTwoColumnLayout: Bool + let isLandscape: Bool if availableSize.width > maxSingleColumnWidth { if let mappedParticipants, mappedParticipants.participants.contains(where: { $0.videoDescription != nil || $0.presentationDescription != nil }) { isTwoColumnLayout = true } else { isTwoColumnLayout = false } + isLandscape = true } else { isTwoColumnLayout = false + isLandscape = false } var containerOffset: CGFloat = 0.0 @@ -1935,24 +1966,32 @@ final class VideoChatScreenComponent: Component { } }) - let landscapeControlsWidth: CGFloat = 88.0 - let landscapeControlsOffsetX: CGFloat = 18.0 + let landscapeControlsWidth: CGFloat = 104.0 + var landscapeControlsOffsetX: CGFloat = 0.0 let landscapeControlsSpacing: CGFloat = 30.0 - let leftInset: CGFloat = max(environment.safeInsets.left, 14.0) + var leftInset: CGFloat = max(environment.safeInsets.left, 14.0) var rightInset: CGFloat = max(environment.safeInsets.right, 14.0) var buttonsOnTheSide = false if availableSize.width > maxSingleColumnWidth && !environment.metrics.isTablet { + leftInset += 2.0 + rightInset += 2.0 + buttonsOnTheSide = true - rightInset += landscapeControlsWidth + if case .landscapeLeft = environment.orientation { + rightInset = max(rightInset, environment.safeInsets.left + landscapeControlsWidth) + landscapeControlsOffsetX = -environment.safeInsets.left + } else { + rightInset = max(rightInset, landscapeControlsWidth) + } } let topInset: CGFloat = environment.statusBarHeight + 2.0 let navigationBarHeight: CGFloat = 61.0 var navigationHeight = topInset + navigationBarHeight - let navigationButtonAreaWidth: CGFloat = 40.0 + let navigationButtonAreaWidth: CGFloat = 34.0 let navigationButtonDiameter: CGFloat = 28.0 let navigationLeftButtonSize = self.navigationLeftButton.update( @@ -2013,7 +2052,10 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) } - let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize) + var navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize) + if buttonsOnTheSide { + navigationRightButtonFrame.origin.x += 42.0 + } if let navigationRightButtonView = self.navigationRightButton.view { if navigationRightButtonView.superview == nil { self.containerView.addSubview(navigationRightButtonView) @@ -2022,6 +2064,7 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: navigationRightButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) } + var navigationSidebarButtonFrame: CGRect? if isTwoColumnLayout { var navigationSidebarButtonTransition = transition let navigationSidebarButton: ComponentView @@ -2057,7 +2100,8 @@ final class VideoChatScreenComponent: Component { environment: {}, containerSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter) ) - let navigationSidebarButtonFrame = CGRect(origin: CGPoint(x: navigationRightButtonFrame.minX - 32.0 - navigationSidebarButtonSize.width, y: topInset + floor((navigationBarHeight - navigationSidebarButtonSize.height) * 0.5)), size: navigationSidebarButtonSize) + let navigationSidebarButtonFrameValue = CGRect(origin: CGPoint(x: navigationRightButtonFrame.minX - 21.0 - navigationSidebarButtonSize.width, y: topInset + floor((navigationBarHeight - navigationSidebarButtonSize.height) * 0.5)), size: navigationSidebarButtonSize) + navigationSidebarButtonFrame = navigationSidebarButtonFrameValue if let navigationSidebarButtonView = navigationSidebarButton.view { var animateIn = false if navigationSidebarButtonView.superview == nil { @@ -2066,7 +2110,7 @@ final class VideoChatScreenComponent: Component { self.containerView.insertSubview(navigationSidebarButtonView, aboveSubview: navigationRightButtonView) } } - navigationSidebarButtonTransition.setFrame(view: navigationSidebarButtonView, frame: navigationSidebarButtonFrame) + navigationSidebarButtonTransition.setFrame(view: navigationSidebarButtonView, frame: navigationSidebarButtonFrameValue) if animateIn { transition.animateScale(view: navigationSidebarButtonView, from: 0.001, to: 1.0) transition.animateAlpha(view: navigationSidebarButtonView, from: 0.0, to: 1.0) @@ -2082,17 +2126,27 @@ final class VideoChatScreenComponent: Component { } } - let idleTitleStatusText: String + var idleTitleStatusText: [AnimatedTextComponent.Item] = [] if let callState = self.callState { if callState.networkState == .connected, let members = self.members { - idleTitleStatusText = environment.strings.VoiceChat_Panel_Members(Int32(max(1, members.totalCount))) + //TODO:localize + let totalCount = max(1, members.totalCount) + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .number(totalCount, minDigits: 0))) + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(1), isUnbreakable: false, content: .text(totalCount == 1 ? " participant" : " participants"))) + if let lastTitleEvent = self.lastTitleEvent { + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(6), isUnbreakable: false, content: .text(", \(lastTitleEvent)"))) + } else if !self.invitedPeers.isEmpty { + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(3), isUnbreakable: true, content: .text(", "))) + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(4), isUnbreakable: false, content: .number(self.invitedPeers.count, minDigits: 0))) + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(5), isUnbreakable: false, content: .text(" invited"))) + } } else if callState.scheduleTimestamp != nil { - idleTitleStatusText = environment.strings.VoiceChat_Scheduled + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(environment.strings.VoiceChat_Scheduled))) } else { - idleTitleStatusText = environment.strings.VoiceChat_Connecting + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(environment.strings.VoiceChat_Connecting))) } } else { - idleTitleStatusText = " " + idleTitleStatusText.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: false, content: .text(" "))) } let canManageCall = self.callState?.canManageCall ?? false @@ -2108,6 +2162,7 @@ final class VideoChatScreenComponent: Component { title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? environment.strings.VideoChat_GroupCallTitle, status: idleTitleStatusText, isRecording: self.callState?.recordingStartTimestamp != nil, + isLandscape: isLandscape, strings: environment.strings, tapAction: self.callState?.recordingStartTimestamp != nil ? { [weak self] in guard let self, let environment = self.environment, let currentCall = self.currentCall else { @@ -2146,7 +2201,12 @@ final class VideoChatScreenComponent: Component { environment: {}, containerSize: CGSize(width: maxTitleWidth, height: 100.0) ) - let titleFrame = CGRect(origin: CGPoint(x: leftInset + floor((availableSize.width - leftInset - rightInset - titleSize.width) * 0.5), y: topInset + floor((navigationBarHeight - titleSize.height) * 0.5)), size: titleSize) + var titleFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + floor((navigationBarHeight - titleSize.height) * 0.5)), size: titleSize) + if isLandscape { + titleFrame.origin.x = navigationLeftButtonFrame.maxX + 20.0 + } else { + titleFrame.origin.x = leftInset + floor((availableSize.width - leftInset - rightInset - titleSize.width) * 0.5) + } if let titleView = self.title.view { if titleView.superview == nil { self.containerView.addSubview(titleView) @@ -2155,6 +2215,27 @@ final class VideoChatScreenComponent: Component { alphaTransition.setAlpha(view: titleView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0) } + let areButtonsCollapsed: Bool + let mainColumnWidth: CGFloat + let mainColumnSideInset: CGFloat + + if isTwoColumnLayout { + areButtonsCollapsed = false + + mainColumnWidth = min(isLandscape ? 340.0 : 320.0, availableSize.width - leftInset - rightInset - 340.0) + mainColumnSideInset = 0.0 + } else { + areButtonsCollapsed = self.expandedParticipantsVideoState != nil + + if availableSize.width > maxSingleColumnWidth { + mainColumnWidth = 420.0 + mainColumnSideInset = 0.0 + } else { + mainColumnWidth = availableSize.width + mainColumnSideInset = max(leftInset, rightInset) + } + } + var encryptionKeyFrame: CGRect? var isConference = false if case let .group(groupCall) = self.currentCall { @@ -2163,7 +2244,9 @@ final class VideoChatScreenComponent: Component { isConference = true } if isConference { - navigationHeight -= 2.0 + if !isLandscape { + navigationHeight -= 2.0 + } let encryptionKey: ComponentView var encryptionKeyTransition = transition if let current = self.encryptionKey { @@ -2194,11 +2277,34 @@ final class VideoChatScreenComponent: Component { environment: {}, containerSize: CGSize(width: min(400.0, availableSize.width - leftInset - rightInset - 16.0 * 2.0), height: 10000.0) ) - let encryptionKeyFrameValue = CGRect(origin: CGPoint(x: leftInset + floor((availableSize.width - leftInset - rightInset - encryptionKeySize.width) * 0.5), y: navigationHeight), size: encryptionKeySize) + var encryptionKeyFrameValue = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: encryptionKeySize) + if isLandscape { + let maxEncryptionKeyX: CGFloat + if let navigationSidebarButtonFrame { + maxEncryptionKeyX = navigationSidebarButtonFrame.minX - 8.0 - encryptionKeySize.width + } else { + maxEncryptionKeyX = navigationRightButtonFrame.minX - 8.0 - encryptionKeySize.width + } + + let idealEncryptionKeyX: CGFloat + if isTwoColumnLayout { + idealEncryptionKeyX = availableSize.width - rightInset - mainColumnWidth + } else { + idealEncryptionKeyX = maxEncryptionKeyX - 13.0 + } + + encryptionKeyFrameValue.origin.x = min(idealEncryptionKeyX, maxEncryptionKeyX) + encryptionKeyFrameValue.origin.y = navigationLeftButtonFrame.minY + floorToScreenPixels((navigationLeftButtonFrame.height - encryptionKeySize.height) * 0.5) + } else { + encryptionKeyFrameValue.origin.x = leftInset + floor((availableSize.width - leftInset - rightInset - encryptionKeySize.width) * 0.5) + encryptionKeyFrameValue.origin.y = navigationHeight + } encryptionKeyFrame = encryptionKeyFrameValue - navigationHeight += encryptionKeySize.height - navigationHeight += 16.0 + if !isLandscape { + navigationHeight += encryptionKeySize.height + navigationHeight += 16.0 + } } else if let encryptionKey = self.encryptionKey { self.encryptionKey = nil encryptionKey.view?.removeFromSuperview() @@ -2207,27 +2313,6 @@ final class VideoChatScreenComponent: Component { self.encryptionKeyBackground = nil } - let areButtonsCollapsed: Bool - let mainColumnWidth: CGFloat - let mainColumnSideInset: CGFloat - - if isTwoColumnLayout { - areButtonsCollapsed = false - - mainColumnWidth = 320.0 - mainColumnSideInset = 0.0 - } else { - areButtonsCollapsed = self.expandedParticipantsVideoState != nil - - if availableSize.width > maxSingleColumnWidth { - mainColumnWidth = 420.0 - mainColumnSideInset = 0.0 - } else { - mainColumnWidth = availableSize.width - mainColumnSideInset = max(leftInset, rightInset) - } - } - let actionButtonDiameter: CGFloat = 56.0 let expandedMicrophoneButtonDiameter: CGFloat = actionButtonDiameter var collapsedMicrophoneButtonDiameter: CGFloat = 116.0 @@ -2285,7 +2370,7 @@ final class VideoChatScreenComponent: Component { if buttonsOnTheSide { collapsedMicrophoneButtonFrame.origin.y = floor((availableSize.height - actionButtonDiameter) * 0.5) - collapsedMicrophoneButtonFrame.origin.x = availableSize.width - environment.safeInsets.right - landscapeControlsWidth + landscapeControlsOffsetX + collapsedMicrophoneButtonFrame.origin.x = availableSize.width - landscapeControlsWidth + landscapeControlsOffsetX + floor((landscapeControlsWidth - actionButtonDiameter) * 0.5) if isMainColumnHidden { collapsedMicrophoneButtonFrame.origin.x += mainColumnWidth + landscapeControlsWidth diff --git a/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift index b1818a7e93..9736688ee3 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatTitleComponent.swift @@ -6,19 +6,22 @@ import MultilineTextComponent import TelegramPresentationData import HierarchyTrackingLayer import ChatTitleActivityNode +import AnimatedTextComponent final class VideoChatTitleComponent: Component { let title: String - let status: String + let status: [AnimatedTextComponent.Item] let isRecording: Bool + let isLandscape: Bool let strings: PresentationStrings let tapAction: (() -> Void)? let longTapAction: (() -> Void)? init( title: String, - status: String, + status: [AnimatedTextComponent.Item], isRecording: Bool, + isLandscape: Bool, strings: PresentationStrings, tapAction: (() -> Void)?, longTapAction: (() -> Void)? @@ -26,6 +29,7 @@ final class VideoChatTitleComponent: Component { self.title = title self.status = status self.isRecording = isRecording + self.isLandscape = isLandscape self.strings = strings self.tapAction = tapAction self.longTapAction = longTapAction @@ -41,6 +45,9 @@ final class VideoChatTitleComponent: Component { if lhs.isRecording != rhs.isRecording { return false } + if lhs.isLandscape != rhs.isLandscape { + return false + } if lhs.strings !== rhs.strings { return false } @@ -211,12 +218,14 @@ final class VideoChatTitleComponent: Component { ) let statusComponent: AnyComponent - statusComponent = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.status, font: Font.regular(13.0), textColor: UIColor(white: 1.0, alpha: 0.5))) + statusComponent = AnyComponent(AnimatedTextComponent( + font: Font.regular(13.0), + color: UIColor(white: 1.0, alpha: 0.5), + items: component.status )) let statusSize = self.status.update( - transition: .immediate, + transition: transition, component: statusComponent, environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -224,7 +233,10 @@ final class VideoChatTitleComponent: Component { let size = CGSize(width: availableSize.width, height: titleSize.height + spacing + statusSize.height) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: 0.0), size: titleSize) + var titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize) + if !component.isLandscape { + titleFrame.origin.x = floor((size.width - titleSize.width) * 0.5) + } if let titleView = self.title.view { if titleView.superview == nil { titleView.layer.anchorPoint = CGPoint() @@ -235,13 +247,17 @@ final class VideoChatTitleComponent: Component { titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) } - let statusFrame = CGRect(origin: CGPoint(x: floor((size.width - statusSize.width) * 0.5), y: titleFrame.maxY + spacing), size: statusSize) + var statusFrame = CGRect(origin: CGPoint(x: 0.0, y: titleFrame.maxY + spacing), size: statusSize) + if !component.isLandscape { + statusFrame.origin.x = floor((size.width - statusSize.width) * 0.5) + } if let statusView = self.status.view { if statusView.superview == nil { + statusView.layer.anchorPoint = CGPoint() statusView.isUserInteractionEnabled = false self.addSubview(statusView) } - transition.setPosition(view: statusView, position: statusFrame.center) + transition.setPosition(view: statusView, position: statusFrame.origin) statusView.bounds = CGRect(origin: CGPoint(), size: statusFrame.size) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift index 0e3b0a0126..12c2cdd65c 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift @@ -53,9 +53,6 @@ func closeButtonImage(dark: Bool) -> UIImage? { return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - context.setLineWidth(2.0) context.setLineCap(.round) context.setStrokeColor(UIColor.white.cgColor) diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift index eafde47eeb..09043395e5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift @@ -26,9 +26,10 @@ public final class MessagePriceItem: ListViewItem, ItemListItem { let price: String public let sectionId: ItemListSectionId let updated: (Int64, Bool) -> Void + let openSetCustom: (() -> Void)? let openPremiumInfo: (() -> Void)? - public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64, Bool) -> Void, openPremiumInfo: (() -> Void)? = nil) { + public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64, Bool) -> Void, openSetCustom: (() -> Void)? = nil, openPremiumInfo: (() -> Void)? = nil) { self.theme = theme self.strings = strings self.isEnabled = isEnabled @@ -38,6 +39,7 @@ public final class MessagePriceItem: ListViewItem, ItemListItem { self.price = price self.sectionId = sectionId self.updated = updated + self.openSetCustom = openSetCustom self.openPremiumInfo = openPremiumInfo } @@ -161,6 +163,8 @@ private class MessagePriceItemNode: ListViewItemNode { private var sliderView: TGPhotoEditorSliderView? private let leftTextNode: ImmediateTextNode private let rightTextNode: ImmediateTextNode + private let centerTextButtonNode: HighlightableButtonNode + private let centerTextButtonBackground: UIImageView private let centerLeftTextNode: ImmediateTextNode private let centerRightTextNode: ImmediateTextNode private let lockIconNode: ASImageNode @@ -186,8 +190,13 @@ private class MessagePriceItemNode: ListViewItemNode { self.leftTextNode = ImmediateTextNode() self.rightTextNode = ImmediateTextNode() + + self.centerTextButtonNode = HighlightableButtonNode() + self.centerTextButtonBackground = UIImageView() self.centerLeftTextNode = ImmediateTextNode() + self.centerLeftTextNode.isUserInteractionEnabled = false self.centerRightTextNode = ImmediateTextNode() + self.centerRightTextNode.isUserInteractionEnabled = false self.lockIconNode = ASImageNode() self.lockIconNode.displaysAsynchronously = false @@ -198,9 +207,13 @@ private class MessagePriceItemNode: ListViewItemNode { self.addSubnode(self.leftTextNode) self.addSubnode(self.rightTextNode) - self.addSubnode(self.centerLeftTextNode) - self.addSubnode(self.centerRightTextNode) + self.addSubnode(self.centerTextButtonNode) + self.centerTextButtonNode.view.addSubview(self.centerTextButtonBackground) + self.centerTextButtonNode.addSubnode(self.centerLeftTextNode) + self.centerTextButtonNode.addSubnode(self.centerRightTextNode) self.addSubnode(self.lockIconNode) + + self.centerTextButtonNode.addTarget(self, action: #selector(self.centerTextButtonPressed), forControlEvents: .touchUpInside) } override func didLoad() { @@ -231,7 +244,11 @@ private class MessagePriceItemNode: ListViewItemNode { sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) self.sliderView = sliderView } - + + @objc private func centerTextButtonPressed() { + self.item?.openSetCustom?() + } + func asyncLayout() -> (_ item: MessagePriceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { let currentItem = self.item @@ -312,8 +329,8 @@ private class MessagePriceItemNode: ListViewItemNode { strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) let centralLeftText = item.strings.Privacy_Messages_Stars(Int32(item.value)) - strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.theme.list.itemPrimaryTextColor) - strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.theme.list.itemSecondaryTextColor) + strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor) + strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor.withMultipliedAlpha(0.5) : item.theme.list.itemSecondaryTextColor) let leftTextSize = strongSelf.leftTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) @@ -328,10 +345,28 @@ private class MessagePriceItemNode: ListViewItemNode { let totalCenterWidth = centerLeftTextSize.width + centerSpacing + centerRightTextSize.width let centerLeftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalCenterWidth) / 2.0), y: 11.0), size: centerLeftTextSize) - strongSelf.centerLeftTextNode.frame = centerLeftFrame - let centerRightFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalCenterWidth) / 2.0) + totalCenterWidth - centerRightTextSize.width, y: 14.0 - UIScreenPixel), size: centerRightTextSize) - strongSelf.centerRightTextNode.frame = centerRightFrame + + let centerButtonFrame = CGRect(origin: CGPoint(x: centerLeftFrame.minX, y: centerLeftFrame.minY), size: CGSize(width: centerRightFrame.maxX - centerLeftFrame.minX, height: centerLeftFrame.height)).insetBy(dx: -8.0, dy: -4.0) + + strongSelf.centerTextButtonNode.frame = centerButtonFrame + + strongSelf.centerTextButtonBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: centerButtonFrame.size) + if strongSelf.centerTextButtonBackground.image == nil { + strongSelf.centerTextButtonBackground.image = generateStretchableFilledCircleImage(diameter: 16.0, color: .white)?.withRenderingMode(.alwaysTemplate) + } + strongSelf.centerTextButtonBackground.tintColor = item.theme.list.itemAccentColor.withMultipliedAlpha(0.1) + + if item.openSetCustom != nil { + strongSelf.centerTextButtonNode.isEnabled = true + strongSelf.centerTextButtonBackground.isHidden = false + } else { + strongSelf.centerTextButtonNode.isEnabled = false + strongSelf.centerTextButtonBackground.isHidden = true + } + + strongSelf.centerLeftTextNode.frame = centerLeftFrame.offsetBy(dx: -centerButtonFrame.minX, dy: -centerButtonFrame.minY) + strongSelf.centerRightTextNode.frame = centerRightFrame.offsetBy(dx: -centerButtonFrame.minX, dy: -centerButtonFrame.minY) if let sliderView = strongSelf.sliderView { if themeUpdated { @@ -343,12 +378,17 @@ private class MessagePriceItemNode: ListViewItemNode { sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0)) - sliderView.interactionEnded = { [weak self] in + sliderView.interactionEnded = { guard let self else { return } self.item?.updated(Int64(self.amount.realValue), true) } + + if !sliderView.isTracking { + strongSelf.amount = Amount(realValue: Int(item.value), maxRealValue: Int(item.maxValue), maxSliderValue: 999, isLogarithmic: true) + sliderView.value = CGFloat(strongSelf.amount.sliderValue) + } } strongSelf.lockIconNode.isHidden = item.isEnabled diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 0ea0dd5b11..3b385b4213 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -156,6 +156,15 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0) amountLabel = nil + case .paidMessages: + //TODO:localize + titleString = "Price per Message" + amountTitle = "PRICE IN STARS" + amountPlaceholder = "Enter Price" + + minAmount = StarsAmount(value: 1, nanos: 0) + maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) + amountLabel = nil } let title = title.update( @@ -280,6 +289,19 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) + case .paidMessages: + let amountInfoString: NSAttributedString + if let value = state.amount?.value, value > 0 { + let fullValue: Int64 = Int64(value) * 1_000_000_000 * 80 / 100 + let amountValue = StarsAmount(value: fullValue / 1_000_000_000, nanos: Int32(fullValue % 1_000_000_000)) + amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(amountValue) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + } else { + amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **80%**.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + } + amountFooter = AnyComponent(MultilineTextComponent( + text: .plain(amountInfoString), + maximumNumberOfLines: 0 + )) default: amountFooter = nil } @@ -340,6 +362,9 @@ private final class SheetContent: CombinedComponent { } else { buttonString = "Sell" } + } else if case .paidMessages = component.mode { + //TODO:localize + buttonString = "OK" } else if let amount = state.amount { buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" } else { @@ -432,6 +457,8 @@ private final class SheetContent: CombinedComponent { amount = nil case .starGiftResell: amount = nil + case let .paidMessages(initialValue): + amount = StarsAmount(value: initialValue, nanos: 0) } self.amount = amount @@ -553,6 +580,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { case paidMedia(Int64?) case reaction(Int64?) case starGiftResell(Bool) + case paidMessages(Int64) } private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 564282e927..d118200ca6 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3664,8 +3664,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsWithdrawScreen(context: context, mode: .withdraw(stats), completion: completion) } - public func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController { - return StarsWithdrawScreen(context: context, mode: .accountWithdraw, completion: completion) + public func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject, completion: @escaping (Int64) -> Void) -> ViewController { + let mode: StarsWithdrawScreen.Mode + switch subject { + case .withdraw: + mode = .accountWithdraw + case let .enterAmount(current): + mode = .paidMessages(current.value) + } + return StarsWithdrawScreen(context: context, mode: mode, completion: completion) } public func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController {