Various fixes

This commit is contained in:
Ilya Laktyushin 2024-09-03 15:48:15 +04:00
parent 32e6629538
commit d2deea0ea2
26 changed files with 528 additions and 268 deletions

View File

@ -10510,11 +10510,15 @@ Sorry for the inconvenience.";
"Notification.GiveawayResultsNoWinners_1" = "Due to the giveaway terms, no winners could be selected by Telegram, a gift link was forwarded to channel administrators.";
"Notification.GiveawayResultsNoWinners_any" = "Due to the giveaway terms, no winners could be selected by Telegram, all **%@** gift links were forwarded to channel administrators.";
"Notification.GiveawayResultsNoWinners.Group_1" = "Due to the giveaway terms, no winners could be selected by Telegram, a gift link was forwarded to group administrators.";
"Notification.GiveawayResultsNoWinners.Group_any" = "Due to the giveaway terms, no winners could be selected by Telegram, all **%@** gift links were forwarded to group administrators.";
"Notification.GiveawayResultsMixedWinners_1" = "**%@** winner of the giveaway was randomly selected by Telegram and received their gift link in a private message.";
"Notification.GiveawayResultsMixedWinners_any" = "**%@** winners of the giveaway were randomly selected by Telegram and received their gift links in private messages.";
"Notification.GiveawayResultsMixedUnclaimed_1" = "**%@** undistributed gift link was forwarded to channel administrators";
"Notification.GiveawayResultsMixedUnclaimed_any" = "**%@** undistributed gift links were forwarded to channel administrators";
"Notification.GiveawayResultsMixedUnclaimed.Group_1" = "**%@** undistributed gift link was forwarded to group administrators";
"Notification.GiveawayResultsMixedUnclaimed.Group_any" = "**%@** undistributed gift links were forwarded to group administrators";
"Chat.Giveaway.DeleteConfirmation.Title" = "Do you want to delete the Giveaway Announcement?";
"Chat.Giveaway.DeleteConfirmation.Text" = "Deleting this message won't cancel the giveaway - the winners will still be selected on **%@**.\n\nOnce deleted, the Giveaway Announcement cannot be recovered.";
@ -12876,6 +12880,11 @@ Sorry for the inconvenience.";
"BoostGift.Stars.Info" = "Choose how many stars to give away and how many boosts to receive for 1 year.";
"BoostGift.AdditionalPrizesInfoStarsOff" = "Turn this on if you want to give the winners your own prizes in addition to Stars.";
"BoostGift.AdditionalPrizesInfoStars_1" = "**%@** Star";
"BoostGift.AdditionalPrizesInfoStars_any" = "**%@** Stars";
"BoostGift.AdditionalPrizesInfoStarsOn" = "All prizes: %1$@%2$@.";
"BoostGift.AdditionalPrizesInfoStarsAndOther" = " and **%1$@** %2$@";
"BoostGift.Stars.Winners" = "NUMBER OF WINNERS";
"BoostGift.Stars.WinnersInfo" = "Choose how many winners you want to distribute stars among.";
@ -12915,3 +12924,6 @@ Sorry for the inconvenience.";
"Premium.BoostByGiveawayDescription" = "Get more boosts and subscribers for your channel by giving away prizes. [Get boosts >]()";
"Premium.Group.BoostByGiveawayDescription" = "Get more boosts and members for your group by giving away prizes. [Get boosts >]()";
"Notification.StarsGiveawayResultsNoWinners" = "Due to the giveaway terms, no winners could be selected by Telegram, all stars were credited to channel administrators.";
"Notification.StarsGiveawayResultsNoWinners.Group" = "Due to the giveaway terms, no winners could be selected by Telegram, all stars were credited to group administrators.";

View File

@ -213,6 +213,8 @@ public protocol AttachmentMediaPickerContext {
var price: Int64? { get }
func setPrice(_ price: Int64) -> Void
var hasTimers: Bool { get }
var loadingProgress: Signal<CGFloat?, NoError> { get }
var mainButtonState: Signal<AttachmentMainButtonState?, NoError> { get }
var secondaryButtonState: Signal<AttachmentMainButtonState?, NoError> { get }
@ -257,6 +259,10 @@ public extension AttachmentMediaPickerContext {
func setPrice(_ price: Int64) -> Void {
}
var hasTimers: Bool {
return false
}
var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}

View File

@ -1004,10 +1004,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var captionIsAboveMedia: Signal<Bool, NoError> = .single(false)
var canMakePaidContent = false
var currentPrice: Int64?
var hasTimers = false
if let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext {
captionIsAboveMedia = mediaPickerContext.captionIsAboveMedia
canMakePaidContent = mediaPickerContext.canMakePaidContent
currentPrice = mediaPickerContext.price
hasTimers = mediaPickerContext.hasTimers
}
let _ = (combineLatest(
@ -1039,7 +1041,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
canSendWhenOnline: sendWhenOnlineAvailable,
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [],
canMakePaidContent: canMakePaidContent,
currentPrice: currentPrice
currentPrice: currentPrice,
hasTimers: hasTimers
)),
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,

View File

@ -92,6 +92,17 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor
}
self.addSubview(self.webView)
self.webView.interactiveTransitionGestureRecognizerTest = { [weak self] point in
if let self {
if let result = self.webView.hitTest(point, with: nil), let scrollView = findScrollView(view: result), scrollView.isDescendant(of: self.webView) {
if scrollView.contentSize.width > scrollView.frame.width, scrollView.contentOffset.x > -scrollView.contentInset.left {
return true
}
}
}
return false
}
}
required init?(coder: NSCoder) {
@ -416,3 +427,14 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
return imageView
}
}
private func findScrollView(view: UIView?) -> UIScrollView? {
if let view = view {
if let view = view as? UIScrollView {
return view
}
return findScrollView(view: view.superview)
} else {
return nil
}
}

View File

@ -117,12 +117,41 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
self.pageNumber = (1, self.pdfView.document?.pageCount ?? 1)
self.startPageIndicatorTimer()
self.pdfView.interactiveTransitionGestureRecognizerTest = { [weak self] point in
if let self {
if let result = self.pdfView.hitTest(point, with: nil), let scrollView = findScrollView(view: result), scrollView.isDescendant(of: self.pdfView) {
if scrollView.contentSize.width > scrollView.frame.width, scrollView.contentOffset.x > -scrollView.contentInset.left {
return true
}
}
}
return false
}
NotificationCenter.default.addObserver(self, selector: #selector(self.pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self, name: .PDFViewPageChanged, object: nil)
}
@objc func pageChangeHandler(_ notification: Notification) {
if let document = self.pdfView.document, let page = self.pdfView.currentPage {
let number = document.index(for: page) + 1
if number != self.pageNumber?.0 {
self.pageNumber = (number, document.pageCount)
if let (size, insets, fullInsets) = self.validLayout {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
}
}
}
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
if #available(iOS 15.0, *) {
@ -421,16 +450,6 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
}
if !scrollView.isZooming && !self.wasZooming {
self.updateScrollingOffset(isReset: false, transition: .immediate)
if let document = self.pdfView.document, let page = self.pdfView.currentPage {
let number = document.index(for: page) + 1
if number != self.pageNumber?.0 {
self.pageNumber = (number, document.pageCount)
if let (size, insets, fullInsets) = self.validLayout {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
}
}
}
}
}
@ -546,3 +565,14 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
return nil
}
}
private func findScrollView(view: UIView?) -> UIScrollView? {
if let view = view {
if let view = view as? UIScrollView {
return view
}
return findScrollView(view: view.superview)
} else {
return nil
}
}

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import SwiftSignalKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import ComponentFlow
@ -1473,10 +1474,19 @@ public class BrowserScreen: ViewController, MinimizableController {
case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation, preloadedResources: [Any]?)
case document(file: TelegramMediaFile, canShare: Bool)
case pdfDocument(file: TelegramMediaFile, canShare: Bool)
public var fileId: MediaId? {
switch self {
case let .document(file, _), let .pdfDocument(file, _):
return file.fileId
default:
return nil
}
}
}
private let context: AccountContext
fileprivate let subject: Subject
public let subject: Subject
private var preferredConfiguration: WKWebViewConfiguration?
private var openPreviousOnClose = false

View File

@ -740,7 +740,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
// })
// } else {
if let url = navigationAction.request.url?.absoluteString {
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url)) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") {
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://")) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") {
decisionHandler(.cancel, preferences)
self.minimize()
self.openAppUrl(url)
@ -776,7 +776,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url?.absoluteString {
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url)) {
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://")) {
decisionHandler(.cancel)
self.minimize()
self.openAppUrl(url)
@ -858,7 +858,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if [-1003, -1100, 102].contains((error as NSError).code) {
self.currentError = error
if let url = (error as NSError).userInfo["NSErrorFailingURLKey"] as? URL, url.absoluteString.hasPrefix("itms-appss:") {
} else {
self.currentError = error
}
} else {
self.currentError = nil
}
@ -870,7 +873,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
if let url = navigationAction.request.url?.absoluteString {
if isTelegramMeLink(url) || isTelegraPhLink(url) {
if isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://") {
self.minimize()
self.openAppUrl(url)
} else {
@ -1350,7 +1353,7 @@ let setupFontFunctions = """
"""
private let videoSource = """
function disableWebkitEnterFullscreen(videoElement) {
function tgBrowserDisableWebkitEnterFullscreen(videoElement) {
if (videoElement && videoElement.webkitEnterFullscreen) {
Object.defineProperty(videoElement, 'webkitEnterFullscreen', {
value: undefined
@ -1358,11 +1361,11 @@ function disableWebkitEnterFullscreen(videoElement) {
}
}
function disableFullscreenOnExistingVideos() {
document.querySelectorAll('video').forEach(disableWebkitEnterFullscreen);
function tgBrowserDisableFullscreenOnExistingVideos() {
document.querySelectorAll('video').forEach(tgBrowserDisableWebkitEnterFullscreen);
}
function handleMutations(mutations) {
function tgBrowserHandleMutations(mutations) {
mutations.forEach((mutation) => {
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((newNode) => {
@ -1377,17 +1380,17 @@ function handleMutations(mutations) {
});
}
disableFullscreenOnExistingVideos();
tgBrowserDisableFullscreenOnExistingVideos();
const observer = new MutationObserver(handleMutations);
const _tgbrowser_observer = new MutationObserver(tgBrowserHandleMutations);
observer.observe(document.body, {
_tgbrowser_observer.observe(document.body, {
childList: true,
subtree: true
});
function disconnectObserver() {
observer.disconnect();
function tgBrowserDisconnectObserver() {
_tgbrowser_observer.disconnect();
}
"""

View File

@ -22,6 +22,7 @@ public enum SendMessageActionSheetControllerParams {
public let forwardMessageIds: [EngineMessage.Id]
public let canMakePaidContent: Bool
public let currentPrice: Int64?
public let hasTimers: Bool
public init(
isScheduledMessages: Bool,
@ -32,7 +33,8 @@ public enum SendMessageActionSheetControllerParams {
canSendWhenOnline: Bool,
forwardMessageIds: [EngineMessage.Id],
canMakePaidContent: Bool,
currentPrice: Int64?
currentPrice: Int64?,
hasTimers: Bool
) {
self.isScheduledMessages = isScheduledMessages
self.mediaPreview = mediaPreview
@ -43,6 +45,7 @@ public enum SendMessageActionSheetControllerParams {
self.forwardMessageIds = forwardMessageIds
self.canMakePaidContent = canMakePaidContent
self.currentPrice = currentPrice
self.hasTimers = hasTimers
}
}

View File

@ -452,6 +452,9 @@ final class ChatSendMessageContextScreenComponent: Component {
if sendMessage.isScheduledMessages {
canSchedule = false
}
if sendMessage.hasTimers {
canSchedule = false
}
canMakePaidContent = sendMessage.canMakePaidContent
currentPrice = sendMessage.currentPrice
case .editMessage:

View File

@ -488,6 +488,8 @@ final class ComposePollScreenComponent: Component {
self.environment = environment
if self.component == nil {
self.isQuiz = component.isQuiz ?? false
self.pollOptions.append(ComposePollScreenComponent.PollOption(
id: self.nextPollOptionId
))
@ -1002,28 +1004,35 @@ final class ComposePollScreenComponent: Component {
contentHeight += pollOptionsSectionFooterSize.height
contentHeight += sectionSpacing
var canBePublic = true
if case let .channel(channel) = component.peer, case .broadcast = channel.info {
canBePublic = false
}
var pollSettingsSectionItems: [AnyComponentWithIdentity<Empty>] = []
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreatePoll_Anonymous,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
))),
], alignment: .left, spacing: 2.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isAnonymous, action: { [weak self] _ in
guard let self else {
return
}
self.isAnonymous = !self.isAnonymous
self.state?.updated(transition: .spring(duration: 0.4))
})),
action: nil
))))
if canBePublic {
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreatePoll_Anonymous,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
))),
], alignment: .left, spacing: 2.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isAnonymous, action: { [weak self] _ in
guard let self else {
return
}
self.isAnonymous = !self.isAnonymous
self.state?.updated(transition: .spring(duration: 0.4))
})),
action: nil
))))
}
pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
@ -1569,7 +1578,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
public static func initialData(context: AccountContext) -> InitialData {
return InitialData(
maxPollTextLength: Int(255),
maxPollTextLength: Int(200),
maxPollOptionLength: 100
)
}

View File

@ -134,7 +134,7 @@ private struct OrderedLinkedList<T: Equatable>: Sequence, Equatable {
}
}
private let maxTextLength = 255
private let maxTextLength = 200
private let maxOptionLength = 100
private let maxOptionCount = 10

View File

@ -1714,6 +1714,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
shareController.dismissed = { [weak self] _ in
self?.interacting?(false)
}
shareController.actionCompleted = { [weak self] in
if let strongSelf = self, let actionCompletionText = actionCompletionText {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }

View File

@ -259,6 +259,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
private let dataDisposable = MetaDisposable()
private let recognitionDisposable = MetaDisposable()
private var status: MediaResourceStatus?
private var fetchedDimensions: PixelDimensions?
private let pagingEnabledPromise = ValuePromise<Bool>(true)
@ -806,9 +807,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
})
}
func setFile(context: AccountContext, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference) {
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
if var largestSize = fileReference.media.dimensions {
func setFile(context: AccountContext, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, force: Bool = false) {
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) || force {
if var largestSize = (fileReference.media.dimensions ?? self.fetchedDimensions) {
var displaySize = largestSize.cgSize.dividedByScreenScale()
if let previewDimensions = largestImageRepresentation(fileReference.media.previewRepresentations)?.dimensions {
let previewAspect = CGFloat(previewDimensions.width) / CGFloat(previewDimensions.height)
@ -848,6 +849,22 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .image, reference: fileReference.resourceReference(fileReference.media.resource)).start())
} else {
let _ = (chatMessageFileDatas(account: context.account, userLocation: userLocation, fileReference: fileReference, progressive: false, fetched: true)
|> mapToSignal { value -> Signal<UIImage?, NoError> in
if value._2, let path = value._1, let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
return .single(UIImage(data: data))
}
return .complete()
}
|> deliverOnMainQueue).start(next: { [weak self] image in
if let self, let image {
self.fetchedDimensions = PixelDimensions(image.size)
self.setFile(context: context, userLocation: userLocation, fileReference: fileReference, force: true)
}
})
self._ready.set(.single(Void()))
}
}

View File

@ -2775,6 +2775,20 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
}
}
var hasTimers: Bool {
guard let controller = self.controller else {
return false
}
if let selectionContext = controller.interaction?.selectionState, let editingContext = controller.interaction?.editingState {
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
if let time = editingContext.timer(for: item), time.intValue > 0 {
return true
}
}
}
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return Signal { [weak self] subscriber in
guard let interaction = self?.controller?.interaction else {

View File

@ -271,7 +271,7 @@ func chatMessagePhotoDatas(mediaBox: MediaBox, userLocation: MediaResourceUserLo
}
}
private func chatMessageFileDatas(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal<Tuple3<Data?, String?, Bool>, NoError> {
public func chatMessageFileDatas(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal<Tuple3<Data?, String?, Bool>, NoError> {
let thumbnailResource = fetched ? nil : smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource
let fullSizeResource = fileReference.media.resource

View File

@ -784,6 +784,7 @@ private func createGiveawayControllerEntries(
entries.append(.prepaid(presentationData.theme, title, text, prepaidGiveaway))
}
var starsPerUser: Int64 = 0
if case .starsGiveaway = state.mode, !starsGiveawayOptions.isEmpty {
let selectedOption = starsGiveawayOptions.first(where: { $0.giveawayOption.count == state.stars })!
entries.append(.starsHeader(presentationData.theme, presentationData.strings.BoostGift_Stars_Title.uppercased(), presentationData.strings.BoostGift_Stars_Boosts(selectedOption.giveawayOption.yearlyBoosts).uppercased()))
@ -800,6 +801,7 @@ private func createGiveawayControllerEntries(
let subtitle = presentationData.strings.BoostGift_Stars_PerUser("\(winners.starsPerUser)").string
let label = product.storeProduct.price
starsPerUser = winners.starsPerUser
let isSelected = product.giveawayOption.count == state.stars
entries.append(.stars(i, presentationData.theme, Int32(product.giveawayOption.count), giftTitle, subtitle, label, isSelected, maxWinners))
@ -933,14 +935,24 @@ private func createGiveawayControllerEntries(
if state.showPrizeDescription {
entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions))
let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 12)
if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "")
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", subscriptionsString, monthsString).string
if state.mode == .starsGiveaway {
let starsString = presentationData.strings.BoostGift_AdditionalPrizesInfoStars(Int32(state.stars))
if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let _ = starsPerUser
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoStarsOn(starsString, "").string
} else {
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoStarsOn(starsString, presentationData.strings.BoostGift_AdditionalPrizesInfoStarsAndOther("\(state.winners)", state.prizeDescription).string).string
}
} else {
let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoWithSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "")
let description = "\(state.prizeDescription) \(subscriptionsString)"
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", description, monthsString).string
let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 12)
if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "")
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", subscriptionsString, monthsString).string
} else {
let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoWithSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "")
let description = "\(state.prizeDescription) \(subscriptionsString)"
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", description, monthsString).string
}
}
}
entries.append(.prizeDescriptionInfo(presentationData.theme, prizeDescriptionInfoText))

View File

@ -241,7 +241,6 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else {
type = .video
}
break inner
case let .Audio(isVoice, _, _, _, _):
if isVoice {
type = .audio
@ -981,12 +980,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
case .joinedChannel:
attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor)
case let .giveawayResults(winners, unclaimed, _):
case let .giveawayResults(winners, unclaimed, stars):
var isGroup = false
let messagePeer = message.peers[message.id.peerId]
if let channel = messagePeer as? TelegramChannel, case .group = channel.info {
isGroup = true
}
if winners == 0 {
attributedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
if stars {
attributedString = parseMarkdownIntoAttributedString(isGroup ? strings.Notification_StarsGiveawayResultsNoWinners_Group : strings.Notification_StarsGiveawayResultsNoWinners, attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
} else {
attributedString = parseMarkdownIntoAttributedString(isGroup ? strings.Notification_GiveawayResultsNoWinners_Group(unclaimed) : strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
}
} else if unclaimed > 0 {
let winnersString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsMixedWinners(winners), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
let unclaimedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsMixedUnclaimed(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
let unclaimedString = parseMarkdownIntoAttributedString(isGroup ? strings.Notification_GiveawayResultsMixedUnclaimed_Group(unclaimed) : strings.Notification_GiveawayResultsMixedUnclaimed(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
let combinedString = NSMutableAttributedString(attributedString: winnersString)
combinedString.append(NSAttributedString(string: "\n"))
combinedString.append(unclaimedString)

View File

@ -289,9 +289,10 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
badgeString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: badgeString.string))
}
let badgeBackgroundColor = !incoming || !isStars ? accentColor : UIColor(rgb: 0xffaf0a)
var updatedBadgeImage: UIImage?
if themeUpdated {
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: isStars ? UIColor(rgb: 0xffaf0a) : accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: badgeBackgroundColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
}
let prizeTitleText: String

View File

@ -995,7 +995,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
return ChatMessageBubbleContentTapAction(content: .none)
} else {
if let text = self.textNode.textNode.attributeSubstring(name: "Attribute__Blockquote", index: index) {
return ChatMessageBubbleContentTapAction(content: .copy(text.1))
return ChatMessageBubbleContentTapAction(content: .copy(text.0))
} else {
return ChatMessageBubbleContentTapAction(content: .none)
}

View File

@ -322,7 +322,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.expandedDeletedMessages.insert(messageId)
}
}, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in
self?.openUrl(url.url)
self?.openUrl(url.url, progress: url.progress)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) {
@ -628,6 +628,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, playMessageEffect: { _ in
}, editMessageFactCheck: { _ in
}, requestMessageUpdate: { _, _ in
}, cancelInteractiveKeyboardGestures: {
}, dismissTextInput: {
}, scrollToMessageId: { _ in
@ -1159,7 +1160,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
}
private func openUrl(_ url: String) {
private func openUrl(_ url: String, progress: Promise<Bool>? = nil) {
self.navigationActionDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: true) |> deliverOnMainQueue).startStrict(next: { [weak self] result in
if let strongSelf = self {
switch result {
@ -1235,11 +1236,104 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
let browserController = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel))
strongSelf.pushController(browserController)
case let .join(link):
strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in
if let strongSelf = self {
strongSelf.openPeer(peer: peer, peekData: peekData)
let context = strongSelf.context
let navigationController = strongSelf.getNavigationController()
let openPeer: (EnginePeer, ChatPeekTimeout?) -> Void = { [weak self] peer, peekData in
self?.openPeer(peer: peer, peekData: peekData)
}
if let progress {
let progressSignal = Signal<Never, NoError> { subscriber in
progress.set(.single(true))
return ActionDisposable {
Queue.mainQueue().async() {
progress.set(.single(false))
}
}
}
}, parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|> runOn(Queue.mainQueue())
|> delay(0.1, queue: Queue.mainQueue())
let progressDisposable = progressSignal.startStrict()
var signal = context.engine.peers.joinLinkInformation(link)
signal = signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
let _ = (signal
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedState in
switch resolvedState {
case let .alreadyJoined(peer):
openPeer(peer, nil)
case let .peek(peer, deadline):
openPeer(peer, ChatPeekTimeout(deadline: deadline, linkData: link))
case let .invite(invite):
if let subscriptionPricing = invite.subscriptionPricing, let subscriptionFormId = invite.subscriptionFormId, let starsContext = context.starsContext {
let inputData = Promise<BotCheckoutController.InputData?>()
var photo: [TelegramMediaImageRepresentation] = []
if let photoRepresentation = invite.photoRepresentation {
photo.append(photoRepresentation)
}
let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, flags: [], version: 0)
inputData.set(.single(BotCheckoutController.InputData(
form: BotPaymentForm(
id: subscriptionFormId,
canSaveCredentials: false,
passwordMissing: false,
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil),
paymentBotId: channel.id,
providerId: nil,
url: nil,
nativeProvider: nil,
savedInfo: nil,
savedCredentials: [],
additionalPaymentMethods: []
),
validatedFormInfo: nil,
botPeer: EnginePeer(channel)
)))
let starsInputData = combineLatest(
inputData.get(),
starsContext.state
)
|> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)? in
if let data, let state {
return (state, data.form, data.botPeer, nil)
} else {
return nil
}
}
let _ = (starsInputData
|> SwiftSignalKit.filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
let controller = context.sharedContext.makeStarsSubscriptionTransferScreen(context: context, starsContext: starsContext, invoice: invoice, link: link, inputData: starsInputData, navigateToPeer: { peer in
openPeer(peer, nil)
})
navigationController?.pushViewController(controller)
})
} else {
strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openPeer(peer, peekData)
}, parentNavigationController: navigationController, resolvedState: resolvedState), .window(.root), nil)
}
default:
strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openPeer(peer, peekData)
}, parentNavigationController: navigationController, resolvedState: resolvedState), .window(.root), nil)
}
})
} else {
strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openPeer(peer, peekData)
}, parentNavigationController: navigationController), .window(.root), nil)
}
case let .localization(identifier):
strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil)
case .proxy, .confirmationCode, .cancelAccountReset, .share:

View File

@ -1746,7 +1746,7 @@ private final class ChatSendStarsScreenComponent: Component {
self.topPeerItems[topPeer.id] = itemView
}
let itemCountString = "\(topPeer.count)"
let itemCountString = presentationStringsFormattedNumber(Int32(topPeer.count), environment.dateTimeFormat.groupingSeparator)
/*if topPeer.isMy && myCountAddition != 0 && topPeer.count > myCountAddition {
itemCountString = "\(topPeer.count - myCountAddition) +\(myCountAddition)"
}*/

View File

@ -711,7 +711,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
canSendWhenOnline: false,
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [],
canMakePaidContent: false,
currentPrice: nil
currentPrice: nil,
hasTimers: false
)),
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,

View File

@ -235,7 +235,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
canSendWhenOnline: sendWhenOnlineAvailable,
forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [],
canMakePaidContent: false,
currentPrice: nil
currentPrice: nil,
hasTimers: false
)),
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,

View File

@ -4748,207 +4748,209 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
isMuted = peerIsMuted
}
items.append(.action(ContextMenuActionItem(text: isMuted ? presentationData.strings.ChatList_Context_Unmute : presentationData.strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
if isMuted {
let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0)
|> deliverOnMainQueue).startStandalone(completed: {
f(.default)
})
} else {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteFor, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
var subItems: [ContextMenuItem] = []
let presetValues: [Int32] = [
1 * 60 * 60,
8 * 60 * 60,
1 * 24 * 60 * 60,
7 * 24 * 60 * 60
]
if !"".isEmpty {
items.append(.action(ContextMenuActionItem(text: isMuted ? presentationData.strings.ChatList_Context_Unmute : presentationData.strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
if isMuted {
let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0)
|> deliverOnMainQueue).startStandalone(completed: {
f(.default)
})
} else {
var items: [ContextMenuItem] = []
for value in presetValues {
subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: presentationData.strings, value: value), icon: { _ in
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteFor, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
var subItems: [ContextMenuItem] = []
let presetValues: [Int32] = [
1 * 60 * 60,
8 * 60 * 60,
1 * 24 * 60 * 60,
7 * 24 * 60 * 60
]
for value in presetValues {
subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: presentationData.strings, value: value), icon: { _ in
return nil
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: value).startStandalone()
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
}
subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in
return nil
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: value).startStandalone()
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
// if let chatListController = chatListController {
// openCustomMute(context: context, peerId: peerId, threadId: threadId, baseController: chatListController)
// }
})))
}
subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in
return nil
}, action: { _, f in
f(.default)
// if let chatListController = chatListController {
// openCustomMute(context: context, peerId: peerId, threadId: threadId, baseController: chatListController)
// }
c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true)
})))
c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true)
})))
items.append(.separator)
var isSoundEnabled = true
switch threadData.notificationSettings.messageSound {
case .none:
isSoundEnabled = false
default:
break
}
if case .muted = threadData.notificationSettings.muteState {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: nil).startStandalone()
let iconColor: UIColor = .white
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
items.append(.separator)
var isSoundEnabled = true
switch threadData.notificationSettings.messageSound {
case .none:
isSoundEnabled = false
default:
break
}
if case .muted = threadData.notificationSettings.muteState {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: nil).startStandalone()
let iconColor: UIColor = .white
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
} else if !isSoundEnabled {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_EnableSound, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .default).startStandalone()
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
} else {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_DisableSound, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .none).startStandalone()
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
], title: nil, text: presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
} else if !isSoundEnabled {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_EnableSound, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .default).startStandalone()
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
} else {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_DisableSound, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .none).startStandalone()
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
}
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.NotificationSettings.Global()
)
|> deliverOnMainQueue).startStandalone(next: { globalSettings in
let updatePeerSound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue
}
let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
peerId, displayPreviews in
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue
}
let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
peerId, mute in
return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
}
let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
peerId, hideSender in
return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
}
let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
}
let defaultSound: PeerMessageSound
if case .broadcast = channel.info {
defaultSound = globalSettings.channels.sound._asMessageSound()
} else {
defaultSound = globalSettings.groupChats.sound._asMessageSound()
}
let canRemove = false
let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in
let _ = (updatePeerSound(peerId, sound)
|> deliverOnMainQueue).startStandalone(next: { _ in
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.NotificationSettings.Global()
)
|> deliverOnMainQueue).startStandalone(next: { globalSettings in
let updatePeerSound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue
}
let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue
}
let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
peerId, displayPreviews in
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue
}
let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
peerId, mute in
return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
}
let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
peerId, hideSender in
return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
}
let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
}
let defaultSound: PeerMessageSound
if case .broadcast = channel.info {
defaultSound = globalSettings.channels.sound._asMessageSound()
} else {
defaultSound = globalSettings.groupChats.sound._asMessageSound()
}
let canRemove = false
let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in
let _ = (updatePeerSound(peerId, sound)
|> deliverOnMainQueue).startStandalone(next: { _ in
})
}, updatePeerNotificationInterval: { [weak self] peerId, muteInterval in
let _ = (updatePeerNotificationInterval(peerId, muteInterval)
|> deliverOnMainQueue).startStandalone(next: { _ in
if let muteInterval = muteInterval, muteInterval == Int32.max {
let iconColor: UIColor = .white
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
})
}, updatePeerDisplayPreviews: { peerId, displayPreviews in
let _ = (updatePeerDisplayPreviews(peerId, displayPreviews)
|> deliverOnMainQueue).startStandalone(next: { _ in
})
}, updatePeerStoriesMuted: { peerId, mute in
let _ = (updatePeerStoriesMuted(peerId, mute)
|> deliverOnMainQueue).startStandalone()
}, updatePeerStoriesHideSender: { peerId, hideSender in
let _ = (updatePeerStoriesHideSender(peerId, hideSender)
|> deliverOnMainQueue).startStandalone()
}, updatePeerStorySound: { peerId, sound in
let _ = (updatePeerStorySound(peerId, sound)
|> deliverOnMainQueue).startStandalone()
}, removePeerFromExceptions: {
}, modifiedPeer: {
})
}, updatePeerNotificationInterval: { [weak self] peerId, muteInterval in
let _ = (updatePeerNotificationInterval(peerId, muteInterval)
|> deliverOnMainQueue).startStandalone(next: { _ in
if let muteInterval = muteInterval, muteInterval == Int32.max {
let iconColor: UIColor = .white
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
})
}, updatePeerDisplayPreviews: { peerId, displayPreviews in
let _ = (updatePeerDisplayPreviews(peerId, displayPreviews)
|> deliverOnMainQueue).startStandalone(next: { _ in
})
}, updatePeerStoriesMuted: { peerId, mute in
let _ = (updatePeerStoriesMuted(peerId, mute)
|> deliverOnMainQueue).startStandalone()
}, updatePeerStoriesHideSender: { peerId, hideSender in
let _ = (updatePeerStoriesHideSender(peerId, hideSender)
|> deliverOnMainQueue).startStandalone()
}, updatePeerStorySound: { peerId, sound in
let _ = (updatePeerStorySound(peerId, sound)
|> deliverOnMainQueue).startStandalone()
}, removePeerFromExceptions: {
}, modifiedPeer: {
exceptionController.navigationPresentation = .modal
self?.push(exceptionController)
})
exceptionController.navigationPresentation = .modal
self?.push(exceptionController)
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor)
}, action: { _, f in
f(.default)
})))
let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: Int32.max).startStandalone()
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: Int32.max).startStandalone()
let iconColor: UIColor = .white
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
let iconColor: UIColor = .white
self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
})))
c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true)
}
})))
c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true)
}
})))
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor)
@ -4973,13 +4975,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = context.engine.peers.setForumChannelTopicClosed(id: peer.id, threadId: threadId, isClosed: !threadData.isClosed).startStandalone()
})))
}
// if channel.hasPermission(.deleteAllMessages) {
// items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak chatListController] _, f in
// f(.default)
//
// chatListController?.deletePeerThread(peerId: peerId, threadId: threadId)
// })))
// }
}
return items

View File

@ -239,6 +239,15 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
useBrowserScreen = true
}
if useBrowserScreen {
if let navigationController = params.navigationController, let minimizedContainer = navigationController.minimizedContainer {
for controller in minimizedContainer.controllers {
if let controller = controller as? BrowserScreen, controller.subject.fileId == file.fileId {
navigationController.maximizeViewController(controller, animated: true)
return
}
}
}
let subject: BrowserScreen.Subject
if file.mimeType == "application/pdf" {
subject = .pdfDocument(file: file, canShare: canShare)

View File

@ -48,6 +48,14 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always))
}
case let .withAttachBot(attachBotStart):
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
}
case let .withBotApp(botAppStart):
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), botAppStart: botAppStart))
}
default:
break
}
@ -92,15 +100,13 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
let sourceLocation = InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group)
let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation)
(controller.navigationController as? NavigationController)?.pushViewController(browserController, animated: true)
// case let .join(link):
// controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
// openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
// }, parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
case .boost, .chatFolder, .join:
if let navigationController = controller.navigationController as? NavigationController {
openResolvedUrlImpl(result, context: context, urlContext: peerId.flatMap { .chat(peerId: $0, message: nil, updatedPresentationData: nil) } ?? .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigateToPeer in
openResolvedPeerImpl(peer, navigateToPeer)
}, sendFile: nil, sendSticker: nil, sendEmoji: nil, joinVoiceChat: nil, present: { c, a in }, dismissInput: {}, contentContext: nil, progress: Promise(), completion: nil)
}, sendFile: nil, sendSticker: nil, sendEmoji: nil, joinVoiceChat: nil, present: { c, a in
controller.present(c, in: .window(.root), with: a)
}, dismissInput: {}, contentContext: nil, progress: Promise(), completion: nil)
}
default:
break