Merge commit '2e7d6be64ca1fd90498825a9d2edf02a34363f1e'

# Conflicts:
#	submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift
#	submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
This commit is contained in:
Isaac 2024-02-13 20:15:33 +04:00
commit 16c226c801
17 changed files with 601 additions and 110 deletions

View File

@ -10988,8 +10988,8 @@ Sorry for the inconvenience.";
"ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**."; "ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**.";
"ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features."; "ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features.";
"ChannelBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This channel reached **%@** and unlocked new features."; "ChannelBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This channel reached **Level %@** and unlocked new features.";
"GroupBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This group reached **%@** and unlocked new features."; "GroupBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This group reached **Level %@** and unlocked new features.";
"ContactList.Context.Delete" = "Delete Contact"; "ContactList.Context.Delete" = "Delete Contact";
"ContactList.Context.Select" = "Select"; "ContactList.Context.Select" = "Select";
@ -11043,7 +11043,7 @@ Sorry for the inconvenience.";
"ChannelBoost.Table.Group.Wallpaper_any" = "%@ Group Backgrounds"; "ChannelBoost.Table.Group.Wallpaper_any" = "%@ Group Backgrounds";
"ChannelBoost.Table.Group.CustomWallpaper" = "Custom Group Background"; "ChannelBoost.Table.Group.CustomWallpaper" = "Custom Group Background";
"Premium.Group.BoostByGiftDescription" = "Boost your group by gifting your subscribers Telegram Premium. [Get boosts >]()"; "Premium.Group.BoostByGiftDescription" = "Boost your group by gifting your members Telegram Premium. [Get boosts >]()";
"Conversation.BoostGroup" = "BOOST"; "Conversation.BoostGroup" = "BOOST";
@ -11303,6 +11303,7 @@ Sorry for the inconvenience.";
"Channel.AdminLog.MessageChangedGroupEmojiPack" = "%@ changed group emoji pack"; "Channel.AdminLog.MessageChangedGroupEmojiPack" = "%@ changed group emoji pack";
"Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack"; "Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack";
"Group.Appearance.EmojiPackUpdated" = "Group emoji pack updated.";
"Attachment.BoostToUnlock" = "Boost to Unlock"; "Attachment.BoostToUnlock" = "Boost to Unlock";
"Story.GroupCommentingRestrictedPlaceholder" = "Comments restricted. Boost the group to unlock.";
"Story.GroupCommentingRestrictedPlaceholderAction" = "Learn More...";

View File

@ -555,7 +555,7 @@ private final class CameraContext {
} }
} }
public func startRecording() -> Signal<CameraRecordingData, NoError> { public func startRecording() -> Signal<CameraRecordingData, CameraRecordingError> {
guard let mainDeviceContext = self.mainDeviceContext else { guard let mainDeviceContext = self.mainDeviceContext else {
return .complete() return .complete()
} }
@ -829,7 +829,7 @@ public final class Camera {
} }
} }
public func startRecording() -> Signal<CameraRecordingData, NoError> { public func startRecording() -> Signal<CameraRecordingData, CameraRecordingError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.queue.async { self.queue.async {
@ -1082,3 +1082,7 @@ public struct CameraRecordingData {
public let duration: Double public let duration: Double
public let filePath: String public let filePath: String
} }
public enum CameraRecordingError {
case audioInitializationError
}

View File

@ -83,6 +83,8 @@ final class CameraOutput: NSObject {
let colorSpace: CGColorSpace let colorSpace: CGColorSpace
let isVideoMessage: Bool let isVideoMessage: Bool
var hasAudio: Bool = false
let photoOutput = AVCapturePhotoOutput() let photoOutput = AVCapturePhotoOutput()
let videoOutput = AVCaptureVideoDataOutput() let videoOutput = AVCaptureVideoDataOutput()
let audioOutput = AVCaptureAudioDataOutput() let audioOutput = AVCaptureAudioDataOutput()
@ -141,9 +143,14 @@ final class CameraOutput: NSObject {
} else { } else {
Logger.shared.log("Camera", "Can't add video output") Logger.shared.log("Camera", "Can't add video output")
} }
if audio, session.session.canAddOutput(self.audioOutput) { if audio {
session.session.addOutput(self.audioOutput) self.hasAudio = true
self.audioOutput.setSampleBufferDelegate(self, queue: self.audioQueue) if session.session.canAddOutput(self.audioOutput) {
session.session.addOutput(self.audioOutput)
self.audioOutput.setSampleBufferDelegate(self, queue: self.audioQueue)
} else {
Logger.shared.log("Camera", "Can't add audio output")
}
} }
if photo, session.session.canAddOutput(self.photoOutput) { if photo, session.session.canAddOutput(self.photoOutput) {
if session.hasMultiCam { if session.hasMultiCam {
@ -302,7 +309,7 @@ final class CameraOutput: NSObject {
private var currentMode: RecorderMode = .default private var currentMode: RecorderMode = .default
private var recordingCompletionPipe = ValuePipe<VideoCaptureResult>() private var recordingCompletionPipe = ValuePipe<VideoCaptureResult>()
func startRecording(mode: RecorderMode, position: Camera.Position? = nil, orientation: AVCaptureVideoOrientation, additionalOutput: CameraOutput? = nil) -> Signal<CameraRecordingData, NoError> { func startRecording(mode: RecorderMode, position: Camera.Position? = nil, orientation: AVCaptureVideoOrientation, additionalOutput: CameraOutput? = nil) -> Signal<CameraRecordingData, CameraRecordingError> {
guard self.videoRecorder == nil else { guard self.videoRecorder == nil else {
return .complete() return .complete()
} }
@ -345,6 +352,10 @@ final class CameraOutput: NSObject {
} }
let audioSettings = self.audioOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mp4) ?? [:] let audioSettings = self.audioOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mp4) ?? [:]
if self.hasAudio && audioSettings.isEmpty {
Logger.shared.log("Camera", "Audio settings are empty on recording start")
return .fail(.audioInitializationError)
}
let outputFileName = NSUUID().uuidString let outputFileName = NSUUID().uuidString
let outputFilePath = NSTemporaryDirectory() + outputFileName + ".mp4" let outputFilePath = NSTemporaryDirectory() + outputFileName + ".mp4"

View File

@ -402,13 +402,15 @@ private final class SheetContent: CombinedComponent {
let mode: PremiumBoostLevelsScreen.Mode let mode: PremiumBoostLevelsScreen.Mode
let status: ChannelBoostStatus? let status: ChannelBoostStatus?
let boostState: InternalBoostState.DisplayData? let boostState: InternalBoostState.DisplayData?
let initialized: Bool
let boost: () -> Void let boost: () -> Void
let copyLink: (String) -> Void let copyLink: (String) -> Void
let dismiss: () -> Void let dismiss: () -> Void
let openStats: (() -> Void)? let openStats: (() -> Void)?
let openGift: (() -> Void)? let openGift: (() -> Void)?
let openPeer: ((EnginePeer) -> Void)? let openPeer: ((EnginePeer) -> Void)?
let updated: () -> Void
init(context: AccountContext, init(context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
@ -419,12 +421,14 @@ private final class SheetContent: CombinedComponent {
mode: PremiumBoostLevelsScreen.Mode, mode: PremiumBoostLevelsScreen.Mode,
status: ChannelBoostStatus?, status: ChannelBoostStatus?,
boostState: InternalBoostState.DisplayData?, boostState: InternalBoostState.DisplayData?,
initialized: Bool,
boost: @escaping () -> Void, boost: @escaping () -> Void,
copyLink: @escaping (String) -> Void, copyLink: @escaping (String) -> Void,
dismiss: @escaping () -> Void, dismiss: @escaping () -> Void,
openStats: (() -> Void)?, openStats: (() -> Void)?,
openGift: (() -> Void)?, openGift: (() -> Void)?,
openPeer: ((EnginePeer) -> Void)? openPeer: ((EnginePeer) -> Void)?,
updated: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
@ -435,12 +439,14 @@ private final class SheetContent: CombinedComponent {
self.mode = mode self.mode = mode
self.status = status self.status = status
self.boostState = boostState self.boostState = boostState
self.initialized = initialized
self.boost = boost self.boost = boost
self.copyLink = copyLink self.copyLink = copyLink
self.dismiss = dismiss self.dismiss = dismiss
self.openStats = openStats self.openStats = openStats
self.openGift = openGift self.openGift = openGift
self.openPeer = openPeer self.openPeer = openPeer
self.updated = updated
} }
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
@ -468,6 +474,9 @@ private final class SheetContent: CombinedComponent {
if lhs.boostState != rhs.boostState { if lhs.boostState != rhs.boostState {
return false return false
} }
if lhs.initialized != rhs.initialized {
return false
}
return true return true
} }
@ -479,35 +488,33 @@ private final class SheetContent: CombinedComponent {
private(set) var memberPeer: EnginePeer? private(set) var memberPeer: EnginePeer?
private var disposable: Disposable? private var disposable: Disposable?
private var memberDisposable: Disposable?
init(context: AccountContext, peerId: EnginePeer.Id, userId: EnginePeer.Id?) { init(context: AccountContext, peerId: EnginePeer.Id, userId: EnginePeer.Id?, updated: @escaping () -> Void) {
super.init() super.init()
self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) var peerIds: [EnginePeer.Id] = [peerId]
|> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let userId {
peerIds.append(userId)
}
self.disposable = (context.engine.data.get(
EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
) |> deliverOnMainQueue).startStrict(next: { [weak self] peers in
guard let self else { guard let self else {
return return
} }
self.peer = peer if let maybePeer = peers[peerId] {
self.updated() self.peer = maybePeer
}
if let userId, let maybePeer = peers[userId] {
self.memberPeer = maybePeer
}
updated()
}) })
if let userId {
self.memberDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: userId))
|> deliverOnMainQueue).startStrict(next: { [weak self] peer in
guard let self else {
return
}
self.memberPeer = peer
self.updated()
})
}
} }
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
self.memberDisposable?.dispose()
} }
} }
@ -516,7 +523,7 @@ private final class SheetContent: CombinedComponent {
if case let .user(mode) = mode, case let .groupPeer(peerId, _) = mode { if case let .user(mode) = mode, case let .groupPeer(peerId, _) = mode {
userId = peerId userId = peerId
} }
return State(context: self.context, peerId: self.peerId, userId: userId) return State(context: self.context, peerId: self.peerId, userId: userId, updated: self.updated)
} }
static var body: Body { static var body: Body {
@ -636,7 +643,11 @@ private final class SheetContent: CombinedComponent {
} else { } else {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
if myBoostCount > 0 { if myBoostCount > 0 {
textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string if remaining == 0 {
textString = isGroup ? strings.GroupBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level + 1)").string : strings.ChannelBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level + 1)").string
} else {
textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string
}
} else { } else {
textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string textString = strings.ChannelBoost_MoreBoostsNeeded_Text(peerName, boostsString).string
} }
@ -674,7 +685,7 @@ private final class SheetContent: CombinedComponent {
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining)) let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
if myBoostCount > 0 { if myBoostCount > 0 {
if remaining == 0 { if remaining == 0 {
textString = isGroup ? strings.GroupBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level)").string : strings.ChannelBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level)").string textString = isGroup ? strings.GroupBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level + 1)").string : strings.ChannelBoost_MoreBoostsNeeded_Boosted_Level_Text("\(level + 1)").string
} else { } else {
textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string
} }
@ -1267,6 +1278,8 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
var cachedStatsImage: (UIImage, PresentationTheme)? var cachedStatsImage: (UIImage, PresentationTheme)?
var cachedCloseImage: (UIImage, PresentationTheme)? var cachedCloseImage: (UIImage, PresentationTheme)?
var initialized = false
private var disposable: Disposable? private var disposable: Disposable?
private(set) var peer: EnginePeer? private(set) var peer: EnginePeer?
@ -1279,7 +1292,6 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
return return
} }
self.peer = peer self.peer = peer
self.updated()
updated() updated()
}) })
} }
@ -1325,6 +1337,7 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
if let isGroup { if let isGroup {
component.externalState.isGroup = isGroup component.externalState.isGroup = isGroup
let updated = component.updated
let scroll = scroll.update( let scroll = scroll.update(
component: ScrollComponent<Empty>( component: ScrollComponent<Empty>(
content: AnyComponent( content: AnyComponent(
@ -1338,12 +1351,17 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
mode: component.mode, mode: component.mode,
status: component.status, status: component.status,
boostState: component.boostState, boostState: component.boostState,
initialized: state.initialized,
boost: component.boost, boost: component.boost,
copyLink: component.copyLink, copyLink: component.copyLink,
dismiss: component.dismiss, dismiss: component.dismiss,
openStats: component.openStats, openStats: component.openStats,
openGift: component.openGift, openGift: component.openGift,
openPeer: component.openPeer openPeer: component.openPeer,
updated: { [weak state] in
state?.initialized = true
updated()
}
) )
), ),
externalState: externalScrollState, externalState: externalScrollState,
@ -1677,7 +1695,7 @@ public class PremiumBoostLevelsScreen: ViewController {
otherText = presentationData.strings.ReassignBoost_OtherGroupsAndChannels(Int32(sourcePeers.count)) otherText = presentationData.strings.ReassignBoost_OtherGroupsAndChannels(Int32(sourcePeers.count))
} }
let text = presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), otherText).string let text = presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), otherText).string
let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostReplaceIcon"), color: .white)!, title: nil, text: text, round: false, undoText: nil), elevatedLayout: false, position: .top, action: { _ in return true }) let undoController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "BoostReplace", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil, timeout: 4.0), elevatedLayout: false, position: .top, action: { _ in return true })
controller.present(undoController, in: .current) controller.present(undoController, in: .current)
} }
} }
@ -1757,9 +1775,16 @@ public class PremiumBoostLevelsScreen: ViewController {
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
} }
func requestLayout(transition: Transition) {
guard let layout = self.currentLayout else {
return
}
self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition)
}
private var dismissOffset: CGFloat? private var dismissOffset: CGFloat?
func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) { func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, transition: Transition) {
guard !self.isDismissing else { guard !self.isDismissing else {
return return
} }
@ -1824,7 +1849,7 @@ public class PremiumBoostLevelsScreen: ViewController {
effectiveExpanded = true effectiveExpanded = true
} }
self.updated(transition: transition) self.updated(transition: transition, forceUpdate: forceUpdate)
let contentHeight = self.containerExternalState.contentHeight let contentHeight = self.containerExternalState.contentHeight
if contentHeight > 0.0 && contentHeight < 400.0, let view = self.footerView.componentView as? FooterComponent.View { if contentHeight > 0.0 && contentHeight < 400.0, let view = self.footerView.componentView as? FooterComponent.View {
@ -1856,7 +1881,7 @@ public class PremiumBoostLevelsScreen: ViewController {
} }
private var boostState: InternalBoostState.DisplayData? private var boostState: InternalBoostState.DisplayData?
func updated(transition: Transition) { func updated(transition: Transition, forceUpdate: Bool = false) {
guard let controller = self.controller else { guard let controller = self.controller else {
return return
} }
@ -1895,11 +1920,12 @@ public class PremiumBoostLevelsScreen: ViewController {
openGift: controller.openGift, openGift: controller.openGift,
openPeer: controller.openPeer, openPeer: controller.openPeer,
updated: { [weak self] in updated: { [weak self] in
self?.controller?.requestLayout(transition: .immediate) self?.requestLayout(transition: .immediate)
} }
) )
), ),
environment: {}, environment: {},
forceUpdate: forceUpdate,
containerSize: self.containerView.bounds.size containerSize: self.containerView.bounds.size
) )
self.contentView.frame = CGRect(origin: .zero, size: contentSize) self.contentView.frame = CGRect(origin: .zero, size: contentSize)
@ -2200,6 +2226,7 @@ public class PremiumBoostLevelsScreen: ViewController {
let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0 let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0
let presentationData = self.presentationData let presentationData = self.presentationData
let forceDark = controller.forceDark let forceDark = controller.forceDark
let boostStatusUpdated = controller.boostStatusUpdated
if let _ = status?.nextLevelBoosts { if let _ = status?.nextLevelBoosts {
if let availableBoost = self.availableBoosts.first { if let availableBoost = self.availableBoosts.first {
@ -2266,15 +2293,23 @@ public class PremiumBoostLevelsScreen: ViewController {
).startStandalone(next: { boostStatus, myBoostStatus in ).startStandalone(next: { boostStatus, myBoostStatus in
dismissReplaceImpl?() dismissReplaceImpl?()
if let boostStatus {
boostStatusUpdated(boostStatus)
}
let levelsController = PremiumBoostLevelsScreen( let levelsController = PremiumBoostLevelsScreen(
context: context, context: context,
peerId: peerId, peerId: peerId,
mode: mode, mode: mode,
status: status, status: boostStatus,
myBoostStatus: myBoostStatus, myBoostStatus: myBoostStatus,
replacedBoosts: (Int32(slots.count), sourcePeers), replacedBoosts: (Int32(slots.count), sourcePeers),
openStats: nil, openGift: nil, openPeer: openPeer, forceDark: forceDark openStats: nil,
openGift: nil,
openPeer: openPeer,
forceDark: forceDark
) )
levelsController.boostStatusUpdated = boostStatusUpdated
if let navigationController { if let navigationController {
navigationController.pushViewController(levelsController, animated: true) navigationController.pushViewController(levelsController, animated: true)
} }

View File

@ -1519,7 +1519,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
peerId: peerId, peerId: peerId,
mode: .owner(subject: nil), mode: .owner(subject: nil),
status: boostStatus, status: boostStatus,
myBoostStatus: myBoostStatus myBoostStatus: myBoostStatus,
openGift: { [weak controller] in
let giveawayController = createGiveawayController(context: context, peerId: peerId, subject: .generic)
controller?.push(giveawayController)
}
) )
boostController.boostStatusUpdated = { boostStatus in boostController.boostStatusUpdated = { boostStatus in
boostDataPromise.set(.single(boostStatus)) boostDataPromise.set(.single(boostStatus))

View File

@ -879,6 +879,62 @@ public extension TelegramEngine.EngineData.Item {
} }
} }
public struct BoostsToUnrestrict: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Int32?
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.boostsToUnrestrict
} else {
return nil
}
}
}
public struct AppliedBoosts: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Int32?
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.appliedBoosts
} else {
return nil
}
}
}
public struct MessageReadStatsAreHidden: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public struct MessageReadStatsAreHidden: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool? public typealias Result = Bool?
@ -1057,6 +1113,91 @@ public extension TelegramEngine.EngineData.Item {
} }
} }
public struct SlowmodeTimeout: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Int32?
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.slowModeTimeout
} else {
return nil
}
}
}
public struct SlowmodeValidUntilTimeout: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Int32?
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.slowModeValidUntilTimestamp
}
return nil
}
}
public struct CanAvoidGroupRestrictions: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
if let boostsToUnrestrict = cachedData.boostsToUnrestrict {
let appliedBoosts = cachedData.appliedBoosts ?? 0
return boostsToUnrestrict <= appliedBoosts
}
}
return true
}
}
public struct IsPremiumRequiredForMessaging: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, AnyPostboxViewDataItem { public struct IsPremiumRequiredForMessaging: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, AnyPostboxViewDataItem {
public typealias Result = Bool public typealias Result = Bool

View File

@ -391,6 +391,186 @@ public extension TelegramEngine {
} }
} }
public func subscribe<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem,
T3: TelegramEngineDataItem,
T4: TelegramEngineDataItem,
T5: TelegramEngineDataItem,
T6: TelegramEngineDataItem,
T7: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2,
_ t3: T3,
_ t4: T4,
_ t5: T5,
_ t6: T6,
_ t7: T7
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result,
T3.Result,
T4.Result,
T5.Result,
T6.Result,
T7.Result
),
NoError> {
return self._subscribe(items: [
t0 as! AnyPostboxViewDataItem,
t1 as! AnyPostboxViewDataItem,
t2 as! AnyPostboxViewDataItem,
t3 as! AnyPostboxViewDataItem,
t4 as! AnyPostboxViewDataItem,
t5 as! AnyPostboxViewDataItem,
t6 as! AnyPostboxViewDataItem,
t7 as! AnyPostboxViewDataItem
])
|> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result, T4.Result, T5.Result, T6.Result, T7.Result) in
return (
results[0] as! T0.Result,
results[1] as! T1.Result,
results[2] as! T2.Result,
results[3] as! T3.Result,
results[4] as! T4.Result,
results[5] as! T5.Result,
results[6] as! T6.Result,
results[7] as! T7.Result
)
}
}
public func subscribe<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem,
T3: TelegramEngineDataItem,
T4: TelegramEngineDataItem,
T5: TelegramEngineDataItem,
T6: TelegramEngineDataItem,
T7: TelegramEngineDataItem,
T8: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2,
_ t3: T3,
_ t4: T4,
_ t5: T5,
_ t6: T6,
_ t7: T7,
_ t8: T8
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result,
T3.Result,
T4.Result,
T5.Result,
T6.Result,
T7.Result,
T8.Result
),
NoError> {
return self._subscribe(items: [
t0 as! AnyPostboxViewDataItem,
t1 as! AnyPostboxViewDataItem,
t2 as! AnyPostboxViewDataItem,
t3 as! AnyPostboxViewDataItem,
t4 as! AnyPostboxViewDataItem,
t5 as! AnyPostboxViewDataItem,
t6 as! AnyPostboxViewDataItem,
t7 as! AnyPostboxViewDataItem,
t8 as! AnyPostboxViewDataItem
])
|> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result, T4.Result, T5.Result, T6.Result, T7.Result, T8.Result) in
return (
results[0] as! T0.Result,
results[1] as! T1.Result,
results[2] as! T2.Result,
results[3] as! T3.Result,
results[4] as! T4.Result,
results[5] as! T5.Result,
results[6] as! T6.Result,
results[7] as! T7.Result,
results[8] as! T8.Result
)
}
}
public func subscribe<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem,
T3: TelegramEngineDataItem,
T4: TelegramEngineDataItem,
T5: TelegramEngineDataItem,
T6: TelegramEngineDataItem,
T7: TelegramEngineDataItem,
T8: TelegramEngineDataItem,
T9: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2,
_ t3: T3,
_ t4: T4,
_ t5: T5,
_ t6: T6,
_ t7: T7,
_ t8: T8,
_ t9: T9
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result,
T3.Result,
T4.Result,
T5.Result,
T6.Result,
T7.Result,
T8.Result,
T9.Result
),
NoError> {
return self._subscribe(items: [
t0 as! AnyPostboxViewDataItem,
t1 as! AnyPostboxViewDataItem,
t2 as! AnyPostboxViewDataItem,
t3 as! AnyPostboxViewDataItem,
t4 as! AnyPostboxViewDataItem,
t5 as! AnyPostboxViewDataItem,
t6 as! AnyPostboxViewDataItem,
t7 as! AnyPostboxViewDataItem,
t8 as! AnyPostboxViewDataItem,
t9 as! AnyPostboxViewDataItem
])
|> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result, T4.Result, T5.Result, T6.Result, T7.Result, T8.Result, T9.Result) in
return (
results[0] as! T0.Result,
results[1] as! T1.Result,
results[2] as! T2.Result,
results[3] as! T3.Result,
results[4] as! T4.Result,
results[5] as! T5.Result,
results[6] as! T6.Result,
results[7] as! T7.Result,
results[8] as! T8.Result,
results[9] as! T9.Result
)
}
}
public func get< public func get<
T0: TelegramEngineDataItem, T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem T1: TelegramEngineDataItem

View File

@ -378,7 +378,7 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil
} }
case .story: case .story:
return .story return .story
case .giveaway: case .giveaway, .giveawayResults:
return .giveaway return .giveaway
case let .webpage(webpage): case let .webpage(webpage):
if let message, message.text.isEmpty, case let .Loaded(content) = webpage.content { if let message, message.text.isEmpty, case let .Loaded(content) = webpage.content {

View File

@ -2184,19 +2184,21 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let attributedString: NSAttributedString let attributedString: NSAttributedString
var adminBadgeString: NSAttributedString? var adminBadgeString: NSAttributedString?
var boostBadgeString: NSAttributedString? var boostBadgeString: NSAttributedString?
if let authorRank = authorRank { if incoming {
let string: String if let authorRank = authorRank {
switch authorRank { let string: String
switch authorRank {
case .owner: case .owner:
string = item.presentationData.strings.Conversation_Owner string = item.presentationData.strings.Conversation_Owner
case .admin: case .admin:
string = item.presentationData.strings.Conversation_Admin string = item.presentationData.strings.Conversation_Admin
case let .custom(rank): case let .custom(rank):
string = rank.trimmingEmojis string = rank.trimmingEmojis
}
adminBadgeString = NSAttributedString(string: " \(string)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor)
} else if authorIsChannel, case .peer = item.chatLocation {
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Channel_Status)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor)
} }
adminBadgeString = NSAttributedString(string: " \(string)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor)
} else if authorIsChannel, case .peer = item.chatLocation {
adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Channel_Status)", font: inlineBotPrefixFont, textColor: messageTheme.secondaryTextColor)
} }
var viaSuffix: NSAttributedString? var viaSuffix: NSAttributedString?
@ -2240,9 +2242,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
var boostCount: Int = 0 var boostCount: Int = 0
for attribute in item.message.attributes { if incoming {
if let attribute = attribute as? BoostCountMessageAttribute { for attribute in item.message.attributes {
boostCount = attribute.count if let attribute = attribute as? BoostCountMessageAttribute {
boostCount = attribute.count
}
} }
} }
@ -3181,9 +3185,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
var boostCount: Int = 0 var boostCount: Int = 0
for attribute in item.message.attributes { if incoming {
if let attribute = attribute as? BoostCountMessageAttribute { for attribute in item.message.attributes {
boostCount = attribute.count if let attribute = attribute as? BoostCountMessageAttribute {
boostCount = attribute.count
}
} }
} }

View File

@ -433,7 +433,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
} else if let giveawayResults { } else if let giveawayResults {
dateTextString = NSAttributedString(string: giveawayResults.winnersCount > 1 ? item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Many : item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_One, font: textFont, textColor: textColor) dateTextString = NSAttributedString(string: giveawayResults.winnersCount > 1 ? item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Many : item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_One, font: textFont, textColor: textColor)
} }
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, hidesHeaders: true) let hideHeaders = item.message.forwardInfo == nil
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: hideHeaders, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, hidesHeaders: hideHeaders)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let sideInsets = layoutConstants.text.bubbleInsets.right * 2.0 let sideInsets = layoutConstants.text.bubbleInsets.right * 2.0

View File

@ -350,11 +350,6 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
if let completion { if let completion {
completionImpl = { value in completionImpl = { value in
completion(value) completion(value)
if let _ = value {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: presentationData.strings.Group_Appearance_EmojiPackUpdated, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in return true })
presentControllerImpl?(controller, nil)
}
} }
} }

View File

@ -92,10 +92,12 @@ public final class MessageInputPanelComponent: Component {
enum Kind { enum Kind {
case text case text
case premiumRequired case premiumRequired
case boostRequired
} }
case text(String) case text(String)
case premiumRequired(title: String, subtitle: String, action: () -> Void) case premiumRequired(title: String, subtitle: String, action: () -> Void)
case boostRequired(title: String, subtitle: String, action: () -> Void)
var kind: Kind { var kind: Kind {
switch self { switch self {
@ -103,6 +105,8 @@ public final class MessageInputPanelComponent: Component {
return .text return .text
case .premiumRequired: case .premiumRequired:
return .premiumRequired return .premiumRequired
case .boostRequired:
return .boostRequired
} }
} }
@ -120,6 +124,12 @@ public final class MessageInputPanelComponent: Component {
} else { } else {
return false return false
} }
case let .boostRequired(title, subtitle, _):
if case .boostRequired(title, subtitle, _) = rhs {
return true
} else {
return false
}
} }
} }
} }
@ -1093,18 +1103,19 @@ public final class MessageInputPanelComponent: Component {
contents = AnyComponent(MultilineTextComponent( contents = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3))) text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3)))
)) ))
case let .premiumRequired(title, subtitle, action): case let .premiumRequired(title, subtitle, action), let .boostRequired(title, subtitle, action):
leftAlignment = true leftAlignment = true
let text = NSMutableAttributedString(attributedString: NSAttributedString())
text.append(NSAttributedString(string: "\(title) ", font: Font.regular(13.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3)))
text.append(NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor))
contents = AnyComponent(PlainButtonComponent( contents = AnyComponent(PlainButtonComponent(
content: AnyComponent(VStack([ content: AnyComponent(MultilineTextComponent(
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( text: .plain(text),
text: .plain(NSAttributedString(string: title, font: Font.regular(13.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3))), maximumNumberOfLines: 0,
maximumNumberOfLines: 2 lineSpacing: 0.1
))), )),
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor))
)))
], alignment: .left, spacing: 1.0)),
effectAlignment: .center, effectAlignment: .center,
action: { action: {
action() action()

View File

@ -216,7 +216,9 @@ public final class StoryContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: false, canViewStats: false,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
) )
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -225,7 +227,9 @@ public final class StoryContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: cachedChannelData.flags.contains(.canViewStats), canViewStats: cachedChannelData.flags.contains(.canViewStats),
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
appliedBoosts: cachedChannelData.appliedBoosts
) )
} else { } else {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -234,18 +238,21 @@ public final class StoryContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: false, canViewStats: false,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
) )
} }
} } else {
else {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: true, isMuted: true,
areVoiceMessagesAvailable: true, areVoiceMessagesAvailable: true,
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: false, canViewStats: false,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
) )
} }
let state = stateView.value?.get(Stories.PeerState.self) let state = stateView.value?.get(Stories.PeerState.self)
@ -1165,7 +1172,9 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.CanViewStats(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: storyId.peerId) TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: storyId.peerId)
), ),
item |> mapToSignal { item -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in item |> mapToSignal { item -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in
return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in
@ -1236,7 +1245,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
return return
} }
let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging) = data let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data
let (item, peers, allEntityFiles, forwardInfoStories) = itemAndPeers let (item, peers, allEntityFiles, forwardInfoStories) = itemAndPeers
guard let peer else { guard let peer else {
@ -1251,7 +1260,9 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
presence: presence, presence: presence,
canViewStats: canViewStats, canViewStats: canViewStats,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts
) )
for (storyId, story) in forwardInfoStories { for (storyId, story) in forwardInfoStories {
@ -1447,7 +1458,9 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId), TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId) TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId),
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: peerId),
TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId)
), ),
listContext.state, listContext.state,
self.focusedIdUpdated.get(), self.focusedIdUpdated.get(),
@ -1458,7 +1471,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
return return
} }
let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging) = data let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data
guard let peer else { guard let peer else {
return return
@ -1472,7 +1485,9 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
presence: presence, presence: presence,
canViewStats: canViewStats, canViewStats: canViewStats,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts
) )
self.listState = state self.listState = state
@ -2389,7 +2404,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: false, canViewStats: false,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
) )
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -2398,7 +2415,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: cachedChannelData.flags.contains(.canViewStats), canViewStats: cachedChannelData.flags.contains(.canViewStats),
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
appliedBoosts: cachedChannelData.appliedBoosts
) )
} else { } else {
additionalPeerData = StoryContentContextState.AdditionalPeerData( additionalPeerData = StoryContentContextState.AdditionalPeerData(
@ -2407,7 +2426,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: false, canViewStats: false,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
) )
} }
} }
@ -2418,7 +2439,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
presence: peerPresence.flatMap { EnginePeer.Presence($0) }, presence: peerPresence.flatMap { EnginePeer.Presence($0) },
canViewStats: false, canViewStats: false,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: nil,
appliedBoosts: nil
) )
} }

View File

@ -147,6 +147,8 @@ public final class StoryContentContextState {
public let canViewStats: Bool public let canViewStats: Bool
public let isPremiumRequiredForMessaging: Bool public let isPremiumRequiredForMessaging: Bool
public let preferHighQualityStories: Bool public let preferHighQualityStories: Bool
public let boostsToUnrestrict: Int32?
public let appliedBoosts: Int32?
public init( public init(
isMuted: Bool, isMuted: Bool,
@ -154,7 +156,9 @@ public final class StoryContentContextState {
presence: EnginePeer.Presence?, presence: EnginePeer.Presence?,
canViewStats: Bool, canViewStats: Bool,
isPremiumRequiredForMessaging: Bool, isPremiumRequiredForMessaging: Bool,
preferHighQualityStories: Bool preferHighQualityStories: Bool,
boostsToUnrestrict: Int32?,
appliedBoosts: Int32?
) { ) {
self.isMuted = isMuted self.isMuted = isMuted
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
@ -162,6 +166,8 @@ public final class StoryContentContextState {
self.canViewStats = canViewStats self.canViewStats = canViewStats
self.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging self.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging
self.preferHighQualityStories = preferHighQualityStories self.preferHighQualityStories = preferHighQualityStories
self.boostsToUnrestrict = boostsToUnrestrict
self.appliedBoosts = appliedBoosts
} }
public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool { public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool {
@ -183,6 +189,12 @@ public final class StoryContentContextState {
if lhs.preferHighQualityStories != rhs.preferHighQualityStories { if lhs.preferHighQualityStories != rhs.preferHighQualityStories {
return false return false
} }
if lhs.boostsToUnrestrict != rhs.boostsToUnrestrict {
return false
}
if lhs.appliedBoosts != rhs.appliedBoosts {
return false
}
return true return true
} }
} }

View File

@ -1668,7 +1668,13 @@ public final class StoryItemSetContainerComponent: Component {
case .broadcast: case .broadcast:
displayFooter = true displayFooter = true
case .group: case .group:
displayFooter = false var canBypassRestrictions = false
if let appliedBoosts = component.slice.additionalPeerData.appliedBoosts, let boostsToUnrestrict = component.slice.additionalPeerData.boostsToUnrestrict {
canBypassRestrictions = appliedBoosts >= boostsToUnrestrict
}
if let bannedSendText = channel.hasBannedPermission(.banSendText, ignoreDefault: canBypassRestrictions), bannedSendText.1 || component.slice.additionalPeerData.boostsToUnrestrict == nil {
displayFooter = true
}
} }
} else if component.slice.peer.id == component.context.account.peerId { } else if component.slice.peer.id == component.context.account.peerId {
displayFooter = true displayFooter = true
@ -2744,9 +2750,42 @@ public final class StoryItemSetContainerComponent: Component {
inputPanelTransition = .immediate inputPanelTransition = .immediate
} }
var canBypassRestrictions = false
if let appliedBoosts = component.slice.additionalPeerData.appliedBoosts, let boostsToUnrestrict = component.slice.additionalPeerData.boostsToUnrestrict {
canBypassRestrictions = appliedBoosts >= boostsToUnrestrict
}
var isChannel = false
var isGroup = false
var showMessageInputPanel = true
var isGroupCommentRestricted = false
if case let .channel(channel) = component.slice.peer {
switch channel.info {
case .broadcast:
isChannel = true
showMessageInputPanel = false
case .group:
if let bannedSendText = channel.hasBannedPermission(.banSendText, ignoreDefault: canBypassRestrictions) {
if bannedSendText.1 || component.slice.additionalPeerData.boostsToUnrestrict == nil {
showMessageInputPanel = false
} else {
isGroupCommentRestricted = true
}
}
isGroup = true
}
} else {
showMessageInputPanel = component.slice.peer.id != component.context.account.peerId
}
var isUnsupported = false var isUnsupported = false
var disabledPlaceholder: MessageInputPanelComponent.DisabledPlaceholder? var disabledPlaceholder: MessageInputPanelComponent.DisabledPlaceholder?
if component.slice.additionalPeerData.isPremiumRequiredForMessaging {
if isGroupCommentRestricted {
disabledPlaceholder = .boostRequired(title: component.strings.Story_GroupCommentingRestrictedPlaceholder, subtitle: component.strings.Story_GroupCommentingRestrictedPlaceholderAction, action: { [weak self] in
self?.presentBoostToUnrestrict()
})
} else if component.slice.additionalPeerData.isPremiumRequiredForMessaging {
disabledPlaceholder = .premiumRequired(title: component.strings.Story_MessagingRestrictedPlaceholder(component.slice.peer.compactDisplayTitle).string, subtitle: component.strings.Story_MessagingRestrictedPlaceholderAction, action: { [weak self] in disabledPlaceholder = .premiumRequired(title: component.strings.Story_MessagingRestrictedPlaceholder(component.slice.peer.compactDisplayTitle).string, subtitle: component.strings.Story_MessagingRestrictedPlaceholderAction, action: { [weak self] in
self?.presentPremiumRequiredForMessaging() self?.presentPremiumRequiredForMessaging()
}) })
@ -2757,21 +2796,6 @@ public final class StoryItemSetContainerComponent: Component {
disabledPlaceholder = .text(component.strings.Story_FooterReplyUnavailable) disabledPlaceholder = .text(component.strings.Story_FooterReplyUnavailable)
} }
var isChannel = false
var isGroup = false
var showMessageInputPanel = true
if case let .channel(channel) = component.slice.peer {
switch channel.info {
case .broadcast:
isChannel = true
showMessageInputPanel = false
case .group:
isGroup = true
}
} else {
showMessageInputPanel = component.slice.peer.id != component.context.account.peerId
}
let inputPlaceholder: MessageInputPanelComponent.Placeholder let inputPlaceholder: MessageInputPanelComponent.Placeholder
if let stealthModeTimeout = component.stealthModeTimeout { if let stealthModeTimeout = component.stealthModeTimeout {
let minutes = Int(stealthModeTimeout / 60) let minutes = Int(stealthModeTimeout / 60)
@ -5673,7 +5697,7 @@ public final class StoryItemSetContainerComponent: Component {
let controller = PremiumIntroScreen(context: component.context, source: .settings, forceDark: true) let controller = PremiumIntroScreen(context: component.context, source: .settings, forceDark: true)
self.sendMessageContext.actionSheet = controller self.sendMessageContext.actionSheet = controller
controller.wasDismissed = { [weak self, weak controller]in controller.wasDismissed = { [weak self, weak controller] in
guard let self else { guard let self else {
return return
} }
@ -5688,6 +5712,45 @@ public final class StoryItemSetContainerComponent: Component {
component.controller()?.push(controller) component.controller()?.push(controller)
} }
private func presentBoostToUnrestrict() {
guard let component = self.component, let boostsToUnrestrict = component.slice.additionalPeerData.boostsToUnrestrict else {
return
}
HapticFeedback().impact()
let _ = combineLatest(queue: Queue.mainQueue(),
component.context.engine.peers.getChannelBoostStatus(peerId: component.slice.peer.id),
component.context.engine.peers.getMyBoostStatus()
).startStandalone(next: { [weak self] boostStatus, myBoostStatus in
guard let self, let component = self.component, let boostStatus, let myBoostStatus else {
return
}
let boostController = PremiumBoostLevelsScreen(
context: component.context,
peerId: component.slice.peer.id,
mode: .user(mode: .unrestrict(Int(boostsToUnrestrict))),
status: boostStatus,
myBoostStatus: myBoostStatus,
forceDark: true
)
boostController.disposed = { [weak self, weak boostController] in
guard let self else {
return
}
if self.sendMessageContext.actionSheet === boostController {
self.sendMessageContext.actionSheet = nil
}
self.updateIsProgressPaused()
}
self.sendMessageContext.actionSheet = boostController
self.updateIsProgressPaused()
component.controller()?.push(boostController)
})
}
private func presentStoriesUpgradeScreen(source: PremiumSource) { private func presentStoriesUpgradeScreen(source: PremiumSource) {
guard let component = self.component else { guard let component = self.component else {
return return

View File

@ -272,6 +272,10 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
controller.onStop() controller.onStop()
} }
} }
}, error: { [weak self] _ in
if let self, let controller = self.getController() {
controller.completion(nil, nil, nil)
}
})) }))
} }
@ -1686,7 +1690,7 @@ public class VideoMessageCameraScreen: ViewController {
self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: audioSessionType, activate: { [weak self] _ in self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: audioSessionType, activate: { [weak self] _ in
if let self { if let self {
Queue.mainQueue().async { Queue.mainQueue().after(0.05) {
self.node.setupCamera() self.node.setupCamera()
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"app": "10.7.2", "app": "10.8",
"bazel": "7.0.2", "bazel": "7.0.2",
"xcode": "15.2", "xcode": "15.2",
"macos": "13.0" "macos": "13.0"