diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c7e9962184..5191eb298c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15813,6 +15813,8 @@ Error: %8$@"; "Notification.CopyProtection.Disabled" = "%1$@ enabled sharing in this chat"; "Notification.CopyProtection.DisabledYou" = "You enabled sharing in this chat"; "Notification.CopyProtection.StillEnabled" = "Sharing in this chat is still disabled"; +"Notification.CopyProtection.Request" = "%@ would like to enable sharing in this chat"; +"Notification.CopyProtection.RequestYou" = "You suggested enabling sharing in this chat"; "Gallery.CreateSticker" = "Create Sticker"; @@ -15834,3 +15836,86 @@ Error: %8$@"; "Channel.AdminLog.MessageRankNew" = "changed member tag:\n%1$@"; "TextFormat.Date" = "Date"; + +"Gift.Craft.UnavailableBlockchain.Title" = "Crafting Unavailable"; +"Gift.Craft.UnavailableBlockchain.Text" = "This gift can be selected as the main gift of crafting."; + +"AuthConfirmation.Emoji.Title" = "Tap the emoji shown\non your other device"; +"AuthConfirmation.Emoji.Description" = "Telegram wants to make sure it's really you."; + +"Chat.TagPlaceholder" = "Tag"; + +"Chat.EnableSharingOffer.Reject" = "Reject"; +"Chat.EnableSharingOffer.Accept" = "Accept"; + +"Chat.EnableSharingOffer.AcceptConfirmation.Title" = "Enable Sharing"; +"Chat.EnableSharingOffer.AcceptConfirmation.Text" = "Are you sure you want to enable sharing?"; +"Chat.EnableSharingOffer.AcceptConfirmation.Accept" = "Enable"; + +"Chat.EnableSharingOffer.RejectConfirmation.Title" = "Disable Sharing"; +"Chat.EnableSharingOffer.RejectConfirmation.Text" = "Are you sure you want to keep sharing disabled?"; +"Chat.EnableSharingOffer.RejectConfirmation.Reject" = "Keep Disabled"; + +"Chat.TagUpdated.Added" = "Tag added"; +"Chat.TagUpdated.Removed" = "Tag removed"; + +"Conversation.FormatDate.Title" = "Date"; +"Conversation.FormatDate.AddDate" = "Add Date"; +"Conversation.FormatDate.EditDate" = "Edit Date"; +"Conversation.FormatDate.RemoveDate" = "Remove Date"; + +"Conversation.CopyProtectionInfoPrivate" = "**%@** disabled copying and forwarding in this chat."; +"Conversation.CopyProtectionInfoPrivateYou" = "You disabled copying and forwarding in this chat."; + +"Chat.JoinedGroup.Text" = "You joined the group"; +"Chat.JoinedGroup.AddTag" = "Add Tag"; + +"GroupPermission.NoEditRank" = "no tag"; + +"Channel.BanUser.PermissionEditRank" = "Edit Member Tag"; +"Channel.BanUser.PermissionEditOwnRank" = "Edit Own Tags"; + +"Channel.Management.AddAdmin" = "Add Admin"; +"Channel.Management.SaveChanges" = "Save Changes"; + +"Preview.Gif.AddCaption" = "Add Caption"; +"Preview.Gif.Edit" = "Edit"; + +"GroupPermission.Member" = "Member"; +"GroupPermission.SaveChanges" = "Save Changes"; +"GroupPermission.AddException" = "Add Exception"; + +"GroupInfo.ActionSend" = "Send Message"; +"GroupInfo.ActionEditRank" = "Edit Member Tag"; +"GroupInfo.ActionRemove" = "Remove"; + +"PeerInfo.EnableSharing" = "Enable Sharing"; +"PeerInfo.DisableSharing" = "Disable Sharing"; + +"Chat.Context.Date.Copy" = "Copy Date"; +"Chat.Context.Date.AddToCalendar" = "Add to Calendar"; +"Chat.Context.Date.SetReminder" = "Set a Reminder"; + +"Conversation.DateCopied" = "Date copied to clipboard"; +"Conversation.DateReminderSet" = "You have set a reminder in [Saved Messages]()."; + +"RankInfo.Member.Title" = "Member Tag"; +"RankInfo.Member.Text" = "This gray tag %1$@ is **%2$@'s** member tag. **%3$@** is a member of **%4$@**."; +"RankInfo.Admin.Title" = "Admin Tag"; +"RankInfo.Admin.Text" = "This green tag %1$@ is **%2$@'s** admin tag. **%3$@** is an administrator of **%4$@**."; +"RankInfo.Owner.Title" = "Owner Tag"; +"RankInfo.Owner.Text" = "This purple tag %1$@ is **%2$@'s** owner tag. **%3$@** is the owner of **%4$@**."; +"RankInfo.ChangeInfo" = "Only admins can assign member tags in this group."; +"RankInfo.MemberTag" = "Admin Tag"; +"RankInfo.AdminTag" = "Admin Tag"; +"RankInfo.OwnerTag" = "Owner Tag"; +"RankInfo.SetMyTag" = "Set My Tag"; + +"EditRank.Title" = "Member Tag"; +"EditRank.Placeholder" = "Add Tag"; +"EditRank.Info" = "Add short tag next to %@'s name."; +"EditRank.InfoYou" = "Share your role, title, or how you're known in this group. Your tag is visible to all members."; +"EditRank.AddLater" = "Add Later"; +"EditRank.AddTag" = "Add Tag"; +"EditRank.EditTag" = "Edit Tag"; +"EditRank.RemoveTag" = "Remove Tag"; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 706b8b719e..284cade852 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -5258,10 +5258,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { canEditRank = true } - //TODO:localize if canEditRank { let context = self.context - let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: "You joined the group", cancel: "Add Tag", destructive: false), elevatedLayout: true, action: { [weak self] action in + let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Chat_JoinedGroup_Text, cancel: self.presentationData.strings.Chat_JoinedGroup_AddTag, destructive: false), elevatedLayout: true, action: { [weak self] action in if let self, case .undo = action { let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peerId, participantId: context.account.peerId, rank: nil, role: .member) self.push(tagController) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index fdf3ca2fac..e1632e50f7 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -1617,8 +1617,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } } - //TODO:localize - var footerButtonTitle: String = "Save Changes" + var footerButtonTitle: String = presentationData.strings.Channel_Management_SaveChanges var footerItem: ItemListControllerFooterItem? var isCreator = false @@ -1667,8 +1666,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD }) } else { title = presentationData.strings.Channel_Management_AddModerator - footerButtonTitle = "Add Admin" - //TODO:localize + footerButtonTitle = presentationData.strings.Channel_Management_AddAdmin } } else { switch initialParticipant { diff --git a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift index 990faa7d0f..2a7f34d471 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift @@ -637,7 +637,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen } let memberPresence = peersMap.1 - var footerButtonTitle: String = "Save Changes" + var footerButtonTitle: String = presentationData.strings.GroupPermission_SaveChanges let rightButtonActionImpl = { let _ = (peerView.get() @@ -819,16 +819,15 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen }) } - //TODO:localize let title: String if editMember { - title = "Member" + title = presentationData.strings.GroupPermission_Member } else { if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo, _, _) = initialParticipant, banInfo != nil { title = presentationData.strings.GroupPermission_Title } else { title = presentationData.strings.GroupPermission_NewTitle - footerButtonTitle = "Add Exception" + footerButtonTitle = presentationData.strings.GroupPermission_AddException } } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index d5c725294f..bd04ccb360 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -535,11 +535,10 @@ func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatB } else if right.contains(.banSendInstantVideos) { return strings.Channel_BanUser_PermissionSendVideoMessage } else if right.contains(.banEditRank) { - //TODO:localize if defaultPermissions { - return "Edit Own Tags" + return strings.Channel_BanUser_PermissionEditOwnRank } else { - return "Edit Member Tag" + return strings.Channel_BanUser_PermissionEditRank } } else { return "" @@ -578,8 +577,7 @@ func compactStringForGroupPermission(strings: PresentationStrings, right: Telegr } else if right.contains(.banManageTopics) { return strings.GroupPermission_NoManageTopics } else if right.contains(.banEditRank) { - //TODO:localize - return "no tag" + return strings.GroupPermission_NoEditRank } else { return "" } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 440b5926de..30fe62074e 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -845,7 +845,7 @@ public enum PremiumPerk: CaseIterable { case .todo: return "Premium/Perk/Todo" case .copyProtection: - return "Premium/Perk/CopyProtection" + return "Premium/Perk/NoForward" case .businessLocation: return "Premium/BusinessPerk/Location" case .businessHours: diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 136bdfb94d..cf87b35f4d 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -864,6 +864,26 @@ public class PremiumLimitsListScreen: ViewController { ) ) + availableItems[.copyProtection] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.copyProtection, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + model: .island, + videoFile: videos["pm_noforwards"], + decoration: .badgeStars + )), + title: strings.Premium_CopyProtection, + text: strings.Premium_CopyProtectionInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.business] = DemoPagerComponent.Item( AnyComponentWithIdentity( id: PremiumDemoScreen.Subject.business, diff --git a/submodules/PremiumUI/Sources/TodoChecksView.swift b/submodules/PremiumUI/Sources/TodoChecksView.swift index a25203f964..1ff026afc6 100644 --- a/submodules/PremiumUI/Sources/TodoChecksView.swift +++ b/submodules/PremiumUI/Sources/TodoChecksView.swift @@ -159,6 +159,7 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView { private var displayLink: SharedDisplayLinkDriver.Link? + private var containerView = UIView() private var particleSet: ParticleSet? private var particleLayers: [CheckLayer] = [] private var particleMap: [Int64: CheckLayer] = [:] @@ -174,6 +175,10 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView { self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] delta in self?.update(deltaTime: CGFloat(delta)) }) + self.displayLink?.isPaused = true + + self.containerView.frame = CGRect(origin: .zero, size: frame.size) + self.addSubview(self.containerView) } required init?(coder: NSCoder) { @@ -218,7 +223,7 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView { particleLayer.theme = CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false) particleLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 22.0, height: 22.0)) self.particleLayers.append(particleLayer) - self.layer.addSublayer(particleLayer) + self.containerView.layer.addSublayer(particleLayer) } self.particleMap[particle.id] = particleLayer } @@ -251,14 +256,16 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView { self.displayLink?.isPaused = !visible -// let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) -// transition.updateAlpha(layer: self.containerView.layer, alpha: visible ? 1.0 : 0.0, completion: { [weak self] finished in -// if let strongSelf = self, finished && !visible && !strongSelf.visible { -// for view in strongSelf.containerView.subviews { -// view.removeFromSuperview() -// } -// } -// }) + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + transition.updateAlpha(layer: self.containerView.layer, alpha: visible ? 1.0 : 0.0, completion: { [weak self] finished in + if let strongSelf = self, finished && !visible && !strongSelf.visible { + for particleLayer in strongSelf.particleLayers { + particleLayer.removeFromSuperlayer() + } + strongSelf.particleLayers.removeAll() + strongSelf.particleMap.removeAll() + } + }) } func resetAnimation() { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index c1d95c434c..65aa5824fa 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1716,7 +1716,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON" } - let timeString = "[TODO]" + let timeString = "" var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) attributes[1] = boldAttributes @@ -1755,7 +1755,6 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_GroupCreatorChangeApplied(authorName, targetName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id), (1, groupCreatorChange.targetPeerId)])) } case let .copyProtectionToggle(previousValue, newValue): - //TODO:localize if previousValue == newValue && newValue { attributedString = NSAttributedString(string: strings.Notification_CopyProtection_StillEnabled, font: titleFont, textColor: primaryTextColor) } else if message.author?.id == accountPeerId { @@ -1775,12 +1774,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } case .copyProtectionRequest: - //TODO:localize let peerName = message.peers[message.id.peerId].flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" if message.author?.id == accountPeerId { - attributedString = NSAttributedString(string: "You suggested enabling sharing in this chat", font: titleFont, textColor: primaryTextColor) + attributedString = NSAttributedString(string: strings.Notification_CopyProtection_RequestYou, font: titleFont, textColor: primaryTextColor) } else { - attributedString = NSAttributedString(string: "\(peerName) would like to enable sharing in this chat", font: titleFont, textColor: primaryTextColor) + attributedString = NSAttributedString(string: strings.Notification_CopyProtection_Request(peerName).string, font: titleFont, textColor: primaryTextColor) } case .unknown: attributedString = nil diff --git a/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD b/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD index b7dc9015d1..0dd57b9b18 100644 --- a/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD +++ b/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/PhoneNumberFormat", "//submodules/ContextUI", "//submodules/AccountUtils", + "//submodules/ActivityIndicator", "//submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem", ], visibility = [ diff --git a/submodules/TelegramUI/Components/AuthConfirmationScreen/Sources/AuthConfirmationScreen.swift b/submodules/TelegramUI/Components/AuthConfirmationScreen/Sources/AuthConfirmationScreen.swift index 16c668ccb1..8b97bdbdf8 100644 --- a/submodules/TelegramUI/Components/AuthConfirmationScreen/Sources/AuthConfirmationScreen.swift +++ b/submodules/TelegramUI/Components/AuthConfirmationScreen/Sources/AuthConfirmationScreen.swift @@ -24,6 +24,7 @@ import ContextUI import AccountUtils import GlassBackgroundComponent import AccountPeerContextItem +import ActivityIndicator private final class AuthConfirmationSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -65,6 +66,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent { var displayEmoji = false var matchCodes: [String]? + var selectedMatchCode: String? init(context: AccountContext, subject: MessageActionUrlAuthResult) { self.context = context @@ -265,11 +267,13 @@ private final class AuthConfirmationSheetContent: CombinedComponent { state.displayPhoneNumberConfirmation(commit: { sharePhoneNumber in component.completion(accountContext, accountPeer, .accept(allowWriteAccess: allowWrite, sharePhoneNumber: sharePhoneNumber, matchCode: matchCode)) state.inProgress = true + state.selectedMatchCode = matchCode state.updated() }) } else { component.completion(accountContext, accountPeer, .accept(allowWriteAccess: allowWrite, sharePhoneNumber: false, matchCode: matchCode)) state.inProgress = true + state.selectedMatchCode = matchCode state.updated() } } @@ -277,12 +281,11 @@ private final class AuthConfirmationSheetContent: CombinedComponent { var contentHeight: CGFloat = 32.0 if state.displayEmoji, let matchCodes = state.matchCodes { - //TODO:localize contentHeight += 36.0 let emojiTitle = emojiTitle.update( component: MultilineTextComponent( - text: .markdown(text: "Tap the emoji shown\non your other device", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })), + text: .markdown(text: strings.AuthConfirmation_Emoji_Title, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })), horizontalAlignment: .center, maximumNumberOfLines: 2, lineSpacing: 0.2 @@ -300,7 +303,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent { let emojiDescription = emojiDescription.update( component: MultilineTextComponent( - text: .markdown(text: "Telegram wants to make sure it's really you.", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })), + text: .markdown(text: strings.AuthConfirmation_Emoji_Description, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })), horizontalAlignment: .center, maximumNumberOfLines: 3, lineSpacing: 0.2 @@ -322,18 +325,30 @@ private final class AuthConfirmationSheetContent: CombinedComponent { let totalWidth = CGFloat(matchCodes.count) * emojiSize.width + CGFloat(matchCodes.count - 1) * emojiSpacing var emojiOriginX = context.availableSize.width / 2.0 - totalWidth / 2.0 for code in matchCodes { + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity(id: "background", component: AnyComponent( + FilledRoundedRectangleComponent(color: theme.list.itemBlocksBackgroundColor, cornerRadius: .minEdge, smoothCorners: false) + )) + ) + if state.selectedMatchCode == code { + items.append( + AnyComponentWithIdentity(id: "progress", component: AnyComponent( + ActivityIndicatorComponent(color: theme.list.itemAccentColor) + )) + ) + } + items.append( + AnyComponentWithIdentity(id: "icon", component: AnyComponent( + Text(text: code, font: Font.regular(32.0), color: .black) + )) + ) + let emoji = emojis[code].update( component: AnyComponent( PlainButtonComponent( content: AnyComponent( - ZStack([ - AnyComponentWithIdentity(id: "background", component: AnyComponent( - FilledRoundedRectangleComponent(color: theme.list.itemBlocksBackgroundColor, cornerRadius: .minEdge, smoothCorners: false) - )), - AnyComponentWithIdentity(id: "icon", component: AnyComponent( - Text(text: code, font: Font.regular(32.0), color: .black) - )) - ]) + ZStack(items) ), minSize: emojiSize, action: { @@ -839,3 +854,68 @@ private final class AuthConfirmationReferenceContentSource: ContextReferenceCont return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class ActivityIndicatorComponent: Component { + let color: UIColor + + init( + color: UIColor + ) { + self.color = color + } + + static func ==(lhs: ActivityIndicatorComponent, rhs: ActivityIndicatorComponent) -> Bool { + if lhs.color != rhs.color { + return false + } + return true + } + + final class View: UIView { + private let background = UIView() + private let activityIndicator: ActivityIndicator + + private var component: ActivityIndicatorComponent? + + override init(frame: CGRect) { + self.activityIndicator = ActivityIndicator(type: .custom(.white, 64.0, 2.0, true)) + + super.init(frame: frame) + + + self.addSubview(self.background) + self.addSubview(self.activityIndicator.view) + } + + required public init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ActivityIndicatorComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { + let size = CGSize(width: 64.0, height: 64.0) + + self.background.backgroundColor = component.color.withMultipliedAlpha(0.1) + self.background.layer.cornerRadius = 32.0 + self.background.clipsToBounds = true + + if self.component == nil { + self.activityIndicator.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + self.component = component + + self.background.frame = CGRect(origin: .zero, size: size) + self.activityIndicator.frame = CGRect(origin: .zero, size: size) + self.activityIndicator.type = .custom(component.color, 64.0, 2.0, true) + + return size + } + } + + public func makeView() -> View { + return View() + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index 71c8ad4310..5ce697f49f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -263,10 +263,9 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { canEditRank = true } - //TODO:localize if canEditRank { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "You joined the group", cancel: "Add Tag", destructive: false), elevatedLayout: true, action: { action in + let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: presentationData.strings.Chat_JoinedGroup_Text, cancel: presentationData.strings.Chat_JoinedGroup_AddTag, destructive: false), elevatedLayout: true, action: { action in if case .undo = action { let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peer.id, participantId: context.account.peerId, rank: nil, role: .member) self.interfaceInteraction?.getNavigationController()?.pushViewController(tagController) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index b715e1108e..0fc88aaf4c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2583,8 +2583,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case let .member(rank): if let rank, !rank.isEmpty { if rank == "0️⃣" { - //TODO:localize - string = "Tag" + string = item.presentationData.strings.Chat_TagPlaceholder defaultRankColor = defaultRankColor.withMultipliedAlpha(0.5) } else { string = rank.trimmingEmojis @@ -2924,7 +2923,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI icon: .suggestedPostReject ) ] - //TODO:localize let (minWidth, buttonsLayout) = actionButtonsLayout( item.context, item.presentationData.theme, @@ -2934,8 +2932,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ReplyMarkupMessageAttribute( rows: [ ReplyMarkupRow(buttons: [ - ReplyMarkupButton(title: "Reject", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: nil), - ReplyMarkupButton(title: "Accept", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: nil) + ReplyMarkupButton(title: item.presentationData.strings.Chat_EnableSharingOffer_Reject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: nil), + ReplyMarkupButton(title: item.presentationData.strings.Chat_EnableSharingOffer_Accept, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: nil) ]) ], flags: [], diff --git a/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift b/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift index 69e350ec6c..4bbf1b3ca0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift @@ -122,39 +122,38 @@ private final class ChatRankInfoSheetContent: CombinedComponent { let textString: String let linkColor: UIColor var linkHasBackground = true - let additionalTextString: String? = component.canChange ? nil : "Only admins can assign member tags in this group." - var adminPreviewTag = "Admin Tag" + let additionalTextString: String? = component.canChange ? nil : strings.RankInfo_ChangeInfo + var adminPreviewTag = strings.RankInfo_AdminTag var adminPreviewRole: ChatRankInfoScreenRole = .admin - var canEdit = false + let userName = context.component.userPeer.compactDisplayTitle + let chatName = context.component.chatPeer.compactDisplayTitle + + let canEdit = component.canChange switch context.component.role { case .creator: var rank = !context.component.rank.isEmpty ? context.component.rank : strings.Conversation_Owner rank = rank.replacingOccurrences(of: " ", with: "\u{00A0}") iconColor = UIColor(rgb: 0x956ac8) linkColor = iconColor - titleString = "Owner Tag" - textString = "This purple tag [\(rank)]() is **\(context.component.userPeer.compactDisplayTitle)'s** owner tag. **\(context.component.userPeer.compactDisplayTitle)** is the owner of **\(context.component.chatPeer.compactDisplayTitle)**." - adminPreviewTag = "Owner Tag" + titleString = strings.RankInfo_Owner_Title + textString = strings.RankInfo_Owner_Text(" [\(rank)]() ", userName, userName, chatName).string + adminPreviewTag = strings.RankInfo_OwnerTag adminPreviewRole = .creator case .admin: var rank = !context.component.rank.isEmpty ? context.component.rank : strings.Conversation_Admin rank = rank.replacingOccurrences(of: " ", with: "\u{00A0}") iconColor = UIColor(rgb: 0x49a355) linkColor = iconColor - titleString = "Admin Tag" - textString = "This green tag [\(rank)]() is **\(context.component.userPeer.compactDisplayTitle)'s** admin tag. **\(context.component.userPeer.compactDisplayTitle)** is an administrator of **\(context.component.chatPeer.compactDisplayTitle)**." + titleString = strings.RankInfo_Admin_Title + textString = strings.RankInfo_Admin_Text(" [\(rank)]() ", userName, userName, chatName).string case .member: let rank = context.component.rank.replacingOccurrences(of: " ", with: "\u{00A0}") iconColor = secondaryTextColor.withMultipliedAlpha(0.85) linkColor = secondaryTextColor linkHasBackground = false - titleString = "Member Tag" - textString = "This grey tag [\(rank)]() is **\(context.component.userPeer.compactDisplayTitle)'s** member tag. **\(context.component.userPeer.compactDisplayTitle)** is a member of **\(context.component.chatPeer.compactDisplayTitle)**." - - if component.canChange { - canEdit = true - } + titleString = strings.RankInfo_Member_Title + textString = strings.RankInfo_Member_Text(" [\(rank)]() ", userName, userName, chatName).string } let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in @@ -241,7 +240,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { text: "Reinhardt, we need to find you some new tunes, mkay?", entities: nil, media: [], - rank: "Member Tag", + rank: strings.RankInfo_MemberTag, rankRole: .member ) @@ -379,7 +378,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { var buttonTitle: [AnyComponentWithIdentity] = [] if canEdit { buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( - text: "Set My Tag", + text: strings.RankInfo_SetMyTag, badge: 0, textColor: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor, diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 248d086dbb..6eb76b913f 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -2285,8 +2285,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Add Caption", icon: { theme in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_Gif_AddCaption, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCaption"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.default) @@ -2295,7 +2294,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } }))) - items.append(.action(ContextMenuActionItem(text: "Edit", icon: { theme in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_Gif_Edit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.default) diff --git a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift index 0cf90a074a..7acc7ab7ff 100644 --- a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift +++ b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift @@ -155,7 +155,7 @@ private final class ChatParticipantRightsContent: CombinedComponent { let titleString: String switch component.subject { case .rank: - titleString = "Member Tag" + titleString = strings.EditRank_Title } let title = title.update( @@ -189,9 +189,9 @@ private final class ChatParticipantRightsContent: CombinedComponent { switch component.subject { case let .rank(_, _, _, role): if peer.id == component.context.account.peerId { - rankFooterString = "Share your role, title, or how you're known in this group. Your tag is visible to all members." + rankFooterString = strings.EditRank_InfoYou } else { - rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." + rankFooterString = strings.EditRank_Info(peer.compactDisplayTitle).string } switch role { case .creator: @@ -239,7 +239,7 @@ private final class ChatParticipantRightsContent: CombinedComponent { theme: theme, initialText: state.initialRank ?? "", resetText: nil, - placeholder: "Add tag", + placeholder: strings.EditRank_Placeholder, characterLimit: Int(rankMaxLength), autocapitalizationType: .sentences, autocorrectionType: .default, @@ -291,13 +291,13 @@ private final class ChatParticipantRightsContent: CombinedComponent { switch component.subject { case let .rank(_, _, initialRank, _): if (initialRank ?? "").isEmpty && (state.rank ?? "").isEmpty { - buttonTitle = "Add Later" + buttonTitle = strings.EditRank_AddLater } else if (initialRank ?? "").isEmpty && !(state.rank ?? "").isEmpty { - buttonTitle = "Add Tag" + buttonTitle = strings.EditRank_AddTag } else if !(initialRank ?? "").isEmpty && (state.rank ?? "").isEmpty { - buttonTitle = "Remove Tag" + buttonTitle = strings.EditRank_RemoveTag } else { - buttonTitle = "Edit Tag" + buttonTitle = strings.EditRank_EditTag } } @@ -503,14 +503,13 @@ public class ChatParticipantRightsScreen: ViewControllerComponentContainer { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let navigationController = self.navigationController as? NavigationController { Queue.mainQueue().after(0.5) { - //TODO:localize var title: String? var text: String if let rank { - title = "Tag added" + title = presentationData.strings.Chat_TagUpdated_Added text = rank } else { - text = "Tag removed" + text = presentationData.strings.Chat_TagUpdated_Removed } let toastController = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: title, text: text, cancel: nil, destructive: false), appearance: .init(isNarrow: true), action: { _ in return true}) (navigationController.topViewController as? ViewController)?.present(toastController, in: .current) diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift index 6f6f7d6137..739395939d 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift @@ -35,6 +35,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { let mode: ChatScheduleTimeScreen.Mode let currentTime: Int32? let currentRepeatPeriod: Int32? + let suggestedTime: Int32? let minimalTime: Int32? let externalState: ExternalState let dismiss: () -> Void @@ -44,6 +45,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { mode: ChatScheduleTimeScreen.Mode, currentTime: Int32?, currentRepeatPeriod: Int32?, + suggestedTime: Int32?, minimalTime: Int32?, externalState: ExternalState, dismiss: @escaping () -> Void @@ -52,6 +54,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { self.mode = mode self.currentTime = currentTime self.currentRepeatPeriod = currentRepeatPeriod + self.suggestedTime = suggestedTime self.minimalTime = minimalTime self.externalState = externalState self.dismiss = dismiss @@ -157,8 +160,10 @@ private final class ChatScheduleTimeSheetContentComponent: Component { if case .format = component.mode { self.minDate = Date(timeIntervalSince1970: 0.0) self.maxDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) - if let current = component.currentTime { - self.date = Date(timeIntervalSince1970: Double(current)) + if let currentTime = component.currentTime { + self.date = Date(timeIntervalSince1970: Double(currentTime)) + } else if let suggestedTime = component.suggestedTime { + self.date = Date(timeIntervalSince1970: Double(suggestedTime)) } else { self.date = Date() } @@ -219,8 +224,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { case .reminders: title = strings.Conversation_SetReminder_Title case .format: - //TODO:localize - title = "Date" + title = strings.Conversation_FormatDate_Title } let titleSize = self.title.update( transition: transition, @@ -481,8 +485,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { buttonTitle = strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string } case .format: - //TODO:localize - buttonTitle = component.currentTime != nil ? "Edit Date" : "Add Date" + buttonTitle = component.currentTime != nil ? strings.Conversation_FormatDate_EditDate : strings.Conversation_FormatDate_AddDate } let buttonSideInset: CGFloat = 30.0 @@ -538,7 +541,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { pressedColor: environment.theme.list.itemDestructiveColor.withMultipliedAlpha(0.8), ), content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( - Text(text: "Remove Date", font: Font.semibold(17.0), color: environment.theme.list.itemDestructiveColor) + Text(text: strings.Conversation_FormatDate_RemoveDate, font: Font.semibold(17.0), color: environment.theme.list.itemDestructiveColor) )), isEnabled: true, displaysProgress: false, @@ -754,6 +757,7 @@ private final class ChatScheduleTimeScreenComponent: Component { let mode: ChatScheduleTimeScreen.Mode let currentTime: Int32? let currentRepeatPeriod: Int32? + let suggestedTime: Int32? let minimalTime: Int32? init( @@ -761,12 +765,14 @@ private final class ChatScheduleTimeScreenComponent: Component { mode: ChatScheduleTimeScreen.Mode, currentTime: Int32?, currentRepeatPeriod: Int32?, + suggestedTime: Int32?, minimalTime: Int32? ) { self.context = context self.mode = mode self.currentTime = currentTime self.currentRepeatPeriod = currentRepeatPeriod + self.suggestedTime = suggestedTime self.minimalTime = minimalTime } @@ -838,6 +844,7 @@ private final class ChatScheduleTimeScreenComponent: Component { mode: component.mode, currentTime: component.currentTime, currentRepeatPeriod: component.currentRepeatPeriod, + suggestedTime: component.suggestedTime, minimalTime: component.minimalTime, externalState: self.contentExternalState, dismiss: { [weak self] in @@ -920,8 +927,9 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer { context: AccountContext, mode: Mode, currentTime: Int32?, - currentRepeatPeriod: Int32?, - minimalTime: Int32?, + currentRepeatPeriod: Int32? = nil, + suggestedTime: Int32? = nil, + minimalTime: Int32? = nil, isDark: Bool, completion: @escaping (Result) -> Void ) { @@ -932,6 +940,7 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer { mode: mode, currentTime: currentTime, currentRepeatPeriod: currentRepeatPeriod, + suggestedTime: suggestedTime, minimalTime: minimalTime ), navigationBarAppearance: .none, theme: isDark ? .dark : .default) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift index 4da90565f7..9d26d18013 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift @@ -85,7 +85,6 @@ public func giftSaleAlertController( priceString = "**\(formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)) TON**" } - //TODO:localize let text = strings.Gift_Sell_Confirm_Text(giftTitle, priceString).string content.append(AnyComponentWithIdentity( id: "text", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index dda37427c5..db374e57f0 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1103,17 +1103,40 @@ private final class GiftViewSheetContent: CombinedComponent { controller.present(alertController, in: .window(.root)) } + func presentGiftUnavailableForCraftingFirst(gift: StarGift.UniqueGift) { + guard let controller = self.getController() as? GiftViewScreen else { + return + } + let context = self.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let alertController = textAlertController( + context: context, + title: presentationData.strings.Gift_Craft_UnavailableBlockchain_Title, + text: presentationData.strings.Gift_Craft_UnavailableBlockchain_Text, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ], + actionLayout: .vertical + ) + controller.present(alertController, in: .window(.root)) + } + func craftGift() { guard let arguments = self.subject.arguments, let controller = self.getController() as? GiftViewScreen, case let .unique(gift) = arguments.gift else { return } + controller.dismissAllTooltips() + guard gift.hostPeerId == nil else { self.presentActionLockedForHostedGift(gift: gift) return } - - controller.dismissAllTooltips() + + guard gift.giftAddress == nil else { + self.presentGiftUnavailableForCraftingFirst(gift: gift) + return + } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift index 8a95ddec53..8046ae477c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift @@ -19,9 +19,8 @@ extension PeerInfoScreenNode { let actions = availableActionsForMemberOfPeer(accountPeerId: self.context.account.peerId, peer: enclosingPeer, member: member) - //TODO:localize if member.id != self.context.account.peerId { - items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionSend, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c?.dismiss { guard let self, let navigationController = self.controller?.navigationController as? NavigationController else { return @@ -32,7 +31,7 @@ extension PeerInfoScreenNode { } if actions.contains(.editRank) { - items.append(.action(ContextMenuActionItem(text: "Edit Member Tag", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tag"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionEditRank, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tag"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c?.dismiss { guard let self else { return @@ -43,7 +42,7 @@ extension PeerInfoScreenNode { } if actions.contains(.promote) && enclosingPeer is TelegramChannel { - items.append(.action(ContextMenuActionItem(text: "Promote", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Promote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionPromote, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Promote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c?.dismiss { guard let self else { return @@ -55,7 +54,7 @@ extension PeerInfoScreenNode { if actions.contains(.restrict) { if enclosingPeer is TelegramChannel { - items.append(.action(ContextMenuActionItem(text: "Restrict", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionRestrict, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c?.dismiss { guard let self else { return @@ -65,7 +64,7 @@ extension PeerInfoScreenNode { }))) } - items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionRemove, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in c?.dismiss { guard let self else { return diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift index 0713d505c9..ce7209b833 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift @@ -765,8 +765,7 @@ extension PeerInfoScreenNode { if let user = data.peer as? TelegramUser, let cachedData = data.cachedData as? CachedUserData, user.botInfo == nil && !user.flags.contains(.isSupport) && user.id != strongSelf.context.account.peerId && strongSelf.peerId.namespace != Namespaces.Peer.SecretChat { let copyProtectionEnabled = cachedData.flags.contains(.myCopyProtectionEnabled) || cachedData.flags.contains(.copyProtectionEnabled) - //TODO:localize - items.append(.action(ContextMenuActionItem(text: !copyProtectionEnabled ? "Disable Sharing" : "Enable Sharing", icon: { theme in + items.append(.action(ContextMenuActionItem(text: !copyProtectionEnabled ? strongSelf.presentationData.strings.PeerInfo_DisableSharing : strongSelf.presentationData.strings.PeerInfo_EnableSharing, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: !copyProtectionEnabled ? "Chat/Context Menu/ForwardDisable" : "Chat/Context Menu/ForwardEnable"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoForward.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoForward.imageset/Contents.json new file mode 100644 index 0000000000..c646c9b3ef --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoForward.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "disablesharing.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoForward.imageset/disablesharing.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoForward.imageset/disablesharing.pdf new file mode 100644 index 0000000000..321e09ee83 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoForward.imageset/disablesharing.pdf differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index ed70252f28..c9c6c6bf17 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -3702,20 +3702,28 @@ extension ChatControllerImpl { }) var date: Int32? + var suggestedDate: Int32? if let text { text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in if let dateAttribute = attributes[ChatTextInputAttributes.date] as? ChatTextInputTextDateAttribute { date = dateAttribute.date } } + if let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.date]).rawValue) { + detector.enumerateMatches(in: text.string, options: [], range: NSMakeRange(0, text.length), using: { result, _, _ in + guard let result, result.resultType == .date, let date = result.date?.timeIntervalSince1970, date > 0.0 else { + return + } + suggestedDate = Int32(date) + }) + } } let controller = ChatScheduleTimeScreen( context: self.context, mode: .format, currentTime: date, - currentRepeatPeriod: nil, - minimalTime: nil, + suggestedTime: suggestedDate, isDark: false, completion: { [weak self] result in guard let self, let inputMode = inputMode, let selectionRange = selectionRange else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift index 4cd0437724..20fdf9a6bc 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift @@ -45,10 +45,9 @@ extension ChatControllerImpl: EKEventEditViewDelegate { source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) // } - //TODO:localize var items: [ContextMenuItem] = [] items.append( - .action(ContextMenuActionItem(text: "Copy Date", 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_Date_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 { @@ -58,11 +57,11 @@ extension ChatControllerImpl: EKEventEditViewDelegate { let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat) UIPasteboard.general.string = fullDate - self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: "Date copied to clipboard."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.Conversation_DateCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) })) ) items.append( - .action(ContextMenuActionItem(text: "Add to Calendar", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Date_AddToCalendar, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { @@ -85,7 +84,7 @@ extension ChatControllerImpl: EKEventEditViewDelegate { })) ) items.append( - .action(ContextMenuActionItem(text: "Set a Reminder", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Date_SetReminder, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) guard let self else { @@ -109,7 +108,7 @@ extension ChatControllerImpl: EKEventEditViewDelegate { let forwardMessage: EnqueueMessage = .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil) let _ = forwardMessage let _ = enqueueMessages(account: self.context.account, peerId: self.context.account.peerId, messages: [forwardMessage]).start() - self.present(UndoOverlayController(presentationData: self.presentationData, content: .forward(savedMessages: true, text: "You have set a reminder in [Saved Messages]()."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + self.present(UndoOverlayController(presentationData: self.presentationData, content: .forward(savedMessages: true, text: self.presentationData.strings.Conversation_DateReminderSet), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) } ) self.push(controller) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index b15dbc2831..0080cb5043 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -89,14 +89,13 @@ extension ChatControllerImpl { } } if self.presentationInterfaceState.myCopyProtectionEnabled && !isAction && !isAd { - tip = .messageCopyProtection(text: "You disabled copying and forwarding in this chat.") + tip = .messageCopyProtection(text: self.presentationData.strings.Conversation_CopyProtectionInfoPrivateYou) } else if self.presentationInterfaceState.copyProtectionEnabled && !isAction && !isAd { if case .scheduledMessages = self.subject { } else { if let peer = self.presentationInterfaceState.renderedPeer?.peer { if peer is TelegramUser { - //TODO:localize - tip = .messageCopyProtection(text: "**\(EnginePeer(peer).compactDisplayTitle)** disabled copying and forwarding in this chat.") + tip = .messageCopyProtection(text: self.presentationData.strings.Conversation_CopyProtectionInfoPrivate(EnginePeer(peer).compactDisplayTitle).string) } else { var isChannel = false if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c03db9ce2f..37ef51110f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2551,12 +2551,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return buffer.baseAddress!.assumingMemoryBound(to: UInt8.self).pointee } - //TODO:localize switch buttonType { case 0: - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: "Disable Sharing", text: "Are you sure you want to keep sharing disabled?", actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Chat_EnableSharingOffer_RejectConfirmation_Title, text: strongSelf.presentationData.strings.Chat_EnableSharingOffer_RejectConfirmation_Text, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), - TextAlertAction(type: .defaultAction, title: "Yes", action: { [weak self] in + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Chat_EnableSharingOffer_RejectConfirmation_Reject, action: { [weak self] in guard let self else { return } @@ -2564,9 +2563,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) ], parseMarkdown: true), in: .window(.root)) case 1: - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: "Enable Sharing", text: "Are you sure you want to enable sharing?", actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Chat_EnableSharingOffer_AcceptConfirmation_Title, text: strongSelf.presentationData.strings.Chat_EnableSharingOffer_AcceptConfirmation_Text, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), - TextAlertAction(type: .defaultAction, title: "Yes", action: { [weak self] in + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Chat_EnableSharingOffer_AcceptConfirmation_Accept, action: { [weak self] in guard let self else { return } diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index c64a417d28..ea64fb5e9d 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -15,7 +15,6 @@ private let whitelistedHosts: Set = Set([ private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue) private let dataAndPhoneNumberDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link, .phoneNumber]).rawValue) private let phoneNumberDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.phoneNumber]).rawValue) -private let dataAndPhoneNumberAndDateDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link, .phoneNumber, .date]).rawValue) private let validHashtagSet: CharacterSet = { var set = CharacterSet.alphanumerics @@ -102,9 +101,8 @@ public struct EnabledEntityTypes: OptionSet { public static let timecode = EnabledEntityTypes(rawValue: 1 << 5) public static let external = EnabledEntityTypes(rawValue: 1 << 6) public static let internalUrl = EnabledEntityTypes(rawValue: 1 << 7) - public static let date = EnabledEntityTypes(rawValue: 1 << 8) - public static let all: EnabledEntityTypes = [.command, .mention, .hashtag, .allUrl, .phoneNumber, .date] + public static let all: EnabledEntityTypes = [.command, .mention, .hashtag, .allUrl, .phoneNumber] } private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType, _ range: Range, _ enabledTypes: EnabledEntityTypes, _ entities: inout [MessageTextEntity], mediaDuration: Double? = nil) { @@ -259,9 +257,7 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType let utf16 = text.utf16 var detector: NSDataDetector? - if enabledTypes.contains(.phoneNumber) && enabledTypes.contains(.date) && (enabledTypes.contains(.allUrl) || enabledTypes.contains(.internalUrl)) { - detector = dataAndPhoneNumberAndDateDetector - } else if enabledTypes.contains(.phoneNumber) && (enabledTypes.contains(.allUrl) || enabledTypes.contains(.internalUrl)) { + if enabledTypes.contains(.phoneNumber) && (enabledTypes.contains(.allUrl) || enabledTypes.contains(.internalUrl)) { detector = dataAndPhoneNumberDetector } else if enabledTypes.contains(.phoneNumber) { detector = phoneNumberDetector @@ -274,7 +270,7 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType if let detector = detector { detector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in if let result = result { - if [NSTextCheckingResult.CheckingType.link, NSTextCheckingResult.CheckingType.phoneNumber, NSTextCheckingResult.CheckingType.date].contains(result.resultType) { + if [NSTextCheckingResult.CheckingType.link, NSTextCheckingResult.CheckingType.phoneNumber].contains(result.resultType) { let lowerBound = utf16.index(utf16.startIndex, offsetBy: result.range.location).samePosition(in: text) let upperBound = utf16.index(utf16.startIndex, offsetBy: result.range.location + result.range.length).samePosition(in: text) if let lowerBound = lowerBound, let upperBound = upperBound { @@ -300,35 +296,6 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType } type = .Url - } else if result.resultType == NSTextCheckingResult.CheckingType.date, let date = result.date?.timeIntervalSince1970, date > Date().timeIntervalSince1970 { - #if DEBUG - var dayOfWeek = false - var format: MessageTextEntityType.DateTimeFormat? - if text.contains("[rel]") { - format = .relative - } - var timeFormat: MessageTextEntityType.DateTimeFormat.TimeFormat? - if text.contains("[st]") { - timeFormat = .short - } else if text.contains("[lt]") { - timeFormat = .long - } - var dateFormat: MessageTextEntityType.DateTimeFormat.DateFormat? - if text.contains("[sd]") { - dateFormat = .short - } else if text.contains("[ld]") { - dateFormat = .long - } - if text.contains("[d]") { - dayOfWeek = true - } - if timeFormat != nil || dateFormat != nil { - format = .full(timeFormat: timeFormat, dateFormat: dateFormat, dayOfWeek: dayOfWeek) - } - type = .FormattedDate(format: format, date: Int32(date)) - #else - type = .FormattedDate(format: nil, date: Int32(date)) - #endif } else { type = .PhoneNumber }