mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Various improvements
This commit is contained in:
parent
10275ad968
commit
ad3bed0a9f
@ -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?";
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Media Editor/Link.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/Link.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "link.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Link.imageset/link.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Link.imageset/link.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Media Editor/LinkLocked.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/LinkLocked.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "linklocked.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/LinkLocked.imageset/linklocked.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/LinkLocked.imageset/linklocked.pdf
vendored
Normal file
Binary file not shown.
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user