Various improvements

This commit is contained in:
Ilya Laktyushin 2024-06-17 21:15:41 +04:00
parent 10275ad968
commit ad3bed0a9f
22 changed files with 267 additions and 138 deletions

View File

@ -12217,6 +12217,17 @@ Sorry for the inconvenience.";
"Chat.Context.Phone.NotOnTelegram" = "This number is not on Telegram.";
"Chat.Context.Phone.ViewProfile" = "View Profile";
"Chat.Context.Username.SendMessage" = "Send Message";
"Chat.Context.Username.OpenGroup" = "Open Group";
"Chat.Context.Username.OpenChannel" = "Open Channel";
"Chat.Context.Username.Copy" = "Copy Username";
"Chat.Context.Username.NotOnTelegram" = "This user doesn't exist on Telegram.";
"Chat.Context.Hashtag.Search" = "View Profile";
"Chat.Context.Hashtag.Copy" = "Copy Hashtag";
"Chat.Context.Card.Copy" = "Copy Card Number";
"Message.FactCheck" = "Fact Check";
"Message.FactCheck.WhatIsThis" = "what's this?";

View File

@ -110,7 +110,7 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate
private var textSize: CGSize = .zero
public override func sizeThatFits(_ size: CGSize) -> CGSize {
if self.linkEntity.webpage != nil, let image = self.linkEntity.renderImage {
if self.linkEntity.webpage != nil, let image = self.linkEntity.whiteImage {
self.imageView.frame = CGRect(origin: .zero, size: image.size)
return image.size
} else {
@ -118,7 +118,7 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate
self.textSize = result
let widthExtension = result.height * 0.65
result.width = floorToScreenPixels(max(224.0, ceil(result.width) + 20.0) + widthExtension)
result.width = floorToScreenPixels(max(104.0, ceil(result.width) + 20.0) + widthExtension)
result.height = ceil(result.height * 1.2);
return result;
}
@ -260,7 +260,11 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate
self.blurredBackgroundView.isHidden = true
self.iconView.isHidden = true
self.imageView.image = self.linkEntity.style == .white ? self.linkEntity.renderImage : self.linkEntity.secondaryRenderImage
if self.linkEntity.style == .white && self.imageView.image !== self.linkEntity.whiteImage {
self.imageView.image = self.linkEntity.whiteImage
} else if self.linkEntity.style == .black && self.imageView.image !== self.linkEntity.blackImage {
self.imageView.image = self.linkEntity.blackImage
}
} else {
self.textView.isHidden = false
self.textView.frameInsets = UIEdgeInsets(top: 0.15, left: 0.0, bottom: 0.15, right: 0.0)

View File

@ -2467,42 +2467,41 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
})))
}
if selectionCount > 1 {
items.append(.action(ContextMenuActionItem(text: "Send Without Grouping", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/GroupingOff"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
self?.groupedValue = false
self?.controllerNode.send(asFile: false, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {})
})))
// if !items.isEmpty {
// items.append(.separator)
// }
// items.append(.action(ContextMenuActionItem(text: strings.Attachment_Grouped, icon: { theme in
// if !grouped {
// return nil
// }
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
// items.append(.action(ContextMenuActionItem(text: "Send Without Grouping", icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/GroupingOff"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, f in
// f(.default)
//
// self?.groupedValue = true
// })))
// items.append(.action(ContextMenuActionItem(text: strings.Attachment_Ungrouped, icon: { theme in
// if grouped {
// return nil
// }
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, f in
// f(.default)
//
// self?.groupedValue = false
// self?.controllerNode.send(asFile: false, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {})
// })))
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: strings.Attachment_Grouped, icon: { theme in
if !grouped {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
self?.groupedValue = true
})))
items.append(.action(ContextMenuActionItem(text: strings.Attachment_Ungrouped, icon: { theme in
if grouped {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
self?.groupedValue = false
})))
}
let isPaidAvailable = true
let isPaidAvailable = !"".isEmpty
if isSpoilerAvailable || isPaidAvailable || (selectionCount > 0 && isCaptionAboveMediaAvailable) {
if !items.isEmpty {
items.append(.separator)

View File

@ -23,6 +23,7 @@ public struct UserLimitsConfiguration: Equatable {
public var maxStoriesWeeklyCount: Int32
public var maxStoriesMonthlyCount: Int32
public var maxStoriesSuggestedReactions: Int32
public var maxStoriesLinksCount: Int32
public var maxGiveawayChannelsCount: Int32
public var maxGiveawayCountriesCount: Int32
public var maxGiveawayPeriodSeconds: Int32
@ -51,6 +52,7 @@ public struct UserLimitsConfiguration: Equatable {
maxStoriesWeeklyCount: 7,
maxStoriesMonthlyCount: 30,
maxStoriesSuggestedReactions: 1,
maxStoriesLinksCount: 3,
maxGiveawayChannelsCount: 10,
maxGiveawayCountriesCount: 10,
maxGiveawayPeriodSeconds: 86400 * 31,
@ -80,6 +82,7 @@ public struct UserLimitsConfiguration: Equatable {
maxStoriesWeeklyCount: Int32,
maxStoriesMonthlyCount: Int32,
maxStoriesSuggestedReactions: Int32,
maxStoriesLinksCount: Int32,
maxGiveawayChannelsCount: Int32,
maxGiveawayCountriesCount: Int32,
maxGiveawayPeriodSeconds: Int32,
@ -106,6 +109,7 @@ public struct UserLimitsConfiguration: Equatable {
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
self.maxStoriesLinksCount = maxStoriesLinksCount
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
@ -158,6 +162,7 @@ extension UserLimitsConfiguration {
self.maxStoriesWeeklyCount = getValue("stories_sent_weekly_limit", orElse: defaultValue.maxStoriesWeeklyCount)
self.maxStoriesMonthlyCount = getValue("stories_sent_monthly_limit", orElse: defaultValue.maxStoriesMonthlyCount)
self.maxStoriesSuggestedReactions = getValue("stories_suggested_reactions_limit", orElse: defaultValue.maxStoriesMonthlyCount)
self.maxStoriesLinksCount = getGeneralValue("stories_area_url_max", orElse: defaultValue.maxStoriesLinksCount)
self.maxGiveawayChannelsCount = getGeneralValue("giveaway_add_peers_max", orElse: defaultValue.maxGiveawayChannelsCount)
self.maxGiveawayCountriesCount = getGeneralValue("giveaway_countries_max", orElse: defaultValue.maxGiveawayCountriesCount)
self.maxGiveawayPeriodSeconds = getGeneralValue("giveaway_period_max", orElse: defaultValue.maxGiveawayPeriodSeconds)

View File

@ -57,6 +57,7 @@ public enum EngineConfiguration {
public let maxStoriesWeeklyCount: Int32
public let maxStoriesMonthlyCount: Int32
public let maxStoriesSuggestedReactions: Int32
public let maxStoriesLinksCount: Int32
public let maxGiveawayChannelsCount: Int32
public let maxGiveawayCountriesCount: Int32
public let maxGiveawayPeriodSeconds: Int32
@ -88,6 +89,7 @@ public enum EngineConfiguration {
maxStoriesWeeklyCount: Int32,
maxStoriesMonthlyCount: Int32,
maxStoriesSuggestedReactions: Int32,
maxStoriesLinksCount: Int32,
maxGiveawayChannelsCount: Int32,
maxGiveawayCountriesCount: Int32,
maxGiveawayPeriodSeconds: Int32,
@ -114,6 +116,7 @@ public enum EngineConfiguration {
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
self.maxStoriesLinksCount = maxStoriesLinksCount
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
@ -176,6 +179,7 @@ public extension EngineConfiguration.UserLimits {
maxStoriesWeeklyCount: userLimitsConfiguration.maxStoriesWeeklyCount,
maxStoriesMonthlyCount: userLimitsConfiguration.maxStoriesMonthlyCount,
maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions,
maxStoriesLinksCount: userLimitsConfiguration.maxStoriesLinksCount,
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount,
maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount,
maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds,

View File

@ -107,15 +107,15 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if selectedMedia == nil {
for media in item.message.media {
if let telegramImage = media as? TelegramMediaImage {
#if DEBUG
if item.message.text == "#" {
selectedMedia = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: 100, startParam: "", extendedMedia: .preview(dimensions: telegramImage.representations.first?.dimensions ?? PixelDimensions(width: 1, height: 1), immediateThumbnailData: telegramImage.immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
} else {
selectedMedia = telegramImage
}
#else
// #if DEBUG
// if item.message.text == "#" {
// selectedMedia = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: 100, startParam: "", extendedMedia: .preview(dimensions: telegramImage.representations.first?.dimensions ?? PixelDimensions(width: 1, height: 1), immediateThumbnailData: telegramImage.immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
// } else {
// selectedMedia = telegramImage
// }
// #else
selectedMedia = telegramImage
#endif
// #endif
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}

View File

@ -24,6 +24,8 @@ public final class DrawingLinkEntity: DrawingEntity, Codable {
case scale
case rotation
case renderImage
case whiteImage
case blackImage
}
public enum Style: Codable, Equatable {
@ -75,9 +77,10 @@ public final class DrawingLinkEntity: DrawingEntity, Codable {
return self.position
}
public var renderImage: UIImage?
public var secondaryRenderImage: UIImage?
public var whiteImage: UIImage?
public var blackImage: UIImage?
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
@ -131,6 +134,15 @@ public final class DrawingLinkEntity: DrawingEntity, Codable {
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let imagePath = try container.decodeIfPresent(String.self, forKey: .whiteImage), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
self.whiteImage = image
}
if let imagePath = try container.decodeIfPresent(String.self, forKey: .blackImage), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
self.blackImage = image
}
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
@ -161,8 +173,29 @@ public final class DrawingLinkEntity: DrawingEntity, Codable {
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(self.rotation, forKey: .rotation)
if let image = self.whiteImage {
let imagePath = "\(self.uuid)_white.png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() {
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .whiteImage)
}
}
if let image = self.blackImage {
let imagePath = "\(self.uuid)black.png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() {
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .blackImage)
}
}
if let renderImage = self.renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
@ -177,6 +210,8 @@ public final class DrawingLinkEntity: DrawingEntity, Codable {
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.whiteImage = self.whiteImage
newEntity.blackImage = self.blackImage
return newEntity
}

View File

@ -5,11 +5,11 @@ import AccountContext
import Postbox
import TelegramCore
private func entitiesPath() -> String {
func entitiesPath() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/mediaEntities"
}
private func fullEntityMediaPath(_ path: String) -> String {
func fullEntityMediaPath(_ path: String) -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/mediaEntities/" + path
}

View File

@ -343,7 +343,7 @@ public final class DrawingMessageRenderer {
}
public func render(completion: @escaping (Result) -> Void) {
Queue.mainQueue().after(0.066) {
Queue.mainQueue().after(0.12) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let defaultPresentationData = defaultPresentationData()

View File

@ -23,6 +23,7 @@ import MediaEditor
import UrlEscaping
private let linkTag = GenericComponentViewTag()
private let nameTag = GenericComponentViewTag()
private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -130,15 +131,16 @@ private final class SheetContent: CombinedComponent {
Text(
text: strings.Common_Done,
font: Font.bold(17.0),
color: state.link.isEmpty ? theme.actionSheet.secondaryTextColor : theme.actionSheet.controlAccentColor
color: isValidLink ? theme.actionSheet.controlAccentColor : theme.actionSheet.secondaryTextColor
)
),
isEnabled: isValidLink,
action: { [weak state] in
if let controller = controller() as? CreateLinkScreen {
state?.complete(controller: controller)
if let controller = controller() as? CreateLinkScreen, let state {
if state.complete(controller: controller) {
component.dismiss()
}
}
component.dismiss()
}
),
availableSize: context.availableSize,
@ -198,6 +200,11 @@ private final class SheetContent: CombinedComponent {
state?.link = text
state?.updated()
},
textReturned: { [weak state] in
if let controller = controller() as? CreateLinkScreen {
state?.switchToNextField(controller: controller)
}
},
tag: linkTag
)
)
@ -263,7 +270,15 @@ private final class SheetContent: CombinedComponent {
placeholderText: strings.MediaEditor_Link_LinkName_Placeholder,
textUpdated: { [weak state] text in
state?.name = text
}
},
textReturned: { [weak state] in
if let controller = controller() as? CreateLinkScreen, let state {
if state.complete(controller: controller) {
component.dismiss()
}
}
},
tag: nameTag
)
)
)
@ -433,7 +448,21 @@ private final class CreateLinkSheetComponent: CombinedComponent {
})
}
func complete(controller: CreateLinkScreen) {
func switchToNextField(controller: CreateLinkScreen) {
if let view = controller.node.hostView.findTaggedView(tag: nameTag) as? LinkFieldComponent.View {
view.activateInput()
}
}
func complete(controller: CreateLinkScreen) -> Bool {
let explicitLink = explicitUrl(self.link)
if !isValidUrl(explicitLink) {
if let view = controller.node.hostView.findTaggedView(tag: linkTag) as? LinkFieldComponent.View {
view.animateError()
}
return false
}
let text = !self.name.isEmpty ? self.name : self.link
var effectiveMedia: TelegramMediaWebpage?
@ -466,6 +495,7 @@ private final class CreateLinkSheetComponent: CombinedComponent {
)
)
})
return true
}
}
@ -638,6 +668,7 @@ private final class LinkFieldComponent: Component {
let link: Bool
let placeholderText: String
let textUpdated: (String) -> Void
let textReturned: () -> Void
let tag: AnyObject?
init(
@ -647,6 +678,7 @@ private final class LinkFieldComponent: Component {
link: Bool,
placeholderText: String,
textUpdated: @escaping (String) -> Void,
textReturned: @escaping () -> Void,
tag: AnyObject? = nil
) {
self.textColor = textColor
@ -655,6 +687,7 @@ private final class LinkFieldComponent: Component {
self.link = link
self.placeholderText = placeholderText
self.textUpdated = textUpdated
self.textReturned = textReturned
self.tag = tag
}
@ -714,14 +747,14 @@ private final class LinkFieldComponent: Component {
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == "\n" {
self.component?.textReturned()
return false
}
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
if let component = self.component, !component.link && newText.count > 48 {
textField.layer.addShakeAnimation()
let hapticFeedback = HapticFeedback()
hapticFeedback.error()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
let _ = hapticFeedback
})
self.animateError()
return false
}
return true
@ -735,6 +768,15 @@ private final class LinkFieldComponent: Component {
self.textField.selectAll(nil)
}
func animateError() {
self.textField.layer.addShakeAnimation()
let hapticFeedback = HapticFeedback()
hapticFeedback.error()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
let _ = hapticFeedback
})
}
func update(component: LinkFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.textField.textColor = component.textColor
self.textField.text = component.text
@ -747,6 +789,8 @@ private final class LinkFieldComponent: Component {
self.textField.autocorrectionType = .no
self.textField.autocapitalizationType = .none
self.textField.textContentType = .URL
} else {
self.textField.returnKeyType = .done
}
self.component = component

View File

@ -4486,7 +4486,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
if existingEntity == nil {
let maxLinkCount = 3
let maxLinkCount = self.context.userLimits.maxStoriesLinksCount
var currentLinkCount = 0
self.entitiesView.eachView { entityView in
if entityView.entity is DrawingLinkEntity {
@ -4528,8 +4528,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
let entity = DrawingLinkEntity(url: result.url, name: result.name, webpage: result.webpage, positionBelowText: result.positionBelowText, largeMedia: result.largeMedia, style: style)
entity.renderImage = result.image
entity.secondaryRenderImage = result.nightImage
entity.whiteImage = result.image
entity.blackImage = result.nightImage
if let existingEntity {
self.entitiesView.remove(uuid: existingEntity.uuid, animated: true)

View File

@ -2565,7 +2565,7 @@ final class StoryStickersContentView: UIView, EmojiCustomContentView {
InteractiveStickerButtonContent(
theme: theme,
title: strings.MediaEditor_AddLink,
iconName: "Premium/Link",
iconName: self.isPremium ? "Media Editor/Link" : "Media Editor/LinkLocked",
useOpaqueTheme: useOpaqueTheme,
tintContainerView: self.tintContainerView
)

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "link.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "linklocked.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -52,26 +52,8 @@ extension ChatControllerImpl {
var items: [ContextMenuItem] = []
if let info {
for url in info.urls {
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_AddToContacts, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
guard let self else {
return
}
f(.default)
self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: false, external: false, message: message))
}))
)
}
if !items.isEmpty {
items.append(.separator)
}
}
items.append(
.action(ContextMenuActionItem(text: "Copy Card Number", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Card_Copy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {

View File

@ -44,7 +44,7 @@ extension ChatControllerImpl {
var items: [ContextMenuItem] = []
items.append(
.action(ContextMenuActionItem(text: "Search", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Hashtag_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
guard let self else {
return
}
@ -54,7 +54,7 @@ extension ChatControllerImpl {
)
items.append(
.action(ContextMenuActionItem(text: "Copy Hashtag", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Hashtag_Copy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {

View File

@ -80,7 +80,7 @@ extension ChatControllerImpl {
if canAddToReadingList {
items.append(
.action(ContextMenuActionItem(text: "Add to Reading List", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_AddToReadingList, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
if let link = URL(string: url) {
@ -88,16 +88,10 @@ extension ChatControllerImpl {
}
}))
)
// / items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
// // actionSheet?.dismissAnimated()
// // if let link = URL(string: url) {
// // let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
// // }
// // }))
}
items.append(
.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {

View File

@ -68,19 +68,46 @@ extension ChatControllerImpl {
var items: [ContextMenuItem] = []
if let peer {
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_SendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
}))
)
if case .user = peer {
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Username_SendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
}))
)
} else {
var isGroup = true
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
}
let openTitle: String
let openIcon: UIImage?
if isGroup {
openTitle = self.presentationData.strings.Chat_Context_Username_OpenGroup
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Groups")
} else {
openTitle = self.presentationData.strings.Chat_Context_Username_OpenChannel
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Channels")
}
items.append(
.action(ContextMenuActionItem(text: openTitle, icon: { theme in return generateTintedImage(image: openIcon, color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
}))
)
}
}
items.append(
.action(ContextMenuActionItem(text: "Copy Username", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Username_Copy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
@ -117,7 +144,7 @@ extension ChatControllerImpl {
} else {
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(
.action(ContextMenuActionItem(text: "This user doesn't exist on Telegram.", textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction))
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Username_NotOnTelegram, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction))
)
}

View File

@ -902,40 +902,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
#if DEBUG
if message.text == "#", let telegramImage = message.media.first(where: { $0 is TelegramMediaImage }) as? TelegramMediaImage {
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: 100, startParam: "", extendedMedia: .preview(dimensions: telegramImage.representations.first?.dimensions ?? PixelDimensions(width: 1, height: 1), immediateThumbnailData: telegramImage.immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
let inputData = Promise<BotCheckoutController.InputData?>()
inputData.set(.single(
BotCheckoutController.InputData(
form: BotPaymentForm(id: 123, canSaveCredentials: false, passwordMissing: false, invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: 100)], tip: nil, termsInfo: nil), paymentBotId: message.id.peerId, providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: []),
validatedFormInfo: nil,
botPeer: nil
)))
if invoice.currency == "XTR", let starsContext = strongSelf.context.starsContext {
let starsInputData = combineLatest(
inputData.get(),
starsContext.state
)
|> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
if let data, let state {
return (state, data.form, data.botPeer)
} else {
return nil
}
}
let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self else {
return
}
let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(message.id), inputData: starsInputData, completion: { _ in })
strongSelf.push(controller)
})
}
return true
}
#endif
// #if DEBUG
// if message.text == "#", let telegramImage = message.media.first(where: { $0 is TelegramMediaImage }) as? TelegramMediaImage {
// let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: 100, startParam: "", extendedMedia: .preview(dimensions: telegramImage.representations.first?.dimensions ?? PixelDimensions(width: 1, height: 1), immediateThumbnailData: telegramImage.immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
//
// let inputData = Promise<BotCheckoutController.InputData?>()
// inputData.set(.single(
// BotCheckoutController.InputData(
// form: BotPaymentForm(id: 123, canSaveCredentials: false, passwordMissing: false, invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: 100)], tip: nil, termsInfo: nil), paymentBotId: message.id.peerId, providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: []),
// validatedFormInfo: nil,
// botPeer: nil
// )))
// if invoice.currency == "XTR", let starsContext = strongSelf.context.starsContext {
// let starsInputData = combineLatest(
// inputData.get(),
// starsContext.state
// )
// |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
// if let data, let state {
// return (state, data.form, data.botPeer)
// } else {
// return nil
// }
// }
// let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
// guard let strongSelf = self else {
// return
// }
// let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(message.id), inputData: starsInputData, completion: { _ in })
// strongSelf.push(controller)
// })
// }
// return true
// }
// #endif
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {

View File

@ -26,7 +26,7 @@ public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": tr
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), let scheme = url.scheme?.lowercased(), let requiresTopLevelDomain = validSchemes[scheme], let host = url.host, (!requiresTopLevelDomain || host.contains(".")) && url.user == nil {
if requiresTopLevelDomain {
let components = host.components(separatedBy: ".")
let domain = (components.first ?? "")
let domain = (components.last ?? "")
if domain.isEmpty {
return false
}