mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Merge commit '2e7d6be64ca1fd90498825a9d2edf02a34363f1e'
# Conflicts: # submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift # submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
This commit is contained in:
commit
16c226c801
@ -10988,8 +10988,8 @@ Sorry for the inconvenience.";
|
||||
"ChannelBoost.MaxLevelReached.Text" = "**%1$@** reached **Level %2$@**.";
|
||||
|
||||
"ChannelBoost.MoreBoostsNeeded.Boosted.Text" = "%@ needed to unlock new features.";
|
||||
"ChannelBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This channel reached **%@** and unlocked new features.";
|
||||
"GroupBoost.MoreBoostsNeeded.Boosted.Level.Text" = "This group 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 **Level %@** and unlocked new features.";
|
||||
|
||||
"ContactList.Context.Delete" = "Delete Contact";
|
||||
"ContactList.Context.Select" = "Select";
|
||||
@ -11043,7 +11043,7 @@ Sorry for the inconvenience.";
|
||||
"ChannelBoost.Table.Group.Wallpaper_any" = "%@ Group Backgrounds";
|
||||
"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";
|
||||
|
||||
@ -11303,6 +11303,7 @@ Sorry for the inconvenience.";
|
||||
"Channel.AdminLog.MessageChangedGroupEmojiPack" = "%@ changed group emoji pack";
|
||||
"Channel.AdminLog.MessageRemovedGroupEmojiPack" = "%@ removed group emoji pack";
|
||||
|
||||
"Group.Appearance.EmojiPackUpdated" = "Group emoji pack updated.";
|
||||
|
||||
"Attachment.BoostToUnlock" = "Boost to Unlock";
|
||||
|
||||
"Story.GroupCommentingRestrictedPlaceholder" = "Comments restricted. Boost the group to unlock.";
|
||||
"Story.GroupCommentingRestrictedPlaceholderAction" = "Learn More...";
|
||||
|
@ -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 {
|
||||
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
|
||||
let disposable = MetaDisposable()
|
||||
self.queue.async {
|
||||
@ -1082,3 +1082,7 @@ public struct CameraRecordingData {
|
||||
public let duration: Double
|
||||
public let filePath: String
|
||||
}
|
||||
|
||||
public enum CameraRecordingError {
|
||||
case audioInitializationError
|
||||
}
|
||||
|
@ -83,6 +83,8 @@ final class CameraOutput: NSObject {
|
||||
let colorSpace: CGColorSpace
|
||||
let isVideoMessage: Bool
|
||||
|
||||
var hasAudio: Bool = false
|
||||
|
||||
let photoOutput = AVCapturePhotoOutput()
|
||||
let videoOutput = AVCaptureVideoDataOutput()
|
||||
let audioOutput = AVCaptureAudioDataOutput()
|
||||
@ -141,9 +143,14 @@ final class CameraOutput: NSObject {
|
||||
} else {
|
||||
Logger.shared.log("Camera", "Can't add video output")
|
||||
}
|
||||
if audio, session.session.canAddOutput(self.audioOutput) {
|
||||
session.session.addOutput(self.audioOutput)
|
||||
self.audioOutput.setSampleBufferDelegate(self, queue: self.audioQueue)
|
||||
if audio {
|
||||
self.hasAudio = true
|
||||
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 session.hasMultiCam {
|
||||
@ -302,7 +309,7 @@ final class CameraOutput: NSObject {
|
||||
|
||||
private var currentMode: RecorderMode = .default
|
||||
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 {
|
||||
return .complete()
|
||||
}
|
||||
@ -345,6 +352,10 @@ final class CameraOutput: NSObject {
|
||||
}
|
||||
|
||||
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 outputFilePath = NSTemporaryDirectory() + outputFileName + ".mp4"
|
||||
|
@ -402,13 +402,15 @@ private final class SheetContent: CombinedComponent {
|
||||
let mode: PremiumBoostLevelsScreen.Mode
|
||||
let status: ChannelBoostStatus?
|
||||
let boostState: InternalBoostState.DisplayData?
|
||||
|
||||
let initialized: Bool
|
||||
|
||||
let boost: () -> Void
|
||||
let copyLink: (String) -> Void
|
||||
let dismiss: () -> Void
|
||||
let openStats: (() -> Void)?
|
||||
let openGift: (() -> Void)?
|
||||
let openPeer: ((EnginePeer) -> Void)?
|
||||
let updated: () -> Void
|
||||
|
||||
init(context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
@ -419,12 +421,14 @@ private final class SheetContent: CombinedComponent {
|
||||
mode: PremiumBoostLevelsScreen.Mode,
|
||||
status: ChannelBoostStatus?,
|
||||
boostState: InternalBoostState.DisplayData?,
|
||||
initialized: Bool,
|
||||
boost: @escaping () -> Void,
|
||||
copyLink: @escaping (String) -> Void,
|
||||
dismiss: @escaping () -> Void,
|
||||
openStats: (() -> Void)?,
|
||||
openGift: (() -> Void)?,
|
||||
openPeer: ((EnginePeer) -> Void)?
|
||||
openPeer: ((EnginePeer) -> Void)?,
|
||||
updated: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
@ -435,12 +439,14 @@ private final class SheetContent: CombinedComponent {
|
||||
self.mode = mode
|
||||
self.status = status
|
||||
self.boostState = boostState
|
||||
self.initialized = initialized
|
||||
self.boost = boost
|
||||
self.copyLink = copyLink
|
||||
self.dismiss = dismiss
|
||||
self.openStats = openStats
|
||||
self.openGift = openGift
|
||||
self.openPeer = openPeer
|
||||
self.updated = updated
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
|
||||
@ -468,6 +474,9 @@ private final class SheetContent: CombinedComponent {
|
||||
if lhs.boostState != rhs.boostState {
|
||||
return false
|
||||
}
|
||||
if lhs.initialized != rhs.initialized {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -479,35 +488,33 @@ private final class SheetContent: CombinedComponent {
|
||||
private(set) var memberPeer: EnginePeer?
|
||||
|
||||
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()
|
||||
|
||||
self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peer in
|
||||
var peerIds: [EnginePeer.Id] = [peerId]
|
||||
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 {
|
||||
return
|
||||
}
|
||||
self.peer = peer
|
||||
self.updated()
|
||||
if let maybePeer = peers[peerId] {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
@ -636,7 +643,11 @@ private final class SheetContent: CombinedComponent {
|
||||
} else {
|
||||
let boostsString = strings.ChannelBoost_MoreBoostsNeeded_Boosts(Int32(remaining))
|
||||
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 {
|
||||
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))
|
||||
if myBoostCount > 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 {
|
||||
textString = strings.ChannelBoost_MoreBoostsNeeded_Boosted_Text(boostsString).string
|
||||
}
|
||||
@ -1267,6 +1278,8 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
|
||||
var cachedStatsImage: (UIImage, PresentationTheme)?
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
|
||||
var initialized = false
|
||||
|
||||
private var disposable: Disposable?
|
||||
private(set) var peer: EnginePeer?
|
||||
|
||||
@ -1279,7 +1292,6 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
|
||||
return
|
||||
}
|
||||
self.peer = peer
|
||||
self.updated()
|
||||
updated()
|
||||
})
|
||||
}
|
||||
@ -1325,6 +1337,7 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
|
||||
|
||||
if let isGroup {
|
||||
component.externalState.isGroup = isGroup
|
||||
let updated = component.updated
|
||||
let scroll = scroll.update(
|
||||
component: ScrollComponent<Empty>(
|
||||
content: AnyComponent(
|
||||
@ -1338,12 +1351,17 @@ private final class BoostLevelsContainerComponent: CombinedComponent {
|
||||
mode: component.mode,
|
||||
status: component.status,
|
||||
boostState: component.boostState,
|
||||
initialized: state.initialized,
|
||||
boost: component.boost,
|
||||
copyLink: component.copyLink,
|
||||
dismiss: component.dismiss,
|
||||
openStats: component.openStats,
|
||||
openGift: component.openGift,
|
||||
openPeer: component.openPeer
|
||||
openPeer: component.openPeer,
|
||||
updated: { [weak state] in
|
||||
state?.initialized = true
|
||||
updated()
|
||||
}
|
||||
)
|
||||
),
|
||||
externalState: externalScrollState,
|
||||
@ -1677,7 +1695,7 @@ public class PremiumBoostLevelsScreen: ViewController {
|
||||
otherText = presentationData.strings.ReassignBoost_OtherGroupsAndChannels(Int32(sourcePeers.count))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -1757,9 +1775,16 @@ public class PremiumBoostLevelsScreen: ViewController {
|
||||
|
||||
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?
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) {
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, transition: Transition) {
|
||||
guard !self.isDismissing else {
|
||||
return
|
||||
}
|
||||
@ -1824,7 +1849,7 @@ public class PremiumBoostLevelsScreen: ViewController {
|
||||
effectiveExpanded = true
|
||||
}
|
||||
|
||||
self.updated(transition: transition)
|
||||
self.updated(transition: transition, forceUpdate: forceUpdate)
|
||||
|
||||
let contentHeight = self.containerExternalState.contentHeight
|
||||
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?
|
||||
func updated(transition: Transition) {
|
||||
func updated(transition: Transition, forceUpdate: Bool = false) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
@ -1895,11 +1920,12 @@ public class PremiumBoostLevelsScreen: ViewController {
|
||||
openGift: controller.openGift,
|
||||
openPeer: controller.openPeer,
|
||||
updated: { [weak self] in
|
||||
self?.controller?.requestLayout(transition: .immediate)
|
||||
self?.requestLayout(transition: .immediate)
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
forceUpdate: forceUpdate,
|
||||
containerSize: self.containerView.bounds.size
|
||||
)
|
||||
self.contentView.frame = CGRect(origin: .zero, size: contentSize)
|
||||
@ -2200,6 +2226,7 @@ public class PremiumBoostLevelsScreen: ViewController {
|
||||
let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0
|
||||
let presentationData = self.presentationData
|
||||
let forceDark = controller.forceDark
|
||||
let boostStatusUpdated = controller.boostStatusUpdated
|
||||
|
||||
if let _ = status?.nextLevelBoosts {
|
||||
if let availableBoost = self.availableBoosts.first {
|
||||
@ -2266,15 +2293,23 @@ public class PremiumBoostLevelsScreen: ViewController {
|
||||
).startStandalone(next: { boostStatus, myBoostStatus in
|
||||
dismissReplaceImpl?()
|
||||
|
||||
if let boostStatus {
|
||||
boostStatusUpdated(boostStatus)
|
||||
}
|
||||
|
||||
let levelsController = PremiumBoostLevelsScreen(
|
||||
context: context,
|
||||
peerId: peerId,
|
||||
mode: mode,
|
||||
status: status,
|
||||
status: boostStatus,
|
||||
myBoostStatus: myBoostStatus,
|
||||
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 {
|
||||
navigationController.pushViewController(levelsController, animated: true)
|
||||
}
|
||||
|
@ -1519,7 +1519,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
peerId: peerId,
|
||||
mode: .owner(subject: nil),
|
||||
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
|
||||
boostDataPromise.set(.single(boostStatus))
|
||||
|
@ -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 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 typealias Result = Bool
|
||||
|
||||
|
@ -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<
|
||||
T0: TelegramEngineDataItem,
|
||||
T1: TelegramEngineDataItem
|
||||
|
@ -378,7 +378,7 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil
|
||||
}
|
||||
case .story:
|
||||
return .story
|
||||
case .giveaway:
|
||||
case .giveaway, .giveawayResults:
|
||||
return .giveaway
|
||||
case let .webpage(webpage):
|
||||
if let message, message.text.isEmpty, case let .Loaded(content) = webpage.content {
|
||||
|
@ -2184,19 +2184,21 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
let attributedString: NSAttributedString
|
||||
var adminBadgeString: NSAttributedString?
|
||||
var boostBadgeString: NSAttributedString?
|
||||
if let authorRank = authorRank {
|
||||
let string: String
|
||||
switch authorRank {
|
||||
if incoming {
|
||||
if let authorRank = authorRank {
|
||||
let string: String
|
||||
switch authorRank {
|
||||
case .owner:
|
||||
string = item.presentationData.strings.Conversation_Owner
|
||||
case .admin:
|
||||
string = item.presentationData.strings.Conversation_Admin
|
||||
case let .custom(rank):
|
||||
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?
|
||||
@ -2240,9 +2242,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
var boostCount: Int = 0
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? BoostCountMessageAttribute {
|
||||
boostCount = attribute.count
|
||||
if incoming {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? BoostCountMessageAttribute {
|
||||
boostCount = attribute.count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3181,9 +3185,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
var boostCount: Int = 0
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? BoostCountMessageAttribute {
|
||||
boostCount = attribute.count
|
||||
if incoming {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? BoostCountMessageAttribute {
|
||||
boostCount = attribute.count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,7 +433,8 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
|
||||
} 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)
|
||||
}
|
||||
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
|
||||
let sideInsets = layoutConstants.text.bubbleInsets.right * 2.0
|
||||
|
@ -350,11 +350,6 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
|
||||
if let completion {
|
||||
completionImpl = { value in
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,10 +92,12 @@ public final class MessageInputPanelComponent: Component {
|
||||
enum Kind {
|
||||
case text
|
||||
case premiumRequired
|
||||
case boostRequired
|
||||
}
|
||||
|
||||
case text(String)
|
||||
case premiumRequired(title: String, subtitle: String, action: () -> Void)
|
||||
case boostRequired(title: String, subtitle: String, action: () -> Void)
|
||||
|
||||
var kind: Kind {
|
||||
switch self {
|
||||
@ -103,6 +105,8 @@ public final class MessageInputPanelComponent: Component {
|
||||
return .text
|
||||
case .premiumRequired:
|
||||
return .premiumRequired
|
||||
case .boostRequired:
|
||||
return .boostRequired
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +124,12 @@ public final class MessageInputPanelComponent: Component {
|
||||
} else {
|
||||
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(
|
||||
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
|
||||
|
||||
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(
|
||||
content: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: title, font: Font.regular(13.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3))),
|
||||
maximumNumberOfLines: 2
|
||||
))),
|
||||
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)),
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(text),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
action: {
|
||||
action()
|
||||
|
@ -216,7 +216,9 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: false,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: nil,
|
||||
appliedBoosts: nil
|
||||
)
|
||||
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
|
||||
additionalPeerData = StoryContentContextState.AdditionalPeerData(
|
||||
@ -225,7 +227,9 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: cachedChannelData.flags.contains(.canViewStats),
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
|
||||
appliedBoosts: cachedChannelData.appliedBoosts
|
||||
)
|
||||
} else {
|
||||
additionalPeerData = StoryContentContextState.AdditionalPeerData(
|
||||
@ -234,18 +238,21 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: false,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: nil,
|
||||
appliedBoosts: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
additionalPeerData = StoryContentContextState.AdditionalPeerData(
|
||||
isMuted: true,
|
||||
areVoiceMessagesAvailable: true,
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: false,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: nil,
|
||||
appliedBoosts: nil
|
||||
)
|
||||
}
|
||||
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.NotificationSettings(id: storyId.peerId),
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
guard let peer else {
|
||||
@ -1251,7 +1260,9 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
presence: presence,
|
||||
canViewStats: canViewStats,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: boostsToUnrestrict,
|
||||
appliedBoosts: appliedBoosts
|
||||
)
|
||||
|
||||
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.NotificationSettings(id: peerId),
|
||||
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,
|
||||
self.focusedIdUpdated.get(),
|
||||
@ -1458,7 +1471,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||
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 {
|
||||
return
|
||||
@ -1472,7 +1485,9 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
||||
presence: presence,
|
||||
canViewStats: canViewStats,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: boostsToUnrestrict,
|
||||
appliedBoosts: appliedBoosts
|
||||
)
|
||||
|
||||
self.listState = state
|
||||
@ -2389,7 +2404,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: false,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: nil,
|
||||
appliedBoosts: nil
|
||||
)
|
||||
} else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData {
|
||||
additionalPeerData = StoryContentContextState.AdditionalPeerData(
|
||||
@ -2398,7 +2415,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: cachedChannelData.flags.contains(.canViewStats),
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: cachedChannelData.boostsToUnrestrict,
|
||||
appliedBoosts: cachedChannelData.appliedBoosts
|
||||
)
|
||||
} else {
|
||||
additionalPeerData = StoryContentContextState.AdditionalPeerData(
|
||||
@ -2407,7 +2426,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext {
|
||||
presence: peerPresence.flatMap { EnginePeer.Presence($0) },
|
||||
canViewStats: false,
|
||||
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) },
|
||||
canViewStats: false,
|
||||
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
|
||||
preferHighQualityStories: preferHighQualityStories
|
||||
preferHighQualityStories: preferHighQualityStories,
|
||||
boostsToUnrestrict: nil,
|
||||
appliedBoosts: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,8 @@ public final class StoryContentContextState {
|
||||
public let canViewStats: Bool
|
||||
public let isPremiumRequiredForMessaging: Bool
|
||||
public let preferHighQualityStories: Bool
|
||||
public let boostsToUnrestrict: Int32?
|
||||
public let appliedBoosts: Int32?
|
||||
|
||||
public init(
|
||||
isMuted: Bool,
|
||||
@ -154,7 +156,9 @@ public final class StoryContentContextState {
|
||||
presence: EnginePeer.Presence?,
|
||||
canViewStats: Bool,
|
||||
isPremiumRequiredForMessaging: Bool,
|
||||
preferHighQualityStories: Bool
|
||||
preferHighQualityStories: Bool,
|
||||
boostsToUnrestrict: Int32?,
|
||||
appliedBoosts: Int32?
|
||||
) {
|
||||
self.isMuted = isMuted
|
||||
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
|
||||
@ -162,6 +166,8 @@ public final class StoryContentContextState {
|
||||
self.canViewStats = canViewStats
|
||||
self.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging
|
||||
self.preferHighQualityStories = preferHighQualityStories
|
||||
self.boostsToUnrestrict = boostsToUnrestrict
|
||||
self.appliedBoosts = appliedBoosts
|
||||
}
|
||||
|
||||
public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool {
|
||||
@ -183,6 +189,12 @@ public final class StoryContentContextState {
|
||||
if lhs.preferHighQualityStories != rhs.preferHighQualityStories {
|
||||
return false
|
||||
}
|
||||
if lhs.boostsToUnrestrict != rhs.boostsToUnrestrict {
|
||||
return false
|
||||
}
|
||||
if lhs.appliedBoosts != rhs.appliedBoosts {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1668,7 +1668,13 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
case .broadcast:
|
||||
displayFooter = true
|
||||
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 {
|
||||
displayFooter = true
|
||||
@ -2744,9 +2750,42 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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 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
|
||||
self?.presentPremiumRequiredForMessaging()
|
||||
})
|
||||
@ -2757,21 +2796,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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
|
||||
if let stealthModeTimeout = component.stealthModeTimeout {
|
||||
let minutes = Int(stealthModeTimeout / 60)
|
||||
@ -5673,7 +5697,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
let controller = PremiumIntroScreen(context: component.context, source: .settings, forceDark: true)
|
||||
self.sendMessageContext.actionSheet = controller
|
||||
controller.wasDismissed = { [weak self, weak controller]in
|
||||
controller.wasDismissed = { [weak self, weak controller] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -5688,6 +5712,45 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
|
@ -272,6 +272,10 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
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
|
||||
if let self {
|
||||
Queue.mainQueue().async {
|
||||
Queue.mainQueue().after(0.05) {
|
||||
self.node.setupCamera()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "10.7.2",
|
||||
"app": "10.8",
|
||||
"bazel": "7.0.2",
|
||||
"xcode": "15.2",
|
||||
"macos": "13.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user