Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-07-16 23:17:04 +02:00
commit 271238fd21
60 changed files with 743 additions and 466 deletions

View File

@ -8,7 +8,7 @@ on:
jobs: jobs:
build: build:
runs-on: macos-12 runs-on: macos-13
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -9354,6 +9354,13 @@ Sorry for the inconvenience.";
"ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off"; "ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off";
"ChatList.PremiumRestoreDiscountText" = "Your Telegram Premium has recently expired. Tap here to extend it."; "ChatList.PremiumRestoreDiscountText" = "Your Telegram Premium has recently expired. Tap here to extend it.";
"Notification.LockScreenReactionPlaceholder" = "Reaction";
"UserInfo.BotNamePlaceholder" = "Bot Name";
"ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off";
"ChatList.PremiumRestoreDiscountText" = "Your Telegram Premium has recently expired. Tap here to extend it.";
"Login.ErrorAppOutdated" = "Please update Telegram to the latest version to log in."; "Login.ErrorAppOutdated" = "Please update Telegram to the latest version to log in.";
"Login.GetCodeViaFragment" = "Get a code via Fragment"; "Login.GetCodeViaFragment" = "Get a code via Fragment";
@ -9380,3 +9387,279 @@ Sorry for the inconvenience.";
"Conversation.StoryMentionTextIncoming" = "%@ mentioned you\nin a story"; "Conversation.StoryMentionTextIncoming" = "%@ mentioned you\nin a story";
"Conversation.StoryExpiredMentionTextOutgoing" = "The story where you mentioned %@\n is no longer available"; "Conversation.StoryExpiredMentionTextOutgoing" = "The story where you mentioned %@\n is no longer available";
"Conversation.StoryExpiredMentionTextIncoming" = "The story you were mentioned in\nis no longer available"; "Conversation.StoryExpiredMentionTextIncoming" = "The story you were mentioned in\nis no longer available";
"ChatList.ArchiveStoryCount_1" = "1 story";
"ChatList.ArchiveStoryCount_any" = "%d stories";
"Notification.Story" = "Story";
"ChatList.StoryFeedTooltip" = "Tap above to view updates\nfrom %@";
"StoryFeed.ContextAddStory" = "Add Story";
"StoryFeed.ContextSavedStories" = "Saved Stories";
"StoryFeed.ContextArchivedStories" = "Archived Stories";
"StoryFeed.ContextOpenChat" = "Send Message";
"StoryFeed.ContextOpenProfile" = "View Profile";
"StoryFeed.ContextNotifyOn" = "Notify About Stories";
"StoryFeed.ContextNotifyOff" = "Do Not Notify About Stories";
"StoryFeed.ContextArchive" = "Hide Stories";
"StoryFeed.ContextUnarchive" = "Unhide Stories";
"StoryFeed.TooltipNotifyOn" = "You will now get a notification whenever **%@** posts a story.";
"StoryFeed.TooltipNotifyOff" = "You will no longer receive a notification when **%@** posts a story.";
"StoryFeed.TooltipArchive" = "Stories from **%@** will now be shown in Archived Chats.";
"StoryFeed.TooltipUnarchive" = "Stories from **%@** will now be shown in Chats.";
"ChatList.Archive.ContextSettings" = "Archive Settings";
"ChatList.Archive.ContextInfo" = "How Does It Work?";
"ChatList.ContextSelectChats" = "Select Chats";
"StoryFeed.TooltipPremiumPosting" = "Posting stories is currently available only\nto subscribers of [Telegram Premium]().";
"StoryFeed.TooltipStoryLimitValue_1" = "1 story";
"StoryFeed.TooltipStoryLimitValue_any" = "%d stories";
"StoryFeed.TooltipStoryLimit" = "You can't post more than **%@** stories in **24 hours**.";
"StoryFeed.TooltipPostingDuringCall" = "You can't post stories during a call.";
"StoryFeed.TooltipPostingDuringGroupCall" = "You can't post stories during a voice chat.";
"StoryFeed.MyStory" = "My Story";
"StoryFeed.MyUploading" = "Uploading...";
"MediaPicker.AddImage" = "Add Image";
"Premium.Stories" = "Story Posting";
"Premium.StoriesInfo" = "Be one of the first to share your stories with your contacts or an unlimited audience.";
"Premium.Stories.Proceed" = "Unlock Story Posting";
"AutoDownloadSettings.OnForContacts" = "On for contacts";
"AutoDownloadSettings.StoriesSectionHeader" = "AUTO-DOWNLOAD STORIES";
"AutoDownloadSettings.StoriesArchivedContacts" = "Archived Contacts";
"AutoDownloadSettings.StoriesTitle" = "Stories";
"Notifications.TopChats" = "Top 5";
"Notifications.Stories" = "Stories";
"Settings.MyStories" = "My Stories";
"Settings.StoriesArchive" = "Stories Archive";
"ArchiveSettings.Title" = "Archive Settings";
"ArchiveSettings.UnmutedChatsHeader" = "UNMUTED CHATS";
"ArchiveSettings.UnmutedChatsFooter" = "Keep archived chats in the Archive even if they are unmuted and get a new message.";
"ArchiveSettings.FolderChatsHeader" = "CHATS FROM FOLDERS";
"ArchiveSettings.FolderChatsFooter" = "Keep archived chats from folders in the Archive even if they are unmuted and get a new message.";
"ArchiveSettings.UnknownChatsHeader" = "NEW CHATS FROM UNKNOWN USERS";
"ArchiveSettings.UnknownChatsFooter" = "Automatically archive and mute new private chats, groups and channels from non-contacts.";
"ArchiveSettings.KeepArchived" = "Always Keep Archived";
"ArchiveSettings.TooltipPremiumRequired" = "This setting is available only to the subscribers of [Telegram Premium]().";
"NotificationSettings.Stories.ShowAll" = "Show All Notifications";
"NotificationSettings.Stories.ShowImportant" = "Show Important Notifications";
"NotificationSettings.Stories.ShowImportantFooter" = "Always on for top 5 contacts.";
"NotificationSettings.Stories.DisplayAuthorName" = "Display Author Name";
"NotificationSettings.Stories.AutomaticValue" = "%@ (automatic)";
"NotificationSettings.Stories.CompactShowName" = "Show name";
"NotificationSettings.Stories.CompactHideName" = "Hide name";
"Notifications.StoriesTitle" = "Stories";
"Message.Story" = "Story";
"Notification.Exceptions.StoriesHeader" = "STORY NOTIFICATIONS";
"Notification.Exceptions.StoriesDisplayAuthorName" = "DISPLAY AUTHOR NAME";
"StorageManagement.SectionStories" = "Stories";
"PeerInfo.PaneStories" = "Stories";
"Story.TooltipExpired" = "This story is no longer available";
"Chat.ReplyExpiredStory" = "Expired story";
"Chat.ReplyStory" = "Story";
"Chat.StoryMentionAction" = "View Story";
"StoryList.ContextSaveToGallery" = "Save to Gallery";
"StoryList.ContextShowArchive" = "Show Archive";
"StoryList.TooltipStoriesDeleted_1" = "1 story deleted.";
"StoryList.TooltipStoriesDeleted_any" = "%d stories deleted.";
"Story.TooltipSaving" = "Saving";
"Story.TooltipSaved" = "Saved";
"StoryList.SaveToProfile" = "Save to Profile";
"StoryList.TooltipStoriesSavedToProfile_1" = "Story saved to your profile";
"StoryList.TooltipStoriesSavedToProfile_any" = "%d stories saved to your profile.";
"StoryList.TooltipStoriesSavedToProfileText" = "Saved stories can be viewed by others on your profile until you remove them.";
"StoryList.TitleSaved" = "My Stories";
"StoryList.TitleArchive" = "Stories Archive";
"StoryList.SubtitleSelected_1" = "1 story selected";
"StoryList.SubtitleSelected_any" = "%d stories selected";
"StoryList.SubtitleSaved_1" = "1 saved story";
"StoryList.SubtitleSaved_any" = "%d saved stories";
"StoryList.SubtitleCount_1" = "1 story";
"StoryList.SubtitleCount_any" = "%d stories";
"StoryList.ArchiveDescription" = "Only you can see archived stories unless you choose to save them to your profile.";
"StoryList.SavedEmptyState.Title" = "No saved stories";
"StoryList.SavedEmptyState.Text" = "Open the Archive to select stories you\nwant to be displayed in your profile.";
"StoryList.ArchivedEmptyState.Title" = "No Archived Stories";
"StoryList.ArchivedEmptyState.Text" = "Upload a new story to view it here";
"StoryList.SavedEmptyAction" = "Open Archive";
"ArchiveInfo.Title" = "This is Your Archive";
"ArchiveInfo.TextKeepArchivedUnmuted" = "Archived chats will remain in the Archive when you receive a new message. [Tap to change >]()";
"ArchiveInfo.TextKeepArchivedDefault" = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change >]()";
"ArchiveInfo.ChatsTitle" = "Archived Chats";
"ArchiveInfo.ChatsText" = "Move any chat into your Archive and back by swiping on it.";
"ArchiveInfo.HideTitle" = "Hiding Archive";
"ArchiveInfo.HideText" = "Hide the Archive from your Main screen by swiping on it.";
"ArchiveInfo.StoriesTitle" = "Stories";
"ArchiveInfo.StoriesText" = "Archive Stories from your contacts separately from chats with them.";
"ArchiveInfo.CloseAction" = "Got it";
"Story.HeaderYourStory" = "Your story";
"Story.HeaderEdited" = "edited";
"Story.CaptionShowMore" = "Show more";
"Story.UnsupportedText" = "This story is not supported by\nyour version of Telegram.";
"Story.UnsupportedAction" = "Update Telegram";
"Story.ScreenshotBlockedTitle" = "Screenshot Blocked";
"Story.ScreenshotBlockedText" = "The story you tried to take a\nscreenshot of is protected from\ncopying by its creator.";
"Story.Footer.NoViews" = "No views";
"Story.Footer.Views_1" = "1 view";
"Story.Footer.Views_any" = "%d views";
"Story.Footer.Uploading" = "Uploading...";
"Story.FooterReplyUnavailable" = "You can't reply to this story";
"Story.InputPlaceholderReplyPrivately" = "Reply Privately...";
"Story.ContextDeleteStory" = "Delete Story";
"Story.TooltipPrivacyCloseFriendsMy" = "Only people from your close friends list will see this story.";
"Story.TooltipPrivacyCloseFriends" = "You are seeing this story because you have\nbeen added to %@'s list of close friends.";
"Story.ToastViewInChat" = "View in Chat";
"Story.ToastReactionSent" = "Reaction Sent.";
"Story.PrivacyTooltipContacts" = "This story is shown to all your contacts.";
"Story.PrivacyTooltipCloseFriends" = "This story is shown to your close friends.";
"Story.PrivacyTooltipSelectedContacts" = "This story is shown to selected contacts.";
"Story.PrivacyTooltipNobody" = "This story is shown only to you.";
"Story.PrivacyTooltipEveryone" = "This story is shown to everyone.";
"Story.ContextPrivacy.LabelCloseFriends" = "Close Friends";
"Story.ContextPrivacy.LabelContactsExcept" = "Contacts (-%@)";
"Story.ContextPrivacy.LabelContacts" = "Contacts";
"Story.ContextPrivacy.LabelOnlySelected_1" = "1 Person";
"Story.ContextPrivacy.LabelOnlySelected_any" = "%d People";
"Story.ContextPrivacy.LabelOnlyMe" = "Only Me";
"Story.ContextPrivacy.LabelEveryone" = "Everyone";
"Story.Context.Privacy" = "Who Can See";
"Story.Context.Edit" = "Edit Story";
"Story.Context.SaveToProfile" = "Save to Profile";
"Story.Context.RemoveFromProfile" = "Remove from Profile";
"Story.ToastRemovedFromProfileText" = "Story removed from your profile";
"Story.ToastSavedToProfileTitle" = "Story saved to your profile";
"Story.ToastSavedToProfileText" = "Saved stories can be viewed by others on your profile until you remove them.";
"Story.Context.SaveToGallery" = "Save to Gallery";
"Story.Context.CopyLink" = "Copy Link";
"Story.ToastLinkCopied" = "Link copied.";
"Story.Context.Share" = "Share";
"Story.Context.Report" = "Report";
"Story.Context.EmbeddedStickersValue_1" = "1 pack";
"Story.Context.EmbeddedStickersValue_any" = "%d packs";
"Story.Context.EmbeddedStickers" = "This story contains stickers from [%@]().";
"Story.Context.EmbeddedEmojiPack" = "This story contains\n#[%@]() emoji.";
"Story.Context.EmbeddedStickerPack" = "This story contains\n#[%@]() stickers.";
"Story.TooltipVideoHasNoSound" = "This video has no sound";
"Story.TooltipMessageScheduled" = "Message Scheduled";
"Story.TooltipMessageSent" = "Message Sent";
"Story.Camera.Photo" = "Photo";
"Story.Camera.Video" = "Video";
"Story.Camera.TooltipTakePhotos" = "Take photos or videos to share with all\nyour contacts or close friends at once.";
"Story.Camera.TooltipDisableDual" = "Tap here to disable\nthe selfie camera";
"Story.Camera.TooltipDraftSaved" = "Draft Saved";
"Story.Camera.SwipeUpToZoom" = "Swipe up to zoom";
"Story.Camera.SwipeLeftToLock" = "Swipe left to lock";
"Story.Camera.SwipeLeftRelease" = "Release to lock";
"Story.Camera.SwipeRightToFlip" = "Swipe right to flip";
"Story.Camera.AccessPlaceholderTitle" = "Allow Telegram to access your camera and microphone";
"Story.Camera.AccessPlaceholderText" = "This lets you share photos and record videos.";
"Story.Camera.AccessOpenSettings" = "Open Settings";
"Story.Editor.Next" = "Next";
"Story.Editor.Done" = "Done";
"Story.Editor.DraftDiscardMedia" = "Discard Media?";
"Story.Editor.DraftDiscardDraft" = "Discard Draft?";
"Story.Editor.DraftKeepMedia" = "Save Draft";
"Story.Editor.DraftKeepDraft" = "Keep Draft";
"Story.Editor.DraftDiscaedText" = "If you go back now, you will lose any changes that you've made.";
"Story.Editor.DraftDiscard" = "Discard";
"Story.Editor.ExpirationText" = "Choose how long the story will be visible.";
"Story.Editor.ExpirationValue_1" = "1 Hour";
"Story.Editor.ExpirationValue_any" = "%d Hours";
"Story.Editor.TooltipPremiumCustomExpiration" = "Subscribe to **Telegram Premium** to make your stories disappear %@.";
"Story.Editor.TooltipPremiumMore" = "More";
"Story.Editor.InputPlaceholderAddCaption" = "Add a caption...";
"Story.Editor.TooltipMuted" = "The story will have no sound";
"Story.Editor.TooltipUnmuted" = "The story will have sound";
"Story.Editor.PreparingVideo" = "Preparing Video...";
"Story.Editor.TooltipImageSavedToPhotos" = "Image saved to Photos.";
"Story.Editor.TooltipVideoSavedToPhotos" = "Video saved to Photos.";
"Story.Editor.Uploading" = "Uploading...";
"Story.Editor.Tool.Enhance" = "Enhance";
"Story.Editor.Tool.Brightness" = "Brightness";
"Story.Editor.Tool.Contrast" = "Contrast";
"Story.Editor.Tool.Saturation" = "Saturation";
"Story.Editor.Tool.Warmth" = "Warmth";
"Story.Editor.Tool.Fade" = "Fade";
"Story.Editor.Tool.Highlights" = "Highlights";
"Story.Editor.Tool.Shadows" = "Shadows";
"Story.Editor.Tool.Vignette" = "Vignette";
"Story.Editor.Tool.Grain" = "Grain";
"Story.Editor.Tint.Shadows" = "Shadows";
"Story.Editor.Tint.Highlights" = "Highlights";
"Story.Editor.Blur.Title" = "Blur";
"Story.Editor.Blur.Off" = "Off";
"Story.Editor.Blur.Radial" = "Radial";
"Story.Editor.Blur.Linear" = "Linear";
"Story.Editor.Blur.Portrait" = "Portrait";
"Story.Editor.Curves.All" = "All";
"Story.Editor.Curves.Red" = "Red";
"Story.Editor.Curves.Green" = "Green";
"Story.Editor.Curves.Blue" = "Blue";

View File

@ -1991,7 +1991,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.displayedStoriesTooltip = true self.displayedStoriesTooltip = true
let absoluteFrame = anchorView.convert(anchorRect, to: self.view) let absoluteFrame = anchorView.convert(anchorRect, to: self.view)
//TODO:localize
let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle) let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle)
var itemListString: String = itemList.joined(separator: ", ") var itemListString: String = itemList.joined(separator: ", ")
@ -2003,7 +2002,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
} }
let text: String = "Tap above to view updates\nfrom \(itemListString)" let text: String = self.presentationData.strings.ChatList_StoryFeedTooltip(itemListString).string
let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0)) let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
@ -2584,13 +2583,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let text: String let text: String
if premiumNeeded { if premiumNeeded {
text = "Posting stories is currently available only\nto subscribers of [Telegram Premium]()." text = self.presentationData.strings.StoryFeed_TooltipPremiumPosting
} else if reachedCountLimit { } else if reachedCountLimit {
text = "You can't post more than **\(storiesCountLimit)** stories in **24 hours**." let valueText = self.presentationData.strings.StoryFeed_TooltipStoryLimitValue(Int32(storiesCountLimit))
text = self.presentationData.strings.StoryFeed_TooltipStoryLimit(valueText).string
} else if hasActiveCall { } else if hasActiveCall {
text = "You can't post stories during a call." text = self.presentationData.strings.StoryFeed_TooltipPostingDuringCall
} else if hasActiveGroupCall { } else if hasActiveGroupCall {
text = "You can't post stories during a voice chat." text = self.presentationData.strings.StoryFeed_TooltipPostingDuringGroupCall
} else { } else {
text = "" text = ""
} }
@ -2730,9 +2730,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
//TODO:localize
if peer.id == self.context.account.peerId { if peer.id == self.context.account.peerId {
items.append(.action(ContextMenuActionItem(text: "Add Story", icon: { theme in items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextAddStory, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in
c.dismiss(completion: { c.dismiss(completion: {
@ -2744,7 +2743,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}) })
}))) })))
items.append(.action(ContextMenuActionItem(text: "Saved Stories", icon: { theme in items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextSavedStories, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in
c.dismiss(completion: { c.dismiss(completion: {
@ -2756,7 +2755,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}) })
}))) })))
items.append(.action(ContextMenuActionItem(text: "Archived Stories", icon: { theme in items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextArchivedStories, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in
c.dismiss(completion: { c.dismiss(completion: {
@ -2768,7 +2767,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}) })
}))) })))
} else { } else {
items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenChat, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in
c.dismiss(completion: { c.dismiss(completion: {
@ -2780,7 +2779,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}) })
}))) })))
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenProfile, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in
c.dismiss(completion: { c.dismiss(completion: {
@ -2804,7 +2803,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}))) })))
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers)
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify About Stories" : "Do Not Notify About Stories", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? self.presentationData.strings.StoryFeed_ContextNotifyOn : self.presentationData.strings.StoryFeed_ContextNotifyOff, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
f(.default) f(.default)
@ -2825,7 +2824,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will now get a notification whenever **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil), ], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOn(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -2839,7 +2838,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will no longer receive a notification when **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil), ], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOff(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -2849,9 +2848,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let hideText: String let hideText: String
if self.location == .chatList(groupId: .archive) { if self.location == .chatList(groupId: .archive) {
hideText = "Unhide Stories" hideText = self.presentationData.strings.StoryFeed_ContextUnarchive
} else { } else {
hideText = "Hide Stories" hideText = self.presentationData.strings.StoryFeed_ContextArchive
} }
let iconName = self.location == .chatList(groupId: .archive) ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive" let iconName = self.location == .chatList(groupId: .archive) ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"
items.append(.action(ContextMenuActionItem(text: hideText, icon: { theme in items.append(.action(ContextMenuActionItem(text: hideText, icon: { theme in
@ -2871,9 +2870,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
undoValue = false undoValue = false
} }
//TODO:localize
if self.location != .chatList(groupId: .archive) { if self.location != .chatList(groupId: .archive) {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Archived Chats.", undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: self.presentationData.strings.StoryFeed_TooltipArchive(peer.compactDisplayTitle).string, undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in
if case .undo = action { if case .undo = action {
if let self { if let self {
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue) self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue)
@ -2882,30 +2880,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return false return false
}), in: .current) }), in: .current)
} }
/*guard let parentController = self.parent as? TabBarController, let contactsController = (self.navigationController as? TelegramRootControllerInterface)?.getContactsController(), let sourceFrame = parentController.frameForControllerTab(controller: contactsController) else {
return
}
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
let tooltipController = TooltipScreen(
context: self.context,
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .markdown(text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Contacts, not Chats."),
icon: .peer(peer: peer, isStory: true),
action: TooltipScreen.Action(
title: "Undo",
action: { [weak self] in
if let self {
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false)
}
}
),
location: .point(location, .bottom),
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
)
self.present(tooltipController, in: .window(.root))*/
}))) })))
} }
@ -3328,8 +3302,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
//TODO:localize items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Archive_ContextSettings, icon: { theme in
items.append(.action(ContextMenuActionItem(text: "Archive Settings", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3341,7 +3314,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}))) })))
if !archiveChatList.items.isEmpty { if !archiveChatList.items.isEmpty {
items.append(.action(ContextMenuActionItem(text: "How Does It Work?", icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Archive_ContextInfo, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/Question"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/Question"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3359,7 +3332,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.push(ArchiveInfoScreen(context: self.context, settings: settings)) self.push(ArchiveInfoScreen(context: self.context, settings: settings))
}) })
}))) })))
items.append(.action(ContextMenuActionItem(text: "Select Chats", icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_ContextSelectChats, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)

View File

@ -2156,13 +2156,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
if textString.length == 0, case let .groupReference(data) = item.content, let storyState = data.storyState, storyState.stats.totalCount != 0 { if textString.length == 0, case let .groupReference(data) = item.content, let storyState = data.storyState, storyState.stats.totalCount != 0 {
//TODO:localize let storyText: String = item.presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyState.stats.totalCount))
let storyText: String
if storyState.stats.totalCount == 1 {
storyText = "1 story"
} else {
storyText = "\(storyState.stats.totalCount) stories"
}
textString.append(NSAttributedString(string: storyText, font: textFont, textColor: theme.messageTextColor)) textString.append(NSAttributedString(string: storyText, font: textFont, textColor: theme.messageTextColor))
} }
attributedText = textString attributedText = textString

View File

@ -303,8 +303,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = strings.Conversation_StoryMentionTextOutgoing(peer.compactDisplayTitle).string messageText = strings.Conversation_StoryMentionTextOutgoing(peer.compactDisplayTitle).string
} }
} else { } else {
//TODO:localize messageText = strings.Notification_Story
messageText = "Story"
} }
default: default:
break break

View File

@ -31,8 +31,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
if isStories { if isStories {
//TODO:localize items.append(.action(ContextMenuActionItem(text: strings.StoryFeed_ContextOpenProfile, icon: { theme in
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in }, action: { c, _ in
c.dismiss(completion: { c.dismiss(completion: {
@ -50,7 +49,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers)
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? strings.StoryFeed_ContextNotifyOn : strings.StoryFeed_ContextNotifyOff, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
f(.default) f(.default)
@ -69,7 +68,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will now get a notification whenever **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil), ], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOn(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -83,7 +82,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will no longer receive a notification when **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil), ], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOff(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -92,36 +91,12 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
} }
}))) })))
items.append(.action(ContextMenuActionItem(text: "Move to Chats", icon: { theme in items.append(.action(ContextMenuActionItem(text: strings.StoryFeed_ContextUnarchive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToChats"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToChats"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
context.engine.peers.updatePeerStoriesHidden(id: peerId, isHidden: false) context.engine.peers.updatePeerStoriesHidden(id: peerId, isHidden: false)
guard let parentController = contactsController?.parent as? TabBarController, let chatsController = (contactsController?.navigationController as? TelegramRootControllerInterface)?.getChatsController(), let sourceFrame = parentController.frameForControllerTab(controller: chatsController) else {
return
}
do {
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
let tooltipController = TooltipScreen(
context: context,
account: context.account,
sharedContext: context.sharedContext,
text: .markdown(text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Chats, not Contacts."),
icon: .peer(peer: peer, isStory: true),
action: TooltipScreen.Action(
title: "Undo",
action: {
context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true)
}
),
location: .point(location, .bottom),
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
)
contactsController?.present(tooltipController, in: .window(.root))
}
}))) })))
return items return items

View File

@ -207,20 +207,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
storyStats = (storyData.count, storyData.unseenCount, storyData.hasUnseenCloseFriends) storyStats = (storyData.count, storyData.unseenCount, storyData.hasUnseenCloseFriends)
let text: String let text: String
//TODO:localize text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count))
if storyData.unseenCount != 0 {
if storyData.unseenCount == 1 {
text = "1 unseen story"
} else {
text = "\(storyData.unseenCount) unseen stories"
}
} else {
if storyData.count == 1 {
text = "1 story"
} else {
text = "\(storyData.count) stories"
}
}
status = .custom(string: text, multiline: false, isActive: false, icon: nil) status = .custom(string: text, multiline: false, isActive: false, icon: nil)
} }
@ -404,8 +391,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
if addHeader { if addHeader {
//TODO:localize commonHeader = ChatListSearchItemHeader(type: .text(strings.Contacts_SortedByPresence.uppercased(), AnyHashable(1)), theme: theme, strings: strings, actionTitle: nil, action: nil)
commonHeader = ChatListSearchItemHeader(type: .text("SORTED BY LAST SEEN TIME", AnyHashable(1)), theme: theme, strings: strings, actionTitle: nil, action: nil)
} }
switch presentation { switch presentation {
@ -529,15 +515,15 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
if let storySubscriptions { if let storySubscriptions {
var index: Int = 0 let _ = storySubscriptions
/*var index: Int = 0
//TODO:localize
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings) let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings)
for item in storySubscriptions.items { for item in storySubscriptions.items {
entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends))) entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends)))
index += 1 index += 1
} }*/
} }
var index: Int = 0 var index: Int = 0

View File

@ -300,12 +300,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
let tabsNode: ASDisplayNode? = nil let tabsNode: ASDisplayNode? = nil
let tabsNodeIsSearch = false let tabsNodeIsSearch = false
//TODO:localize
let primaryContent = ChatListHeaderComponent.Content( let primaryContent = ChatListHeaderComponent.Content(
title: "Contacts", title: self.presentationData.strings.Contacts_Title,
navigationBackTitle: nil, navigationBackTitle: nil,
titleComponent: nil, titleComponent: nil,
chatListTitle: NetworkStatusTitle(text: "Contacts", activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil), chatListTitle: NetworkStatusTitle(text: self.presentationData.strings.Contacts_Title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil),
leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent( leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent(
content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false), content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false),
pressed: { [weak self] sourceView in pressed: { [weak self] sourceView in

View File

@ -192,7 +192,7 @@ final class MediaPickerGridItemNode: GridItemNode {
} else if let (fetchResult, index) = self.currentAssetState { } else if let (fetchResult, index) = self.currentAssetState {
let asset = fetchResult.object(at: index) let asset = fetchResult.object(at: index)
if let localTimestamp = asset.creationDate?.timeIntervalSince1970 { if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue let tag = Month(localTimestamp: Int32(exactly: floor(localTimestamp)) ?? 0).packedValue
self._cachedTag = tag self._cachedTag = tag
return tag return tag
} else { } else {

View File

@ -95,8 +95,8 @@ struct Month: Equatable {
var timeinfo: tm = tm() var timeinfo: tm = tm()
gmtime_r(&time, &timeinfo) gmtime_r(&time, &timeinfo)
let year = UInt32(timeinfo.tm_year) let year = UInt32(max(timeinfo.tm_year, 0))
let month = UInt32(timeinfo.tm_mon) let month = UInt32(max(timeinfo.tm_mon, 0))
self.packedValue = Int32(bitPattern: year | (month << 16)) self.packedValue = Int32(bitPattern: year | (month << 16))
} }
@ -1445,8 +1445,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
case .wallpaper: case .wallpaper:
self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
case .addImage: case .addImage:
//TODO:localize self.titleView.title = presentationData.strings.MediaPicker_AddImage
self.titleView.title = "Add Image"
} }
} }
} else { } else {

View File

@ -1265,6 +1265,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
transition: indicatorTransition, transition: indicatorTransition,
component: AnyComponent(StorySetIndicatorComponent( component: AnyComponent(StorySetIndicatorComponent(
context: self.context, context: self.context,
strings: self.context.sharedContext.currentPresentationData.with({ $0 }).strings,
peer: storyParams.peer, peer: storyParams.peer,
items: storyParams.items, items: storyParams.items,
hasUnseen: storyParams.hasUnseen, hasUnseen: storyParams.hasUnseen,

View File

@ -939,7 +939,6 @@ private final class DemoSheetContent: CombinedComponent {
) )
) )
) )
//TODO:localize
availableItems[.stories] = DemoPagerComponent.Item( availableItems[.stories] = DemoPagerComponent.Item(
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.stories, id: PremiumDemoScreen.Subject.stories,
@ -951,8 +950,8 @@ private final class DemoSheetContent: CombinedComponent {
videoFile: configuration.videos["voice_to_text"], videoFile: configuration.videos["voice_to_text"],
decoration: .badgeStars decoration: .badgeStars
)), )),
title: "Story Posting", title: strings.Premium_Stories,
text: "Be one of the first to share your stories with your contacts or an unlimited audience.", text: strings.Premium_StoriesInfo,
textColor: textColor textColor: textColor
) )
) )
@ -1049,8 +1048,7 @@ private final class DemoSheetContent: CombinedComponent {
case .translation: case .translation:
buttonText = strings.Premium_Translation_Proceed buttonText = strings.Premium_Translation_Proceed
case .stories: case .stories:
//TODO:localize buttonText = strings.Premium_Stories_Proceed
buttonText = "Unlock Story Posting"
buttonAnimationName = "premium_unlock" buttonAnimationName = "premium_unlock"
default: default:
buttonText = strings.Common_OK buttonText = strings.Common_OK

View File

@ -395,8 +395,7 @@ enum PremiumPerk: CaseIterable {
case .translation: case .translation:
return strings.Premium_Translation return strings.Premium_Translation
case .stories: case .stories:
//TODO:localize return strings.Premium_Stories
return "Story Posting"
} }
} }

View File

@ -85,34 +85,33 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ArchiveSettingsControllerArguments let arguments = arguments as! ArchiveSettingsControllerArguments
//TODO:localize
switch self { switch self {
case .unmutedHeader: case .unmutedHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "UNMUTED CHATS", sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnmutedChatsHeader, sectionId: self.section)
case let .unmutedValue(value): case let .unmutedValue(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateUnmuted(value) arguments.updateUnmuted(value)
}) })
case .unmutedFooter: case .unmutedFooter:
return ItemListTextItem(presentationData: presentationData, text: .markdown("Keep archived chats in the Archive even if they are unmuted and get a new message."), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.ArchiveSettings_UnmutedChatsFooter), sectionId: self.section)
case .foldersHeader: case .foldersHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "CHATS FROM FOLDERS", sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_FolderChatsHeader, sectionId: self.section)
case let .foldersValue(value): case let .foldersValue(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateFolders(value) arguments.updateFolders(value)
}) })
case .foldersFooter: case .foldersFooter:
return ItemListTextItem(presentationData: presentationData, text: .markdown("Keep archived chats from folders in the Archive even if they are unmuted and get a new message."), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.ArchiveSettings_FolderChatsFooter), sectionId: self.section)
case .unknownHeader: case .unknownHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "NEW CHATS FROM UNKNOWN USERS", sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnknownChatsHeader, sectionId: self.section)
case let .unknownValue(isOn, isLocked): case let .unknownValue(isOn, isLocked):
return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateUnknown(value) arguments.updateUnknown(value)
}, activatedWhileDisabled: { }, activatedWhileDisabled: {
arguments.updateUnknown(nil) arguments.updateUnknown(nil)
}) })
case .unknownFooter: case .unknownFooter:
return ItemListTextItem(presentationData: presentationData, text: .markdown("Automatically archive and mute new private chats, groups and channels from non-contacts."), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.ArchiveSettings_UnknownChatsFooter), sectionId: self.section)
} }
} }
} }
@ -163,7 +162,8 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
if let value { if let value {
let _ = context.engine.privacy.updateAccountAutoArchiveChats(value: value).start() let _ = context.engine.privacy.updateAccountAutoArchiveChats(value: value).start()
} else { } else {
presentUndoImpl?(.premiumPaywall(title: nil, text: "This setting is available only to the subscribers of [Telegram Premium]().", customUndoText: nil, timeout: nil, linkAction: { _ in let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentUndoImpl?(.premiumPaywall(title: nil, text: presentationData.strings.ArchiveSettings_TooltipPremiumRequired, customUndoText: nil, timeout: nil, linkAction: { _ in
presentPremiumImpl?() presentPremiumImpl?()
})) }))
} }
@ -181,8 +181,7 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
let isPremium = accountPeer?.isPremium ?? false let isPremium = accountPeer?.isPremium ?? false
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
//TODO:localize let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ArchiveSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Archive Settings"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archiveSettingsControllerEntries( let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archiveSettingsControllerEntries(
presentationData: presentationData, presentationData: presentationData,
settings: settings, settings: settings,

View File

@ -207,8 +207,7 @@ private func stringForAutomaticDownloadPeers(strings: PresentationStrings, decim
if peers.contacts && peers.otherPrivate { if peers.contacts && peers.otherPrivate {
return strings.AutoDownloadSettings_OnForAll return strings.AutoDownloadSettings_OnForAll
} else if peers.contacts { } else if peers.contacts {
//TODO:localize return strings.AutoDownloadSettings_OnForContacts
return "On for contacts"
} else { } else {
return strings.AutoDownloadSettings_OffForAll return strings.AutoDownloadSettings_OffForAll
} }

View File

@ -274,16 +274,14 @@ private func autodownloadMediaCategoryControllerEntries(presentationData: Presen
downloadTitle = presentationData.strings.AutoDownloadSettings_AutodownloadFiles downloadTitle = presentationData.strings.AutoDownloadSettings_AutodownloadFiles
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
case .story: case .story:
//TODO:localize downloadTitle = presentationData.strings.AutoDownloadSettings_StoriesSectionHeader
downloadTitle = "AUTO-DOWNLOAD STORIES"
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
} }
if case .story = category { if case .story = category {
entries.append(.peerContacts(presentationData.theme, presentationData.strings.AutoDownloadSettings_Contacts, peers.contacts)) entries.append(.peerContacts(presentationData.theme, presentationData.strings.AutoDownloadSettings_Contacts, peers.contacts))
//TODO:localize
if peers.contacts { if peers.contacts {
entries.append(.peerOtherPrivate(presentationData.theme, "Hidden Contacts", peers.otherPrivate)) entries.append(.peerOtherPrivate(presentationData.theme, presentationData.strings.AutoDownloadSettings_StoriesArchivedContacts, peers.otherPrivate))
} }
} else { } else {
entries.append(.peerHeader(presentationData.theme, downloadTitle)) entries.append(.peerHeader(presentationData.theme, downloadTitle))
@ -470,8 +468,7 @@ func autodownloadMediaCategoryController(context: AccountContext, connectionType
case .file: case .file:
title = presentationData.strings.AutoDownloadSettings_DocumentsTitle title = presentationData.strings.AutoDownloadSettings_DocumentsTitle
case .story: case .story:
//TODO:localize title = presentationData.strings.AutoDownloadSettings_StoriesTitle
title = "Stories"
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)

View File

@ -552,18 +552,17 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
entries.append(.groupChats(presentationData.theme, presentationData.strings.Notifications_GroupChats, !exceptions.groups.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.groups.peerIds.count)) : "", globalSettings.groupChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) entries.append(.groupChats(presentationData.theme, presentationData.strings.Notifications_GroupChats, !exceptions.groups.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.groups.peerIds.count)) : "", globalSettings.groupChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
entries.append(.channels(presentationData.theme, presentationData.strings.Notifications_Channels, !exceptions.channels.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.channels.peerIds.count)) : "", globalSettings.channels.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) entries.append(.channels(presentationData.theme, presentationData.strings.Notifications_Channels, !exceptions.channels.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.channels.peerIds.count)) : "", globalSettings.channels.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
//TODO:localize
let storiesValue: String let storiesValue: String
switch globalSettings.privateChats.storySettings.mute { switch globalSettings.privateChats.storySettings.mute {
case .default: case .default:
storiesValue = "Top 5" storiesValue = presentationData.strings.Notifications_TopChats
case .muted: case .muted:
storiesValue = presentationData.strings.Notifications_Off storiesValue = presentationData.strings.Notifications_Off
case .unmuted: case .unmuted:
storiesValue = presentationData.strings.Notifications_On storiesValue = presentationData.strings.Notifications_On
} }
entries.append(.stories(presentationData.theme, "Stories", !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue)) entries.append(.stories(presentationData.theme, presentationData.strings.Notifications_Stories, !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue))
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased())) entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds)) entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))

View File

@ -345,17 +345,16 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
importantEnabled = true importantEnabled = true
} }
//TODO:localize entries.append(.enable(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowAll, allEnabled))
entries.append(.enable(presentationData.theme, "Show All Notifications", allEnabled))
if !allEnabled { if !allEnabled {
entries.append(.enableImportant(presentationData.theme, "Show Important Notifications", importantEnabled)) entries.append(.enableImportant(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportant, importantEnabled))
entries.append(.importantInfo(presentationData.theme, "Always on for top 5 contacts.")) entries.append(.importantInfo(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportantFooter))
} }
if notificationSettings.enabled || !notificationExceptions.isEmpty { if notificationSettings.enabled || !notificationExceptions.isEmpty {
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased())) entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased()))
entries.append(.previews(presentationData.theme, "Display Author Name", notificationSettings.storySettings.hideSender != .hide)) entries.append(.previews(presentationData.theme, presentationData.strings.NotificationSettings_Stories_DisplayAuthorName, notificationSettings.storySettings.hideSender != .hide))
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound))) entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound)))
} }
} else { } else {
@ -413,8 +412,7 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
var title: String = "" var title: String = ""
if automaticSet.contains(value.peer.id) { if automaticSet.contains(value.peer.id) {
//TODO:localize title = presentationData.strings.NotificationSettings_Stories_AutomaticValue(presentationData.strings.Notification_Exceptions_AlwaysOn).string
title = "\(presentationData.strings.Notification_Exceptions_AlwaysOn) (automatic)"
canRemove = false canRemove = false
} else { } else {
if case .stories = category { if case .stories = category {
@ -443,11 +441,10 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
if !title.isEmpty { if !title.isEmpty {
title += ", " title += ", "
} }
//TODO:localize
if case .show = value.settings.storySettings.hideSender { if case .show = value.settings.storySettings.hideSender {
title += "Show Name" title += presentationData.strings.NotificationSettings_Stories_CompactShowName
} else { } else {
title += "Hide Name" title += presentationData.strings.NotificationSettings_Stories_CompactHideName
} }
} }
} }
@ -1068,8 +1065,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
case .channel: case .channel:
title = presentationData.strings.Notifications_ChannelsTitle title = presentationData.strings.Notifications_ChannelsTitle
case .stories: case .stories:
//TODO:localize title = presentationData.strings.Notifications_StoriesTitle
title = "Stories"
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem)

View File

@ -354,12 +354,11 @@ private func storiesSearchableItems(context: AccountContext) -> [SettingsSearcha
var result: [SettingsSearchableItem] = [] var result: [SettingsSearchableItem] = []
//TODO:localize result.append(SettingsSearchableItem(id: .stories(0), title: strings.Settings_MyStories, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
result.append(SettingsSearchableItem(id: .stories(0), title: "My Stories", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .saved)) present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .saved))
})) }))
result.append(SettingsSearchableItem(id: .stories(1), title: "Stories Archive", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in result.append(SettingsSearchableItem(id: .stories(1), title: strings.Settings_StoriesArchive, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .archive)) present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .archive))
})) }))

View File

@ -396,8 +396,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
case let .invoice(text): case let .invoice(text):
return (NSAttributedString(string: text), true) return (NSAttributedString(string: text), true)
case .story: case .story:
//TODO:localize return (NSAttributedString(string: strings.Message_Story), true)
return (NSAttributedString(string: "Story"), true)
} }
} }

View File

@ -934,13 +934,13 @@ private final class CameraScreenComponent: CombinedComponent {
case .none: case .none:
hintText = " " hintText = " "
case .zoom: case .zoom:
hintText = "Swipe up to zoom" hintText = environment.strings.Story_Camera_SwipeUpToZoom
case .lock: case .lock:
hintText = "Swipe left to lock" hintText = environment.strings.Story_Camera_SwipeLeftToLock
case .releaseLock: case .releaseLock:
hintText = "Release to lock" hintText = environment.strings.Story_Camera_SwipeLeftRelease
case .flip: case .flip:
hintText = "Swipe right to flip" hintText = environment.strings.Story_Camera_SwipeRightToFlip
} }
if let hintText { if let hintText {
let hintLabel = hintLabel.update( let hintLabel = hintLabel.update(
@ -967,6 +967,7 @@ private final class CameraScreenComponent: CombinedComponent {
let modeControl = modeControl.update( let modeControl = modeControl.update(
component: ModeComponent( component: ModeComponent(
isTablet: isTablet, isTablet: isTablet,
strings: environment.strings,
availableModes: [.photo, .video], availableModes: [.photo, .video],
currentMode: component.cameraState.mode, currentMode: component.cameraState.mode,
updatedMode: { [weak state] mode in updatedMode: { [weak state] mode in
@ -1948,7 +1949,7 @@ public class CameraScreen: ViewController {
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 29.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 29.0), size: CGSize())
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Draft Saved"), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipDraftSaved), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .ignore return .ignore
}) })
self.controller?.present(controller, in: .current) self.controller?.present(controller, in: .current)
@ -1963,7 +1964,7 @@ public class CameraScreen: ViewController {
let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let accountManager = self.context.sharedContext.accountManager let accountManager = self.context.sharedContext.accountManager
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Tap here to disable\nthe selfie camera"), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipDisableDual), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
if containerFrame.contains(point) { if containerFrame.contains(point) {
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start() let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
return .dismiss(consume: true) return .dismiss(consume: true)
@ -1983,7 +1984,7 @@ public class CameraScreen: ViewController {
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
let accountManager = self.context.sharedContext.accountManager let accountManager = self.context.sharedContext.accountManager
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipTakePhotos), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in
if containerFrame.contains(point) { if containerFrame.contains(point) {
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start() let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {

View File

@ -3,14 +3,15 @@ import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData
extension CameraMode { extension CameraMode {
var title: String { func title(strings: PresentationStrings) -> String {
switch self { switch self {
case .photo: case .photo:
return "Photo" return strings.Story_Camera_Photo
case .video: case .video:
return "Video" return strings.Story_Camera_Video
} }
} }
} }
@ -19,6 +20,7 @@ private let buttonSize = CGSize(width: 55.0, height: 44.0)
final class ModeComponent: Component { final class ModeComponent: Component {
let isTablet: Bool let isTablet: Bool
let strings: PresentationStrings
let availableModes: [CameraMode] let availableModes: [CameraMode]
let currentMode: CameraMode let currentMode: CameraMode
let updatedMode: (CameraMode) -> Void let updatedMode: (CameraMode) -> Void
@ -26,12 +28,14 @@ final class ModeComponent: Component {
init( init(
isTablet: Bool, isTablet: Bool,
strings: PresentationStrings,
availableModes: [CameraMode], availableModes: [CameraMode],
currentMode: CameraMode, currentMode: CameraMode,
updatedMode: @escaping (CameraMode) -> Void, updatedMode: @escaping (CameraMode) -> Void,
tag: AnyObject? tag: AnyObject?
) { ) {
self.isTablet = isTablet self.isTablet = isTablet
self.strings = strings
self.availableModes = availableModes self.availableModes = availableModes
self.currentMode = currentMode self.currentMode = currentMode
self.updatedMode = updatedMode self.updatedMode = updatedMode
@ -42,6 +46,9 @@ final class ModeComponent: Component {
if lhs.isTablet != rhs.isTablet { if lhs.isTablet != rhs.isTablet {
return false return false
} }
if lhs.strings !== rhs.strings {
return false
}
if lhs.availableModes != rhs.availableModes { if lhs.availableModes != rhs.availableModes {
return false return false
} }
@ -145,7 +152,7 @@ final class ModeComponent: Component {
updatedMode(mode) updatedMode(mode)
} }
itemView.update(value: mode.title, selected: mode == component.currentMode) itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode)
itemView.bounds = CGRect(origin: .zero, size: itemFrame.size) itemView.bounds = CGRect(origin: .zero, size: itemFrame.size)
if isTablet { if isTablet {

View File

@ -55,9 +55,6 @@ final class PlaceholderComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.backgroundColor = UIColor(rgb: 0x1c1c1e) self.backgroundColor = UIColor(rgb: 0x1c1c1e)
// if #available(iOS 13.0, *) {
// self.layer.cornerCurve = .continuous
// }
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -71,9 +68,10 @@ final class PlaceholderComponent: Component {
let sideInset: CGFloat = 36.0 let sideInset: CGFloat = 36.0
let animationHeight: CGFloat = 120.0 let animationHeight: CGFloat = 120.0
let title: String = "Allow Telegram to access your camera and microphone" let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let text: String = "This lets you share photos and record videos." let title = presentationData.strings.Story_Camera_AccessPlaceholderTitle
let buttonTitle: String = "Open Settings" let text = presentationData.strings.Story_Camera_AccessPlaceholderText
let buttonTitle = presentationData.strings.Story_Camera_AccessOpenSettings
let animationSize = self.animation.update( let animationSize = self.animation.update(
transition: .immediate, transition: .immediate,

View File

@ -283,7 +283,6 @@ public final class ChatListNavigationBar: Component {
placeholder = component.strings.Common_Search placeholder = component.strings.Common_Search
compactPlaceholder = component.strings.Common_Search compactPlaceholder = component.strings.Common_Search
//TODO:localize
searchContentNode = NavigationBarSearchContentNode( searchContentNode = NavigationBarSearchContentNode(
theme: component.theme, theme: component.theme,
placeholder: placeholder, placeholder: placeholder,

View File

@ -309,6 +309,25 @@ public final class MediaEditor {
} }
} }
public func replaceSource(_ image: UIImage, additionalImage: UIImage?, time: CMTime) {
func fixImageOrientation(_ image: UIImage) -> UIImage {
UIGraphicsBeginImageContext(image.size)
image.draw(at: .zero)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage ?? image
}
let image = fixImageOrientation(image)
guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice, let texture = loadTexture(image: image, device: device) else {
return
}
let additionalImage = additionalImage.flatMap { fixImageOrientation($0) }
let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) }
self.renderer.consumeTexture(texture, additionalTexture: additionalTexture, time: time, render: true)
}
private var volumeFade: SwiftSignalKit.Timer? private var volumeFade: SwiftSignalKit.Timer?
private func setupSource() { private func setupSource() {
guard let renderTarget = self.previewView else { guard let renderTarget = self.previewView else {
@ -633,6 +652,23 @@ public final class MediaEditor {
} }
} }
public func seek(_ position: Double, completion: @escaping () -> Void) {
guard let player = self.player else {
completion()
return
}
player.pause()
self.additionalPlayer?.pause()
let targetPosition = CMTime(seconds: position, preferredTimescale: CMTimeScale(60.0))
player.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: { _ in
Queue.mainQueue().async {
completion()
}
})
self.additionalPlayer?.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero)
}
public var isPlaying: Bool { public var isPlaying: Bool {
return (self.player?.rate ?? 0.0) > 0.0 return (self.player?.rate ?? 0.0) > 0.0
} }

View File

@ -87,6 +87,9 @@ final class MediaEditorRenderer: TextureConsumer {
private var textureCache: CVMetalTextureCache? private var textureCache: CVMetalTextureCache?
private var currentTexture: MTLTexture? private var currentTexture: MTLTexture?
private var currentAdditionalTexture: MTLTexture?
private var currentTime: CMTime = .zero
private var currentPixelBuffer: VideoPixelBuffer? private var currentPixelBuffer: VideoPixelBuffer?
private var currentAdditionalPixelBuffer: VideoPixelBuffer? private var currentAdditionalPixelBuffer: VideoPixelBuffer?
@ -174,8 +177,8 @@ final class MediaEditorRenderer: TextureConsumer {
self.renderPasses.forEach { $0.setup(device: device, library: library) } self.renderPasses.forEach { $0.setup(device: device, library: library) }
} }
public var displayEnabled = true
var renderPassedEnabled = true var renderPassedEnabled = true
var needsDisplay = false var needsDisplay = false
func renderFrame() { func renderFrame() {
@ -200,7 +203,13 @@ final class MediaEditorRenderer: TextureConsumer {
} }
var texture: MTLTexture var texture: MTLTexture
if let currentTexture = self.currentTexture { if let currentAdditionalTexture = self.currentAdditionalTexture, let currentTexture = self.currentTexture {
if let result = self.videoFinishPass.process(input: currentTexture, secondInput: currentAdditionalTexture, timestamp: self.currentTime, device: device, commandBuffer: commandBuffer) {
texture = result
} else {
texture = currentTexture
}
} else if let currentTexture = self.currentTexture {
texture = currentTexture texture = currentTexture
} else if let currentPixelBuffer = self.currentPixelBuffer, let currentAdditionalPixelBuffer = self.currentAdditionalPixelBuffer, let videoTexture = self.videoInputPass.processPixelBuffer(currentPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer), let additionalVideoTexture = self.additionalVideoInputPass.processPixelBuffer(currentAdditionalPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer) { } else if let currentPixelBuffer = self.currentPixelBuffer, let currentAdditionalPixelBuffer = self.currentAdditionalPixelBuffer, let videoTexture = self.videoInputPass.processPixelBuffer(currentPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer), let additionalVideoTexture = self.additionalVideoInputPass.processPixelBuffer(currentAdditionalPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer) {
if let result = self.videoFinishPass.process(input: videoTexture, secondInput: additionalVideoTexture, timestamp: currentPixelBuffer.timestamp, device: device, commandBuffer: commandBuffer) { if let result = self.videoFinishPass.process(input: videoTexture, secondInput: additionalVideoTexture, timestamp: currentPixelBuffer.timestamp, device: device, commandBuffer: commandBuffer) {
@ -237,7 +246,7 @@ final class MediaEditorRenderer: TextureConsumer {
} }
commandBuffer.commit() commandBuffer.commit()
if let renderTarget = self.renderTarget { if let renderTarget = self.renderTarget, self.displayEnabled {
if self.needsDisplay { if self.needsDisplay {
self.didRenderFrame() self.didRenderFrame()
} else { } else {
@ -299,6 +308,19 @@ final class MediaEditorRenderer: TextureConsumer {
} }
} }
func consumeTexture(_ texture: MTLTexture, additionalTexture: MTLTexture?, time: CMTime, render: Bool) {
if render {
self.willRenderFrame()
}
self.currentTexture = texture
self.currentAdditionalTexture = additionalTexture
self.currentTime = time
if render {
self.renderFrame()
}
}
var previousPresentationTimestamp: CMTime? var previousPresentationTimestamp: CMTime?
func consumeVideoPixelBuffer(pixelBuffer: VideoPixelBuffer, additionalPixelBuffer: VideoPixelBuffer?, render: Bool) { func consumeVideoPixelBuffer(pixelBuffer: VideoPixelBuffer, additionalPixelBuffer: VideoPixelBuffer?, render: Bool) {
self.willRenderFrame() self.willRenderFrame()

View File

@ -4,6 +4,7 @@ import Display
import ComponentFlow import ComponentFlow
import LegacyComponents import LegacyComponents
import MediaEditor import MediaEditor
import TelegramPresentationData
private final class BlurModeComponent: Component { private final class BlurModeComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
@ -115,17 +116,20 @@ private final class BlurModeComponent: Component {
final class BlurComponent: Component { final class BlurComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
let strings: PresentationStrings
let value: BlurValue let value: BlurValue
let hasPortrait: Bool let hasPortrait: Bool
let valueUpdated: (BlurValue) -> Void let valueUpdated: (BlurValue) -> Void
let isTrackingUpdated: (Bool) -> Void let isTrackingUpdated: (Bool) -> Void
init( init(
strings: PresentationStrings,
value: BlurValue, value: BlurValue,
hasPortrait: Bool, hasPortrait: Bool,
valueUpdated: @escaping (BlurValue) -> Void, valueUpdated: @escaping (BlurValue) -> Void,
isTrackingUpdated: @escaping (Bool) -> Void isTrackingUpdated: @escaping (Bool) -> Void
) { ) {
self.strings = strings
self.value = value self.value = value
self.hasPortrait = hasPortrait self.hasPortrait = hasPortrait
self.valueUpdated = valueUpdated self.valueUpdated = valueUpdated
@ -133,6 +137,9 @@ final class BlurComponent: Component {
} }
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool { static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.value != rhs.value { if lhs.value != rhs.value {
return false return false
} }
@ -190,7 +197,7 @@ final class BlurComponent: Component {
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
Text( Text(
text: "Blur", text: component.strings.Story_Editor_Blur_Title,
font: Font.regular(14.0), font: Font.regular(14.0),
color: UIColor(rgb: 0x808080) color: UIColor(rgb: 0x808080)
) )
@ -212,7 +219,7 @@ final class BlurComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
BlurModeComponent( BlurModeComponent(
title: "Off", title: component.strings.Story_Editor_Blur_Off,
icon: self.offImage, icon: self.offImage,
isSelected: state.value.mode == .off isSelected: state.value.mode == .off
) )
@ -233,7 +240,7 @@ final class BlurComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
BlurModeComponent( BlurModeComponent(
title: "Radial", title: component.strings.Story_Editor_Blur_Radial,
icon: self.radialImage, icon: self.radialImage,
isSelected: state.value.mode == .radial isSelected: state.value.mode == .radial
) )
@ -254,7 +261,7 @@ final class BlurComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
BlurModeComponent( BlurModeComponent(
title: "Linear", title: component.strings.Story_Editor_Blur_Linear,
icon: self.linearImage, icon: self.linearImage,
isSelected: state.value.mode == .linear isSelected: state.value.mode == .linear
) )
@ -275,7 +282,7 @@ final class BlurComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
BlurModeComponent( BlurModeComponent(
title: "Portrait", title: component.strings.Story_Editor_Blur_Portrait,
icon: self.portraitImage, icon: self.portraitImage,
isSelected: state.value.mode == .portrait isSelected: state.value.mode == .portrait
) )

View File

@ -5,6 +5,7 @@ import ComponentFlow
import LegacyComponents import LegacyComponents
import MediaEditor import MediaEditor
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData
private class HistogramView: UIView { private class HistogramView: UIView {
private var size: CGSize? private var size: CGSize?
@ -75,18 +76,24 @@ class CurvesInternalState {
final class CurvesComponent: Component { final class CurvesComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
let strings: PresentationStrings
let histogram: MediaEditorHistogram? let histogram: MediaEditorHistogram?
let internalState: CurvesInternalState let internalState: CurvesInternalState
init( init(
strings: PresentationStrings,
histogram: MediaEditorHistogram?, histogram: MediaEditorHistogram?,
internalState: CurvesInternalState internalState: CurvesInternalState
) { ) {
self.strings = strings
self.histogram = histogram self.histogram = histogram
self.internalState = internalState self.internalState = internalState
} }
static func ==(lhs: CurvesComponent, rhs: CurvesComponent) -> Bool { static func ==(lhs: CurvesComponent, rhs: CurvesComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.histogram != rhs.histogram { if lhs.histogram != rhs.histogram {
return false return false
} }
@ -145,7 +152,7 @@ final class CurvesComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
Text( Text(
text: "All", text: component.strings.Story_Editor_Curves_All,
font: Font.regular(14.0), font: Font.regular(14.0),
color: state.section == .all ? .white : UIColor(rgb: 0x808080) color: state.section == .all ? .white : UIColor(rgb: 0x808080)
) )
@ -173,7 +180,7 @@ final class CurvesComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
Text( Text(
text: "Red", text: component.strings.Story_Editor_Curves_Red,
font: Font.regular(14.0), font: Font.regular(14.0),
color: state.section == .red ? .white : UIColor(rgb: 0x808080) color: state.section == .red ? .white : UIColor(rgb: 0x808080)
) )
@ -201,7 +208,7 @@ final class CurvesComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
Text( Text(
text: "Green", text: component.strings.Story_Editor_Curves_Green,
font: Font.regular(14.0), font: Font.regular(14.0),
color: state.section == .green ? .white : UIColor(rgb: 0x808080) color: state.section == .green ? .white : UIColor(rgb: 0x808080)
) )
@ -229,7 +236,7 @@ final class CurvesComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
Text( Text(
text: "Blue", text: component.strings.Story_Editor_Curves_Blue,
font: Font.regular(14.0), font: Font.regular(14.0),
color: state.section == .blue ? .white : UIColor(rgb: 0x808080) color: state.section == .blue ? .white : UIColor(rgb: 0x808080)
) )

View File

@ -725,9 +725,9 @@ final class MediaEditorScreenComponent: Component {
transition.setAlpha(view: cancelButtonView, alpha: component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0) transition.setAlpha(view: cancelButtonView, alpha: component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0)
} }
var doneButtonTitle = "NEXT" var doneButtonTitle = environment.strings.Story_Editor_Next
if let controller = environment.controller() as? MediaEditorScreen, controller.isEditingStory { if let controller = environment.controller() as? MediaEditorScreen, controller.isEditingStory {
doneButtonTitle = "DONE" doneButtonTitle = environment.strings.Story_Editor_Done
} }
let doneButtonSize = self.doneButton.update( let doneButtonSize = self.doneButton.update(
@ -736,7 +736,7 @@ final class MediaEditorScreenComponent: Component {
content: AnyComponent(DoneButtonComponent( content: AnyComponent(DoneButtonComponent(
backgroundColor: UIColor(rgb: 0x007aff), backgroundColor: UIColor(rgb: 0x007aff),
icon: UIImage(bundleImageName: "Media Editor/Next")!, icon: UIImage(bundleImageName: "Media Editor/Next")!,
title: doneButtonTitle)), title: doneButtonTitle.uppercased())),
action: { action: {
guard let controller = environment.controller() as? MediaEditorScreen else { guard let controller = environment.controller() as? MediaEditorScreen else {
return return
@ -1097,7 +1097,7 @@ final class MediaEditorScreenComponent: Component {
theme: environment.theme, theme: environment.theme,
strings: environment.strings, strings: environment.strings,
style: .editor, style: .editor,
placeholder: "Add a caption...", placeholder: environment.strings.Story_Editor_InputPlaceholderAddCaption,
maxLength: Int(component.context.userLimits.maxStoryCaptionLength), maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
queryTypes: [.mention], queryTypes: [.mention],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,
@ -1274,20 +1274,20 @@ final class MediaEditorScreenComponent: Component {
var privacyText: String var privacyText: String
switch component.privacy.privacy.base { switch component.privacy.privacy.base {
case .everyone: case .everyone:
privacyText = "Everyone" privacyText = environment.strings.Story_ContextPrivacy_LabelEveryone
case .closeFriends: case .closeFriends:
privacyText = "Close Friends" privacyText = environment.strings.Story_ContextPrivacy_LabelCloseFriends
case .contacts: case .contacts:
privacyText = "Contacts"
if additionalPeersCount > 0 { if additionalPeersCount > 0 {
privacyText += " (-\(additionalPeersCount))" privacyText = environment.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalPeersCount)").string
} else {
privacyText = environment.strings.Story_ContextPrivacy_LabelContacts
} }
case .nobody: case .nobody:
privacyText = "Selected Contacts"
if additionalPeersCount > 0 { if additionalPeersCount > 0 {
privacyText += " (\(additionalPeersCount))" privacyText = environment.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalPeersCount))
} else { } else {
privacyText = "Only You" privacyText = environment.strings.Story_ContextPrivacy_LabelOnlyMe
} }
} }
@ -2641,7 +2641,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound" : "The story will have sound"), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? self.presentationData.strings.Story_Editor_TooltipMuted : self.presentationData.strings.Story_Editor_TooltipUnmuted), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .ignore return .ignore
}) })
self.muteTooltip = tooltipController self.muteTooltip = tooltipController
@ -2664,9 +2664,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let text: String let text: String
let isVideo = self.mediaEditor?.resultIsVideo ?? false let isVideo = self.mediaEditor?.resultIsVideo ?? false
if isVideo { if isVideo {
text = "Video saved to Photos." text = self.presentationData.strings.Story_Editor_TooltipVideoSavedToPhotos
} else { } else {
text = "Image saved to Photos." text = self.presentationData.strings.Story_Editor_TooltipImageSavedToPhotos
} }
if let tooltipController = self.saveTooltip { if let tooltipController = self.saveTooltip {
@ -2690,8 +2690,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
} }
let text = "Uploading..." let text = self.presentationData.strings.Story_Editor_Uploading
if let tooltipController = self.saveTooltip { if let tooltipController = self.saveTooltip {
tooltipController.content = .progress(text, progress) tooltipController.content = .progress(text, progress)
} else { } else {
@ -2718,8 +2717,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
} }
let text = "Preparing video..." let text = self.presentationData.strings.Story_Editor_PreparingVideo
if let tooltipController = self.saveTooltip { if let tooltipController = self.saveTooltip {
tooltipController.content = .progress(text, progress) tooltipController.content = .progress(text, progress)
} else { } else {
@ -3081,7 +3079,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
ToolValueComponent( ToolValueComponent(
title: "Enhance", title: environment.strings.Story_Editor_Tool_Enhance,
value: "\(Int(abs(enhanceValue) * 100.0))" value: "\(Int(abs(enhanceValue) * 100.0))"
) )
), ),
@ -3441,14 +3439,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, isForwardingDisabled: self.state.privacy.isForwardingDisabled, pin: self.state.privacy.pin) self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, isForwardingDisabled: self.state.privacy.isForwardingDisabled, pin: self.state.privacy.pin)
} }
let title = "Choose how long the story will be visible." let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let title = presentationData.strings.Story_Editor_ExpirationText
let currentValue = self.state.privacy.timeout let currentValue = self.state.privacy.timeout
let currentArchived = self.state.privacy.pin let currentArchived = self.state.privacy.pin
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction))) items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
items.append(.action(ContextMenuActionItem(text: "6 Hours", icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(6), icon: { theme in
if !hasPremium { if !hasPremium {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
} else { } else {
@ -3463,7 +3462,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self?.presentTimeoutPremiumSuggestion(3600 * 6) self?.presentTimeoutPremiumSuggestion(3600 * 6)
} }
}))) })))
items.append(.action(ContextMenuActionItem(text: "12 Hours", icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(12), icon: { theme in
if !hasPremium { if !hasPremium {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
} else { } else {
@ -3478,14 +3477,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self?.presentTimeoutPremiumSuggestion(3600 * 12) self?.presentTimeoutPremiumSuggestion(3600 * 12)
} }
}))) })))
items.append(.action(ContextMenuActionItem(text: "24 Hours", icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(24), icon: { theme in
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in }, action: { _, a in
a(.default) a(.default)
updateTimeout(86400) updateTimeout(86400)
}))) })))
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(48), icon: { theme in
if !hasPremium { if !hasPremium {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
} else { } else {
@ -3501,7 +3500,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
}))) })))
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.present(contextController, in: .window(.root)) self.present(contextController, in: .window(.root))
} }
@ -3510,10 +3508,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let timeoutString = presentationData.strings.MuteExpires_Hours(max(1, timeout / (60 * 60))) let timeoutString = presentationData.strings.MuteExpires_Hours(max(1, timeout / (60 * 60)))
let text = "Subscribe to **Telegram Premium** to make your stories disappear \(timeoutString)." let text = presentationData.strings.Story_Editor_TooltipPremiumCustomExpiration(timeoutString).string
let context = self.context let context = self.context
let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: "More"), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: presentationData.strings.Story_Editor_TooltipPremiumMore), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
if case .undo = action, let self { if case .undo = action, let self {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
self.push(controller) self.push(controller)
@ -3549,23 +3547,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return return
} }
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let title: String let title: String
let save: String let save: String
if case .draft = self.node.subject { if case .draft = self.node.subject {
title = "Discard Draft?" title = presentationData.strings.Story_Editor_DraftDiscardDraft
save = "Keep Draft" save = presentationData.strings.Story_Editor_DraftKeepDraft
} else { } else {
title = "Discard Media?" title = presentationData.strings.Story_Editor_DraftDiscardMedia
save = "Save Draft" save = presentationData.strings.Story_Editor_DraftKeepMedia
} }
let theme = defaultDarkPresentationTheme let theme = defaultDarkPresentationTheme
let controller = textAlertController( let controller = textAlertController(
context: self.context, context: self.context,
forceTheme: theme, forceTheme: theme,
title: title, title: title,
text: "If you go back now, you will lose any changes that you've made.", text: presentationData.strings.Story_Editor_DraftDiscaedText,
actions: [ actions: [
TextAlertAction(type: .destructiveAction, title: "Discard", action: { [weak self] in TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in
if let self { if let self {
self.requestDismiss(saveDraft: false, animated: true) self.requestDismiss(saveDraft: false, animated: true)
} }
@ -3575,7 +3574,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.requestDismiss(saveDraft: true, animated: true) self.requestDismiss(saveDraft: true, animated: true)
} }
}), }),
TextAlertAction(type: .genericAction, title: "Cancel", action: { TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}) })
], ],
@ -3715,8 +3714,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.dismissAllTooltips() self.dismissAllTooltips()
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false) mediaEditor.stop()
mediaEditor.requestRenderFrame()
mediaEditor.invalidate() mediaEditor.invalidate()
self.node.entitiesView.invalidate() self.node.entitiesView.invalidate()
@ -3777,7 +3775,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
if mediaEditor.resultIsVideo { if mediaEditor.resultIsVideo {
var firstFrame: Signal<UIImage?, NoError> var firstFrame: Signal<(UIImage?, UIImage?), NoError>
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60)) let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
let videoResult: Result.VideoResult let videoResult: Result.VideoResult
@ -3791,8 +3789,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
videoResult = .imageFile(path: tempImagePath) videoResult = .imageFile(path: tempImagePath)
duration = 5.0 duration = 5.0
firstFrame = .single(image) firstFrame = .single((image, nil))
case let .video(path, _, _, _, _, _, durationValue, _, _): case let .video(path, _, _, additionalPath, _, _, durationValue, _, _):
videoResult = .videoFile(path: path) videoResult = .videoFile(path: path)
if let videoTrimRange = mediaEditor.values.videoTrimRange { if let videoTrimRange = mediaEditor.values.videoTrimRange {
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
@ -3800,12 +3798,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
duration = durationValue duration = durationValue
} }
firstFrame = Signal<UIImage?, NoError> { subscriber in let _ = additionalPath
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: path)) let avAsset = AVURLAsset(url: URL(fileURLWithPath: path))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset) let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage { if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage)) subscriber.putNext((UIImage(cgImage: cgImage), nil))
subscriber.putCompletion() subscriber.putCompletion()
} }
}) })
@ -3824,14 +3825,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else { } else {
duration = 5.0 duration = 5.0
} }
firstFrame = Signal<UIImage?, NoError> { subscriber in firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
if asset.mediaType == .video { if asset.mediaType == .video {
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if let avAsset { if let avAsset {
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset) let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage { if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage)) subscriber.putNext((UIImage(cgImage: cgImage), nil))
subscriber.putCompletion() subscriber.putCompletion()
} }
}) })
@ -3842,7 +3844,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
options.deliveryMode = .highQualityFormat options.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
if let image { if let image {
subscriber.putNext(image) subscriber.putNext((image, nil))
subscriber.putCompletion() subscriber.putCompletion()
} }
} }
@ -3857,12 +3859,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else { } else {
duration = min(draft.duration ?? 5.0, storyMaxVideoDuration) duration = min(draft.duration ?? 5.0, storyMaxVideoDuration)
} }
firstFrame = Signal<UIImage?, NoError> { subscriber in firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath())) let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath()))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset) let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage { if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage)) subscriber.putNext((UIImage(cgImage: cgImage), nil))
subscriber.putCompletion() subscriber.putCompletion()
} }
}) })
@ -3875,21 +3878,34 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
duration = 5.0 duration = 5.0
if let image = UIImage(contentsOfFile: draft.fullPath()) { if let image = UIImage(contentsOfFile: draft.fullPath()) {
firstFrame = .single(image) firstFrame = .single((image, nil))
} else { } else {
firstFrame = .single(UIImage()) firstFrame = .single((UIImage(), nil))
} }
} }
} }
if let resultImage = mediaEditor.resultImage {
firstFrame = .single(resultImage)
}
let _ = (firstFrame let _ = (firstFrame
|> deliverOnMainQueue).start(next: { [weak self] image in |> deliverOnMainQueue).start(next: { [weak self] image, additionalImage in
if let self { if let self {
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in var currentImage = mediaEditor.resultImage
if let image {
mediaEditor.replaceSource(image, additionalImage: additionalImage, time: firstFrameTime)
if let updatedImage = mediaEditor.resultImage {
currentImage = updatedImage
}
}
var inputImage: UIImage
if let currentImage {
inputImage = currentImage
} else if let image {
inputImage = image
} else {
inputImage = UIImage()
}
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
if let self { if let self {
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)") Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in

View File

@ -335,6 +335,7 @@ private final class MediaToolsScreenComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let isTablet: Bool let isTablet: Bool
if case .regular = environment.metrics.widthClass { if case .regular = environment.metrics.widthClass {
isTablet = true isTablet = true
@ -577,7 +578,7 @@ private final class MediaToolsScreenComponent: Component {
var tools: [AdjustmentTool] = [ var tools: [AdjustmentTool] = [
AdjustmentTool( AdjustmentTool(
key: .enhance, key: .enhance,
title: "Enhance", title: presentationData.strings.Story_Editor_Tool_Enhance,
value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0,
minValue: 0.0, minValue: 0.0,
maxValue: 1.0, maxValue: 1.0,
@ -585,7 +586,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .brightness, key: .brightness,
title: "Brightness", title: presentationData.strings.Story_Editor_Tool_Brightness,
value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0,
minValue: -1.0, minValue: -1.0,
maxValue: 1.0, maxValue: 1.0,
@ -593,7 +594,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .contrast, key: .contrast,
title: "Contrast", title: presentationData.strings.Story_Editor_Tool_Contrast,
value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0,
minValue: -1.0, minValue: -1.0,
maxValue: 1.0, maxValue: 1.0,
@ -601,7 +602,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .saturation, key: .saturation,
title: "Saturation", title: presentationData.strings.Story_Editor_Tool_Saturation,
value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0,
minValue: -1.0, minValue: -1.0,
maxValue: 1.0, maxValue: 1.0,
@ -609,7 +610,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .warmth, key: .warmth,
title: "Warmth", title: presentationData.strings.Story_Editor_Tool_Warmth,
value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0,
minValue: -1.0, minValue: -1.0,
maxValue: 1.0, maxValue: 1.0,
@ -617,7 +618,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .fade, key: .fade,
title: "Fade", title: presentationData.strings.Story_Editor_Tool_Fade,
value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0,
minValue: 0.0, minValue: 0.0,
maxValue: 1.0, maxValue: 1.0,
@ -625,7 +626,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .highlights, key: .highlights,
title: "Highlights", title: presentationData.strings.Story_Editor_Tool_Highlights,
value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0,
minValue: -1.0, minValue: -1.0,
maxValue: 1.0, maxValue: 1.0,
@ -633,7 +634,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .shadows, key: .shadows,
title: "Shadows", title: presentationData.strings.Story_Editor_Tool_Shadows,
value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0,
minValue: -1.0, minValue: -1.0,
maxValue: 1.0, maxValue: 1.0,
@ -641,7 +642,7 @@ private final class MediaToolsScreenComponent: Component {
), ),
AdjustmentTool( AdjustmentTool(
key: .vignette, key: .vignette,
title: "Vignette", title: presentationData.strings.Story_Editor_Tool_Vignette,
value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0,
minValue: 0.0, minValue: 0.0,
maxValue: 1.0, maxValue: 1.0,
@ -660,7 +661,7 @@ private final class MediaToolsScreenComponent: Component {
if !component.mediaEditor.sourceIsVideo { if !component.mediaEditor.sourceIsVideo {
tools.insert(AdjustmentTool( tools.insert(AdjustmentTool(
key: .grain, key: .grain,
title: "Grain", title: presentationData.strings.Story_Editor_Tool_Grain,
value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0, value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0,
minValue: 0.0, minValue: 0.0,
maxValue: 1.0, maxValue: 1.0,
@ -721,6 +722,7 @@ private final class MediaToolsScreenComponent: Component {
optionsSize = self.toolOptions.update( optionsSize = self.toolOptions.update(
transition: optionsTransition, transition: optionsTransition,
component: AnyComponent(TintComponent( component: AnyComponent(TintComponent(
strings: presentationData.strings,
shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial, shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial,
highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial, highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial,
shadowsValueUpdated: { [weak state] value in shadowsValueUpdated: { [weak state] value in
@ -778,6 +780,7 @@ private final class MediaToolsScreenComponent: Component {
optionsSize = self.toolOptions.update( optionsSize = self.toolOptions.update(
transition: optionsTransition, transition: optionsTransition,
component: AnyComponent(BlurComponent( component: AnyComponent(BlurComponent(
strings: presentationData.strings,
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial, value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
hasPortrait: mediaEditor?.hasPortraitMask ?? false, hasPortrait: mediaEditor?.hasPortraitMask ?? false,
valueUpdated: { [weak state] value in valueUpdated: { [weak state] value in
@ -853,6 +856,7 @@ private final class MediaToolsScreenComponent: Component {
optionsSize = self.toolOptions.update( optionsSize = self.toolOptions.update(
transition: optionsTransition, transition: optionsTransition,
component: AnyComponent(CurvesComponent( component: AnyComponent(CurvesComponent(
strings: presentationData.strings,
histogram: state.histogram, histogram: state.histogram,
internalState: internalState internalState: internalState
)), )),

View File

@ -218,10 +218,12 @@ final class StoryPreviewComponent: Component {
} }
} }
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let titleSize = self.title.update( let titleSize = self.title.update(
transition: transition, transition: transition,
component: AnyComponent(Text( component: AnyComponent(Text(
text: "My story", text: presentationData.strings.Story_HeaderYourStory,
font: Font.medium(14.0), font: Font.medium(14.0),
color: .white color: .white
)), )),
@ -240,7 +242,6 @@ final class StoryPreviewComponent: Component {
transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size)) transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size))
} }
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let inputPanelSize = self.inputPanel.update( let inputPanelSize = self.inputPanel.update(
transition: transition, transition: transition,
component: AnyComponent(MessageInputPanelComponent( component: AnyComponent(MessageInputPanelComponent(
@ -249,7 +250,7 @@ final class StoryPreviewComponent: Component {
theme: presentationData.theme, theme: presentationData.theme,
strings: presentationData.strings, strings: presentationData.strings,
style: .story, style: .story,
placeholder: "Reply Privately...", placeholder: presentationData.strings.Story_InputPlaceholderReplyPrivately,
maxLength: nil, maxLength: nil,
queryTypes: [], queryTypes: [],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,

View File

@ -4,6 +4,7 @@ import Display
import ComponentFlow import ComponentFlow
import LegacyComponents import LegacyComponents
import MediaEditor import MediaEditor
import TelegramPresentationData
private final class TintColorComponent: Component { private final class TintColorComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
@ -106,6 +107,7 @@ final class TintComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
let strings: PresentationStrings
let shadowsValue: TintValue let shadowsValue: TintValue
let highlightsValue: TintValue let highlightsValue: TintValue
let shadowsValueUpdated: (TintValue) -> Void let shadowsValueUpdated: (TintValue) -> Void
@ -113,12 +115,14 @@ final class TintComponent: Component {
let isTrackingUpdated: (Bool) -> Void let isTrackingUpdated: (Bool) -> Void
init( init(
strings: PresentationStrings,
shadowsValue: TintValue, shadowsValue: TintValue,
highlightsValue: TintValue, highlightsValue: TintValue,
shadowsValueUpdated: @escaping (TintValue) -> Void, shadowsValueUpdated: @escaping (TintValue) -> Void,
highlightsValueUpdated: @escaping (TintValue) -> Void, highlightsValueUpdated: @escaping (TintValue) -> Void,
isTrackingUpdated: @escaping (Bool) -> Void isTrackingUpdated: @escaping (Bool) -> Void
) { ) {
self.strings = strings
self.shadowsValue = shadowsValue self.shadowsValue = shadowsValue
self.highlightsValue = highlightsValue self.highlightsValue = highlightsValue
self.shadowsValueUpdated = shadowsValueUpdated self.shadowsValueUpdated = shadowsValueUpdated
@ -127,6 +131,9 @@ final class TintComponent: Component {
} }
static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool { static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.highlightsValue != rhs.highlightsValue { if lhs.highlightsValue != rhs.highlightsValue {
return false return false
} }
@ -185,7 +192,7 @@ final class TintComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
Text( Text(
text: "Shadows", text: component.strings.Story_Editor_Tint_Shadows,
font: Font.regular(14.0), font: Font.regular(14.0),
color: state.section == .shadows ? .white : UIColor(rgb: 0x808080) color: state.section == .shadows ? .white : UIColor(rgb: 0x808080)
) )
@ -213,7 +220,7 @@ final class TintComponent: Component {
Button( Button(
content: AnyComponent( content: AnyComponent(
Text( Text(
text: "Highlights", text: component.strings.Story_Editor_Tint_Highlights,
font: Font.regular(14.0), font: Font.regular(14.0),
color: state.section == .highlights ? .white : UIColor(rgb: 0x808080) color: state.section == .highlights ? .white : UIColor(rgb: 0x808080)
) )

View File

@ -726,8 +726,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
if isStories == nil || isStories == true { if isStories == nil || isStories == true {
if case .user = peer { if case .user = peer {
//TODO:localize entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notification_Exceptions_StoriesHeader))
entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: "STORY NOTIFICATIONS"))
index += 1 index += 1
entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesMuted == .alwaysOn)) entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesMuted == .alwaysOn))
index += 1 index += 1
@ -735,7 +734,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
index += 1 index += 1
if state.storiesMuted != .alwaysOff { if state.storiesMuted != .alwaysOff {
entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: "DISPLAY AUTHOR NAME")) entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notification_Exceptions_StoriesDisplayAuthorName))
index += 1 index += 1
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn)) entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn))
index += 1 index += 1

View File

@ -88,9 +88,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
let strings = presentationData.strings let strings = presentationData.strings
if self.selectedCount != 0 { if self.selectedCount != 0 {
//TODO:localize items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryList_ContextSaveToGallery, icon: { theme in
//TODO:update icon
items.append(.action(ContextMenuActionItem(text: "Save to Photos", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -114,15 +112,9 @@ final class PeerInfoStoryGridScreenComponent: Component {
} }
let _ = component.context.engine.messages.deleteStories(ids: Array(paneNode.selectedIds)).start() let _ = component.context.engine.messages.deleteStories(ids: Array(paneNode.selectedIds)).start()
//TODO:localize
let text: String
if paneNode.selectedIds.count == 1 {
text = "1 story deleted."
} else {
text = "\(paneNode.selectedIds.count) stories deleted."
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let text: String = presentationData.strings.StoryList_TooltipStoriesDeleted(Int32(paneNode.selectedIds.count))
environment.controller()?.present(UndoOverlayController( environment.controller()?.present(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .info(title: nil, text: text, timeout: nil), content: .info(title: nil, text: text, timeout: nil),
@ -163,8 +155,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
if component.peerId == component.context.account.peerId, case .saved = component.scope { if component.peerId == component.context.account.peerId, case .saved = component.scope {
var ignoreNextActions = false var ignoreNextActions = false
//TODO:localize items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryList_ContextShowArchive, icon: { theme in
items.append(.action(ContextMenuActionItem(text: "Show Archive", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StoryArchive"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StoryArchive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
if ignoreNextActions { if ignoreNextActions {
@ -305,8 +296,8 @@ final class PeerInfoStoryGridScreenComponent: Component {
return return
} }
//TODO:localize let strings = (component.context.sharedContext.currentPresentationData.with { $0 }).strings
let saveScreen = SaveProgressScreen(context: component.context, content: .progress("Saving", 0.0)) let saveScreen = SaveProgressScreen(context: component.context, content: .progress(strings.Story_TooltipSaving, 0.0))
self.environment?.controller()?.present(saveScreen, in: .current) self.environment?.controller()?.present(saveScreen, in: .current)
let valueNorm: Float = 1.0 / Float(sortedItems.count) let valueNorm: Float = 1.0 / Float(sortedItems.count)
@ -330,12 +321,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
guard let saveScreen else { guard let saveScreen else {
return return
} }
saveScreen.content = .progress("Saving", progress) saveScreen.content = .progress(strings.Story_TooltipSaving, progress)
}, completed: { [weak saveScreen] in }, completed: { [weak saveScreen] in
guard let saveScreen else { guard let saveScreen else {
return return
} }
saveScreen.content = .completion("Saved") saveScreen.content = .completion(strings.Story_TooltipSaved)
Queue.mainQueue().after(3.0, { [weak saveScreen] in Queue.mainQueue().after(3.0, { [weak saveScreen] in
saveScreen?.dismiss() saveScreen?.dismiss()
}) })
@ -376,12 +367,11 @@ final class PeerInfoStoryGridScreenComponent: Component {
self.selectionPanel = selectionPanel self.selectionPanel = selectionPanel
} }
//TODO:localize
let selectionPanelSize = selectionPanel.update( let selectionPanelSize = selectionPanel.update(
transition: selectionPanelTransition, transition: selectionPanelTransition,
component: AnyComponent(BottomButtonPanelComponent( component: AnyComponent(BottomButtonPanelComponent(
theme: environment.theme, theme: environment.theme,
title: "Save to Profile", title: environment.strings.StoryList_SaveToProfile,
label: nil, label: nil,
isEnabled: true, isEnabled: true,
insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset), insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset),
@ -395,18 +385,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
let _ = component.context.engine.messages.updateStoriesArePinned(ids: paneNode.selectedItems, isPinned: true).start() let _ = component.context.engine.messages.updateStoriesArePinned(ids: paneNode.selectedItems, isPinned: true).start()
//TODO:localize
let title: String
if paneNode.selectedIds.count == 1 {
title = "Story saved to your profile"
} else {
title = "\(paneNode.selectedIds.count) saved to your profile"
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count))
environment.controller()?.present(UndoOverlayController( environment.controller()?.present(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .info(title: title, text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil), content: .info(title: title, text: presentationData.strings.StoryList_TooltipStoriesSavedToProfileText, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -596,7 +580,8 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
} }
func updateTitle() { func updateTitle() {
//TODO:localize let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
switch self.scope { switch self.scope {
case .saved: case .saved:
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else { guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
@ -608,16 +593,16 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
} else { } else {
title = nil title = nil
} }
self.titleView?.titleContent = .custom("My Stories", title, false) self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
case .archive: case .archive:
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else { guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
return return
} }
let title: String let title: String
if componentView.selectedCount != 0 { if componentView.selectedCount != 0 {
title = "\(componentView.selectedCount) Selected" title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
} else { } else {
title = "Stories Archive" title = presentationData.strings.StoryList_TitleArchive
} }
self.titleView?.titleContent = .custom(title, nil, false) self.titleView?.titleContent = .custom(title, nil, false)
} }

View File

@ -1560,17 +1560,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let title: String let title: String
if state.totalCount == 0 { if state.totalCount == 0 {
title = "" title = ""
} else if state.totalCount == 1 {
if self.isSaved {
title = "1 saved story"
} else {
title = "1 story"
}
} else { } else {
if self.isSaved { if self.isSaved {
title = "\(state.totalCount) saved stories" title = self.presentationData.strings.StoryList_SubtitleSaved(Int32(state.totalCount))
} else { } else {
title = "\(state.totalCount) stories" title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount))
} }
} }
self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: .stories))) self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: .stories)))
@ -1607,8 +1601,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
var headerText: String? var headerText: String?
if strongSelf.isArchive && !mappedItems.isEmpty { if strongSelf.isArchive && !mappedItems.isEmpty {
//TODO:localize headerText = strongSelf.presentationData.strings.StoryList_ArchiveDescription
headerText = "Only you can see archived stories unless you choose to save them to your profile."
} }
let items = SparseItemGrid.Items( let items = SparseItemGrid.Items(
@ -1914,16 +1907,15 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
emptyStateView = ComponentView() emptyStateView = ComponentView()
self.emptyStateView = emptyStateView self.emptyStateView = emptyStateView
} }
//TODO:localize
let emptyStateSize = emptyStateView.update( let emptyStateSize = emptyStateView.update(
transition: emptyStateTransition, transition: emptyStateTransition,
component: AnyComponent(EmptyStateIndicatorComponent( component: AnyComponent(EmptyStateIndicatorComponent(
context: self.context, context: self.context,
theme: presentationData.theme, theme: presentationData.theme,
animationName: "StoryListEmpty", animationName: "StoryListEmpty",
title: self.isArchive ? "No Archived Stories" : "No saved stories", title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title,
text: self.isArchive ? "Upload a new story to view it here" : "Open the Archive to select stories you\nwant to be displayed in your profile.", text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text,
actionTitle: self.isArchive ? nil : "Open Archive", actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
action: { [weak self] in action: { [weak self] in
guard let self else { guard let self else {
return return

View File

@ -128,12 +128,11 @@ public final class ArchiveInfoContentComponent: Component {
contentHeight += 15.0 contentHeight += 15.0
let titleString = NSMutableAttributedString() let titleString = NSMutableAttributedString()
titleString.append(NSAttributedString(string: "This is Your Archive", font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor)) titleString.append(NSAttributedString(string: component.strings.ArchiveInfo_Title, font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor))
let imageAttachment = NSTextAttachment() let imageAttachment = NSTextAttachment()
imageAttachment.image = self.iconBackground.image imageAttachment.image = self.iconBackground.image
titleString.append(NSAttributedString(attachment: imageAttachment)) titleString.append(NSAttributedString(attachment: imageAttachment))
//TODO:localize
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
@ -153,11 +152,10 @@ public final class ArchiveInfoContentComponent: Component {
contentHeight += 16.0 contentHeight += 16.0
let text: String let text: String
//TODO:localize
if component.settings.keepArchivedUnmuted { if component.settings.keepArchivedUnmuted {
text = "Archived chats will remain in the Archive when you receive a new message. [Tap to change >]()" text = component.strings.ArchiveInfo_TextKeepArchivedUnmuted
} else { } else {
text = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change >]()" text = component.strings.ArchiveInfo_TextKeepArchivedDefault
} }
let mainText = NSMutableAttributedString() let mainText = NSMutableAttributedString()
@ -229,18 +227,18 @@ public final class ArchiveInfoContentComponent: Component {
let itemDescs: [ItemDesc] = [ let itemDescs: [ItemDesc] = [
ItemDesc( ItemDesc(
icon: "Chat List/Archive/IconArchived", icon: "Chat List/Archive/IconArchived",
title: "Archived Chats", title: component.strings.ArchiveInfo_ChatsTitle,
text: "Move any chat into your Archive and back by swiping on it." text: component.strings.ArchiveInfo_ChatsText
), ),
ItemDesc( ItemDesc(
icon: "Chat List/Archive/IconHide", icon: "Chat List/Archive/IconHide",
title: "Hiding Archive", title: component.strings.ArchiveInfo_HideTitle,
text: "Hide the Archive from your Main screen by swiping on it." text: component.strings.ArchiveInfo_HideText
), ),
ItemDesc( ItemDesc(
icon: "Chat List/Archive/IconStories", icon: "Chat List/Archive/IconStories",
title: "Stories", title: component.strings.ArchiveInfo_StoriesTitle,
text: "Archive Stories from your contacts separately from chats with them." text: component.strings.ArchiveInfo_StoriesText
) )
] ]
for i in 0 ..< itemDescs.count { for i in 0 ..< itemDescs.count {

View File

@ -76,7 +76,6 @@ private final class ArchiveInfoSheetContentComponent: Component {
contentHeight += contentSize.height contentHeight += contentSize.height
contentHeight += 30.0 contentHeight += 30.0
//TODO:localize
let buttonSize = self.button.update( let buttonSize = self.button.update(
transition: transition, transition: transition,
component: AnyComponent(ButtonComponent( component: AnyComponent(ButtonComponent(
@ -86,7 +85,7 @@ private final class ArchiveInfoSheetContentComponent: Component {
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
), ),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: "Got it", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) Text(text: environment.strings.ArchiveInfo_CloseAction, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
)), )),
isEnabled: true, isEnabled: true,
displaysProgress: false, displaysProgress: false,

View File

@ -310,8 +310,7 @@ final class StorageUsageScreenComponent: Component {
case .misc: case .misc:
return strings.StorageManagement_SectionMiscellaneous return strings.StorageManagement_SectionMiscellaneous
case .stories: case .stories:
//TODO:localize return strings.StorageManagement_SectionStories
return "Stories"
} }
} }
@ -1810,8 +1809,7 @@ final class StorageUsageScreenComponent: Component {
mappedCategory = .groups mappedCategory = .groups
case 3: case 3:
iconName = "Settings/Menu/Stories" iconName = "Settings/Menu/Stories"
//TODO:localized title = environment.strings.Notifications_Stories
title = "Stories"
mappedCategory = .stories mappedCategory = .stories
default: default:
iconName = "Settings/Menu/Channels" iconName = "Settings/Menu/Channels"

View File

@ -6,6 +6,7 @@ import AccountContext
import TelegramCore import TelegramCore
import TelegramStringFormatting import TelegramStringFormatting
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData
final class StoryAuthorInfoComponent: Component { final class StoryAuthorInfoComponent: Component {
struct Counters: Equatable { struct Counters: Equatable {
@ -14,13 +15,15 @@ final class StoryAuthorInfoComponent: Component {
} }
let context: AccountContext let context: AccountContext
let strings: PresentationStrings
let peer: EnginePeer? let peer: EnginePeer?
let timestamp: Int32 let timestamp: Int32
let counters: Counters? let counters: Counters?
let isEdited: Bool let isEdited: Bool
init(context: AccountContext, peer: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) { init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) {
self.context = context self.context = context
self.strings = strings
self.peer = peer self.peer = peer
self.timestamp = timestamp self.timestamp = timestamp
self.counters = counters self.counters = counters
@ -31,6 +34,9 @@ final class StoryAuthorInfoComponent: Component {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.strings !== rhs.strings {
return false
}
if lhs.peer != rhs.peer { if lhs.peer != rhs.peer {
return false return false
} }
@ -75,8 +81,7 @@ final class StoryAuthorInfoComponent: Component {
let title: String let title: String
if component.peer?.id == component.context.account.peerId { if component.peer?.id == component.context.account.peerId {
//TODO:localize title = component.strings.Story_HeaderYourStory
title = "Your story"
} else { } else {
title = component.peer?.debugDisplayTitle ?? "" title = component.peer?.debugDisplayTitle ?? ""
} }
@ -86,7 +91,7 @@ final class StoryAuthorInfoComponent: Component {
if component.isEdited { if component.isEdited {
subtitle.append("") subtitle.append("")
subtitle.append("edited") subtitle.append(component.strings.Story_HeaderEdited)
} }
let titleSize = self.title.update( let titleSize = self.title.update(

View File

@ -9,6 +9,7 @@ import TextNodeWithEntities
import TextFormat import TextFormat
import InvisibleInkDustNode import InvisibleInkDustNode
import UrlEscaping import UrlEscaping
import TelegramPresentationData
final class StoryContentCaptionComponent: Component { final class StoryContentCaptionComponent: Component {
enum Action { enum Action {
@ -41,6 +42,7 @@ final class StoryContentCaptionComponent: Component {
let externalState: ExternalState let externalState: ExternalState
let context: AccountContext let context: AccountContext
let strings: PresentationStrings
let text: String let text: String
let entities: [MessageTextEntity] let entities: [MessageTextEntity]
let entityFiles: [EngineMedia.Id: TelegramMediaFile] let entityFiles: [EngineMedia.Id: TelegramMediaFile]
@ -50,6 +52,7 @@ final class StoryContentCaptionComponent: Component {
init( init(
externalState: ExternalState, externalState: ExternalState,
context: AccountContext, context: AccountContext,
strings: PresentationStrings,
text: String, text: String,
entities: [MessageTextEntity], entities: [MessageTextEntity],
entityFiles: [EngineMedia.Id: TelegramMediaFile], entityFiles: [EngineMedia.Id: TelegramMediaFile],
@ -58,6 +61,7 @@ final class StoryContentCaptionComponent: Component {
) { ) {
self.externalState = externalState self.externalState = externalState
self.context = context self.context = context
self.strings = strings
self.text = text self.text = text
self.entities = entities self.entities = entities
self.entityFiles = entityFiles self.entityFiles = entityFiles
@ -72,6 +76,9 @@ final class StoryContentCaptionComponent: Component {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.strings !== rhs.strings {
return false
}
if lhs.text != rhs.text { if lhs.text != rhs.text {
return false return false
} }
@ -425,9 +432,8 @@ final class StoryContentCaptionComponent: Component {
let truncationToken = NSMutableAttributedString() let truncationToken = NSMutableAttributedString()
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(16.0), textColor: .white)) truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(16.0), textColor: .white))
truncationToken.append(NSAttributedString(string: "Show more", font: Font.semibold(16.0), textColor: .white)) truncationToken.append(NSAttributedString(string: component.strings.Story_CaptionShowMore, font: Font.semibold(16.0), textColor: .white))
//TODO:localize
let collapsedTextLayout = TextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(TextNodeLayoutArguments( let collapsedTextLayout = TextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(TextNodeLayoutArguments(
attributedString: attributedText, attributedString: attributedText,
maximumNumberOfLines: 3, maximumNumberOfLines: 3,

View File

@ -31,13 +31,15 @@ final class StoryItemContentComponent: Component {
let peer: EnginePeer let peer: EnginePeer
let item: EngineStoryItem let item: EngineStoryItem
let audioMode: StoryContentItem.AudioMode let audioMode: StoryContentItem.AudioMode
let isVideoBuffering: Bool
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, audioMode: StoryContentItem.AudioMode) { init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool) {
self.context = context self.context = context
self.strings = strings self.strings = strings
self.peer = peer self.peer = peer
self.item = item self.item = item
self.audioMode = audioMode self.audioMode = audioMode
self.isVideoBuffering = isVideoBuffering
} }
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool { static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
@ -53,6 +55,9 @@ final class StoryItemContentComponent: Component {
if lhs.item != rhs.item { if lhs.item != rhs.item {
return false return false
} }
if lhs.isVideoBuffering != rhs.isVideoBuffering {
return false
}
return true return true
} }
@ -592,11 +597,10 @@ final class StoryItemContentComponent: Component {
self.unsupportedButton = unsupportedButton self.unsupportedButton = unsupportedButton
} }
//TODO:localize
let unsupportedTextSize = unsupportedText.update( let unsupportedTextSize = unsupportedText.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "This story is not supported by\nyour version of Telegram.", font: Font.regular(17.0), textColor: .white)), text: .plain(NSAttributedString(string: component.strings.Story_UnsupportedText, font: Font.regular(17.0), textColor: .white)),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)), )),
@ -611,7 +615,7 @@ final class StoryItemContentComponent: Component {
foreground: environment.theme.list.itemCheckColors.foregroundColor, foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.7) pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.7)
), ),
content: AnyComponentWithIdentity(id: AnyHashable(""), component: AnyComponent(Text(text: "Update Telegram", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor content: AnyComponentWithIdentity(id: AnyHashable(""), component: AnyComponent(Text(text: component.strings.Story_UnsupportedAction, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor
))), ))),
isEnabled: true, isEnabled: true,
displaysProgress: false, displaysProgress: false,
@ -655,10 +659,13 @@ final class StoryItemContentComponent: Component {
self.updateProgressMode(update: false) self.updateProgressMode(update: false)
if reloadMedia && synchronousLoad { if reloadMedia && synchronousLoad {
let _ = startTime
#if DEBUG
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
#endif
} }
if !self.contentLoaded { if !self.contentLoaded || component.isVideoBuffering {
let loadingEffectView: StoryItemLoadingEffectView let loadingEffectView: StoryItemLoadingEffectView
if let current = self.loadingEffectView { if let current = self.loadingEffectView {
loadingEffectView = current loadingEffectView = current
@ -668,7 +675,7 @@ final class StoryItemContentComponent: Component {
self.addSubview(loadingEffectView) self.addSubview(loadingEffectView)
} }
loadingEffectView.update(size: availableSize, transition: transition) loadingEffectView.update(size: availableSize, transition: transition)
} else if let loadingEffectView = self.loadingEffectView{ } else if let loadingEffectView = self.loadingEffectView {
self.loadingEffectView = nil self.loadingEffectView = nil
loadingEffectView.layer.animateAlpha(from: loadingEffectView.alpha, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak loadingEffectView] _ in loadingEffectView.layer.animateAlpha(from: loadingEffectView.alpha, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak loadingEffectView] _ in
loadingEffectView?.removeFromSuperview() loadingEffectView?.removeFromSuperview()

View File

@ -34,11 +34,6 @@ final class StoryItemImageView: UIView {
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.contentView) self.addSubview(self.contentView)
#if DEBUG
if "".isEmpty {
self.contentView.isHidden = true
}
#endif
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -292,11 +287,10 @@ final class CaptureProtectedInfoComponent: Component {
environment: {}, environment: {},
containerSize: availableSize containerSize: availableSize
) )
//TODO:localize
let titleSize = self.title.update( let titleSize = self.title.update(
transition: transition, transition: transition,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Screenshot Blocked", font: Font.semibold(20.0), textColor: .white)), text: .plain(NSAttributedString(string: component.strings.Story_ScreenshotBlockedTitle, font: Font.semibold(20.0), textColor: .white)),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)), )),
@ -306,7 +300,7 @@ final class CaptureProtectedInfoComponent: Component {
let textSize = self.text.update( let textSize = self.text.update(
transition: transition, transition: transition,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "The story you tried to take a\nscreenshot of is protected from\ncopying by its creator.", font: Font.regular(17.0), textColor: UIColor(white: 1.0, alpha: 0.6))), text: .plain(NSAttributedString(string: component.strings.Story_ScreenshotBlockedText, font: Font.regular(17.0), textColor: UIColor(white: 1.0, alpha: 0.6))),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)), )),

View File

@ -1074,11 +1074,18 @@ public final class StoryItemSetContainerComponent: Component {
} }
if visibleItem.currentProgress != progress || visibleItem.isBuffering != isBuffering || canSwitch { if visibleItem.currentProgress != progress || visibleItem.isBuffering != isBuffering || canSwitch {
visibleItem.currentProgress = progress visibleItem.currentProgress = progress
let isBufferingUpdated = visibleItem.isBuffering != isBuffering
visibleItem.isBuffering = isBuffering visibleItem.isBuffering = isBuffering
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View { if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
navigationStripView.updateCurrentItemProgress(value: progress, isBuffering: isBuffering, transition: .immediate) navigationStripView.updateCurrentItemProgress(value: progress, isBuffering: isBuffering, transition: .immediate)
} }
if isBufferingUpdated {
self.state?.updated(transition: .immediate)
}
if progress >= 1.0 && canSwitch && !visibleItem.requestedNext { if progress >= 1.0 && canSwitch && !visibleItem.requestedNext {
visibleItem.requestedNext = true visibleItem.requestedNext = true
@ -1102,7 +1109,8 @@ public final class StoryItemSetContainerComponent: Component {
strings: component.strings, strings: component.strings,
peer: component.slice.peer, peer: component.slice.peer,
item: item.storyItem, item: item.storyItem,
audioMode: component.audioMode audioMode: component.audioMode,
isVideoBuffering: visibleItem.isBuffering
)), )),
environment: { environment: {
itemEnvironment itemEnvironment
@ -1761,10 +1769,10 @@ public final class StoryItemSetContainerComponent: Component {
var isUnsupported = false var isUnsupported = false
var disabledPlaceholder: String? var disabledPlaceholder: String?
if component.slice.peer.isService { if component.slice.peer.isService {
disabledPlaceholder = "You can't reply to this story" disabledPlaceholder = component.strings.Story_FooterReplyUnavailable
} else if case .unsupported = component.slice.item.storyItem.media { } else if case .unsupported = component.slice.item.storyItem.media {
isUnsupported = true isUnsupported = true
disabledPlaceholder = "You can't reply to this story" disabledPlaceholder = component.strings.Story_FooterReplyUnavailable
} }
var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false) var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
@ -1779,7 +1787,7 @@ public final class StoryItemSetContainerComponent: Component {
theme: component.theme, theme: component.theme,
strings: component.strings, strings: component.strings,
style: .story, style: .story,
placeholder: "Reply Privately...", placeholder: component.strings.Story_InputPlaceholderReplyPrivately,
maxLength: 4096, maxLength: 4096,
queryTypes: [.mention, .emoji], queryTypes: [.mention, .emoji],
alwaysDarkWhenHasText: component.metrics.widthClass == .regular, alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
@ -2120,7 +2128,7 @@ public final class StoryItemSetContainerComponent: Component {
actionSheet.setItemGroups([ actionSheet.setItemGroups([
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Delete Story", color: .destructive, action: { [weak self, weak actionSheet] in ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
guard let self, let component = self.component else { guard let self, let component = self.component else {
@ -2463,9 +2471,9 @@ public final class StoryItemSetContainerComponent: Component {
} }
let tooltipText: String let tooltipText: String
if component.slice.peer.id == component.context.account.peerId { if component.slice.peer.id == component.context.account.peerId {
tooltipText = "Only people from your close friends list will see this story." tooltipText = component.strings.Story_TooltipPrivacyCloseFriendsMy
} else { } else {
tooltipText = "You are seeing this story because you have\nbeen added to \(component.slice.peer.compactDisplayTitle)'s list of close friends." tooltipText = component.strings.Story_TooltipPrivacyCloseFriends(component.slice.peer.compactDisplayTitle).string
} }
let tooltipScreen = TooltipScreen( let tooltipScreen = TooltipScreen(
account: component.context.account, account: component.context.account,
@ -2541,6 +2549,7 @@ public final class StoryItemSetContainerComponent: Component {
let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent( let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(
context: component.context, context: component.context,
strings: component.strings,
peer: component.slice.peer, peer: component.slice.peer,
timestamp: component.slice.item.storyItem.timestamp, timestamp: component.slice.item.storyItem.timestamp,
counters: counters, counters: counters,
@ -2679,6 +2688,7 @@ public final class StoryItemSetContainerComponent: Component {
component: AnyComponent(StoryContentCaptionComponent( component: AnyComponent(StoryContentCaptionComponent(
externalState: captionItem.externalState, externalState: captionItem.externalState,
context: component.context, context: component.context,
strings: component.strings,
text: component.slice.item.storyItem.text, text: component.slice.item.storyItem.text,
entities: component.slice.item.storyItem.entities, entities: component.slice.item.storyItem.entities,
entityFiles: component.slice.item.entityFiles, entityFiles: component.slice.item.entityFiles,
@ -2923,10 +2933,10 @@ public final class StoryItemSetContainerComponent: Component {
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message]) let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message])
|> deliverOnMainQueue).start(next: { [weak self] messageIds in |> deliverOnMainQueue).start(next: { [weak self] messageIds in
if let animation, let self { if let animation, let self, let component = self.component {
let controller = UndoOverlayController( let controller = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .sticker(context: context, file: animation, loop: false, title: nil, text: "Reaction Sent.", undoText: "View in Chat", customAction: { [weak self] in content: .sticker(context: context, file: animation, loop: false, title: nil, text: component.strings.Story_ToastReactionSent, undoText: component.strings.Story_ToastViewInChat, customAction: { [weak self] in
if let messageId = messageIds.first, let self { if let messageId = messageIds.first, let self {
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) }) self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
} }
@ -3153,17 +3163,17 @@ public final class StoryItemSetContainerComponent: Component {
let text: String let text: String
if privacy.base == .contacts { if privacy.base == .contacts {
text = "This story is shown to all your contacts." text = component.strings.Story_PrivacyTooltipContacts
} else if privacy.base == .closeFriends { } else if privacy.base == .closeFriends {
text = "This story is shown to your close friends." text = component.strings.Story_PrivacyTooltipCloseFriends
} else if privacy.base == .nobody { } else if privacy.base == .nobody {
if !privacy.additionallyIncludePeers.isEmpty { if !privacy.additionallyIncludePeers.isEmpty {
text = "This story is shown to selected contacts." text = component.strings.Story_PrivacyTooltipSelectedContacts
} else { } else {
text = "This story is shown only to you." text = component.strings.Story_PrivacyTooltipNobody
} }
} else { } else {
text = "This story is shown to everyone." text = component.strings.Story_PrivacyTooltipEveryone
} }
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
@ -3560,20 +3570,23 @@ public final class StoryItemSetContainerComponent: Component {
return return
} }
let saveScreen = SaveProgressScreen(context: component.context, content: .progress("Saving", 0.0)) let saveScreen = SaveProgressScreen(context: component.context, content: .progress(component.strings.Story_TooltipSaving, 0.0))
component.controller()?.present(saveScreen, in: .current) component.controller()?.present(saveScreen, in: .current)
let stringSaving = component.strings.Story_TooltipSaving
let stringSaved = component.strings.Story_TooltipSaved
let disposable = (saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: component.slice.item.storyItem.media._asMedia())) let disposable = (saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: component.slice.item.storyItem.media._asMedia()))
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in |> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
guard let saveScreen else { guard let saveScreen else {
return return
} }
saveScreen.content = .progress("Saving", progress) saveScreen.content = .progress(stringSaving, progress)
}, completed: { [weak saveScreen] in }, completed: { [weak saveScreen] in
guard let saveScreen else { guard let saveScreen else {
return return
} }
saveScreen.content = .completion("Saved") saveScreen.content = .completion(stringSaved)
Queue.mainQueue().after(3.0, { [weak saveScreen] in Queue.mainQueue().after(3.0, { [weak saveScreen] in
saveScreen?.dismiss() saveScreen?.dismiss()
}) })
@ -3624,28 +3637,24 @@ public final class StoryItemSetContainerComponent: Component {
let privacyText: String let privacyText: String
switch component.slice.item.storyItem.privacy?.base { switch component.slice.item.storyItem.privacy?.base {
case .closeFriends: case .closeFriends:
privacyText = "Close Friends" privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends
case .contacts: case .contacts:
if additionalCount != 0 { if additionalCount != 0 {
privacyText = "Contacts (-\(additionalCount))" privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string
} else { } else {
privacyText = "Contacts" privacyText = component.strings.Story_ContextPrivacy_LabelContacts
} }
case .nobody: case .nobody:
if additionalCount != 0 { if additionalCount != 0 {
if additionalCount == 1 { privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount))
privacyText = "\(additionalCount) Person"
} else { } else {
privacyText = "\(additionalCount) People" privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe
}
} else {
privacyText = "Only Me"
} }
default: default:
privacyText = "Everyone" privacyText = component.strings.Story_ContextPrivacy_LabelEveryone
} }
items.append(.action(ContextMenuActionItem(text: "Who can see", textLayout: .secondLineWithValue(privacyText), icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Privacy, textLayout: .secondLineWithValue(privacyText), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3656,7 +3665,7 @@ public final class StoryItemSetContainerComponent: Component {
self.openItemPrivacySettings() self.openItemPrivacySettings()
}))) })))
items.append(.action(ContextMenuActionItem(text: "Edit Story", icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3669,7 +3678,7 @@ public final class StoryItemSetContainerComponent: Component {
items.append(.separator) items.append(.separator)
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? "Remove from Profile" : "Save to Profile", icon: { theme in items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromProfile : component.strings.Story_Context_SaveToProfile, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Check" : "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Check" : "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3684,7 +3693,7 @@ public final class StoryItemSetContainerComponent: Component {
if component.slice.item.storyItem.isPinned { if component.slice.item.storyItem.isPinned {
self.component?.presentController(UndoOverlayController( self.component?.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .info(title: nil, text: "Story removed from your profile", timeout: nil), content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -3692,7 +3701,7 @@ public final class StoryItemSetContainerComponent: Component {
} else { } else {
self.component?.presentController(UndoOverlayController( self.component?.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .info(title: "Story saved to your profile", text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil), content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -3700,12 +3709,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
}))) })))
let saveText: String let saveText: String = component.strings.Story_Context_SaveToGallery
if case .file = component.slice.item.storyItem.media {
saveText = "Save Video"
} else {
saveText = "Save Image"
}
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
@ -3718,7 +3722,7 @@ public final class StoryItemSetContainerComponent: Component {
}))) })))
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) { if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) {
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3737,7 +3741,7 @@ public final class StoryItemSetContainerComponent: Component {
component.presentController(UndoOverlayController( component.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .linkCopied(text: "Link copied."), content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -3745,7 +3749,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
}) })
}))) })))
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3782,7 +3786,8 @@ public final class StoryItemSetContainerComponent: Component {
tipSignal = packsPromise.get() tipSignal = packsPromise.get()
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in |> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
if packReferences.count > 1 { if packReferences.count > 1 {
return .single(.animatedEmoji(text: "This story contains stickers from [\(packReferences.count) packs]().", arguments: nil, file: nil, action: action)) let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
} else if let reference = packReferences.first { } else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in |> filter { result in
@ -3796,7 +3801,7 @@ public final class StoryItemSetContainerComponent: Component {
if case let .result(info, items, _) = result { if case let .result(info, items, _) = result {
let isEmoji = info.flags.contains(.isEmoji) let isEmoji = info.flags.contains(.isEmoji)
let tip: ContextController.Tip = .animatedEmoji( let tip: ContextController.Tip = .animatedEmoji(
text: isEmoji ? "This story contains\n#[\(info.title)]() emoji." : "This story contains\n#[\(info.title)]() stickers.", text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
arguments: TextNodeWithEntities.Arguments( arguments: TextNodeWithEntities.Arguments(
context: context, context: context,
cache: context.animationCache, cache: context.animationCache,
@ -3854,7 +3859,7 @@ public final class StoryItemSetContainerComponent: Component {
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers) let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers)
if !component.slice.peer.isService { if !component.slice.peer.isService {
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify About Stories" : "Do Not Notify About Stories", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? component.strings.StoryFeed_ContextNotifyOn : component.strings.StoryFeed_ContextNotifyOff, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3876,7 +3881,7 @@ public final class StoryItemSetContainerComponent: Component {
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will now get a notification whenever **\(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil), ], title: nil, text: component.strings.StoryFeed_TooltipNotifyOn(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -3890,7 +3895,7 @@ public final class StoryItemSetContainerComponent: Component {
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will no longer receive a notification when **\(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil), ], title: nil, text: component.strings.StoryFeed_TooltipNotifyOff(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -3903,7 +3908,7 @@ public final class StoryItemSetContainerComponent: Component {
isHidden = storiesHidden isHidden = storiesHidden
} }
items.append(.action(ContextMenuActionItem(text: isHidden ? "Unhide Stories" : "Hide Stories", icon: { theme in items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3914,7 +3919,7 @@ public final class StoryItemSetContainerComponent: Component {
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden) let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden)
let text = !isHidden ? "Stories from **\(component.slice.peer.compactDisplayTitle)** will now be shown in Archived Chats." : "Stories from **\(component.slice.peer.compactDisplayTitle)** will now be shown in Chats." let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string
let tooltipScreen = TooltipScreen( let tooltipScreen = TooltipScreen(
context: component.context, context: component.context,
account: component.context.account, account: component.context.account,
@ -3923,7 +3928,7 @@ public final class StoryItemSetContainerComponent: Component {
style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0), style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0),
icon: .peer(peer: component.slice.peer, isStory: true), icon: .peer(peer: component.slice.peer, isStory: true),
action: TooltipScreen.Action( action: TooltipScreen.Action(
title: "Undo", title: component.strings.Undo_Undo,
action: { action: {
component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden) component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden)
} }
@ -3946,7 +3951,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
if !component.slice.item.storyItem.isForwardingDisabled { if !component.slice.item.storyItem.isForwardingDisabled {
let saveText: String = "Save to Gallery" let saveText: String = component.strings.Story_Context_SaveToGallery
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
@ -3960,7 +3965,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) { if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) {
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.default) a(.default)
@ -3979,7 +3984,7 @@ public final class StoryItemSetContainerComponent: Component {
component.presentController(UndoOverlayController( component.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .linkCopied(text: "Link copied."), content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -3987,20 +3992,10 @@ public final class StoryItemSetContainerComponent: Component {
} }
}) })
}))) })))
/*items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
self.sendMessageContext.performShareAction(view: self)
})))*/
} }
if !component.slice.peer.isService { if !component.slice.peer.isService {
items.append(.action(ContextMenuActionItem(text: "Report", icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Report, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, a in }, action: { [weak self] c, a in
guard let self, let component = self.component, let controller = component.controller() else { guard let self, let component = self.component, let controller = component.controller() else {
@ -4060,7 +4055,8 @@ public final class StoryItemSetContainerComponent: Component {
tipSignal = packsPromise.get() tipSignal = packsPromise.get()
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in |> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
if packReferences.count > 1 { if packReferences.count > 1 {
return .single(.animatedEmoji(text: "This story contains stickers from [\(packReferences.count) packs]().", arguments: nil, file: nil, action: action)) let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
} else if let reference = packReferences.first { } else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in |> filter { result in
@ -4074,7 +4070,7 @@ public final class StoryItemSetContainerComponent: Component {
if case let .result(info, items, _) = result { if case let .result(info, items, _) = result {
let isEmoji = info.flags.contains(.isEmoji) let isEmoji = info.flags.contains(.isEmoji)
let tip: ContextController.Tip = .animatedEmoji( let tip: ContextController.Tip = .animatedEmoji(
text: isEmoji ? "This story contains\n#[\(info.title)]() emoji." : "This story contains\n#[\(info.title)]() stickers.", text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
arguments: TextNodeWithEntities.Arguments( arguments: TextNodeWithEntities.Arguments(
context: context, context: context,
cache: context.animationCache, cache: context.animationCache,
@ -4121,7 +4117,7 @@ public final class StoryItemSetContainerComponent: Component {
let tooltipScreen = TooltipScreen( let tooltipScreen = TooltipScreen(
account: component.context.account, account: component.context.account,
sharedContext: component.context.sharedContext, sharedContext: component.context.sharedContext,
text: .plain(text: "This video has no sound"), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: nil).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in text: .plain(text: component.strings.Story_TooltipVideoHasNoSound), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: nil).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
return .dismiss(consume: true) return .dismiss(consume: true)
} }
) )

View File

@ -344,11 +344,11 @@ final class StoryItemSetContainerSendMessage {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let text = isScheduled ? "Message Scheduled" : "Message Sent" let text = isScheduled ? presentationData.strings.Story_TooltipMessageScheduled : presentationData.strings.Story_TooltipMessageSent
let tooltipScreen = UndoOverlayController( let tooltipScreen = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .actionSucceeded(title: "", text: text, cancel: messageId != nil ? "View in Chat" : "", destructive: false), content: .actionSucceeded(title: "", text: text, cancel: messageId != nil ? presentationData.strings.Story_ToastViewInChat : "", destructive: false),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { [weak view, weak self] action in action: { [weak view, weak self] action in
@ -881,7 +881,7 @@ final class StoryItemSetContainerSendMessage {
actionSheet.setItemGroups([ actionSheet.setItemGroups([
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Copy Link", color: .accent, action: { [weak self, weak view, weak actionSheet] in ActionSheetButtonItem(title: presentationData.strings.Story_Context_CopyLink, color: .accent, action: { [weak self, weak view, weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
guard let self, let view else { guard let self, let view else {
@ -911,7 +911,7 @@ final class StoryItemSetContainerSendMessage {
} else { } else {
var preferredAction: ShareControllerPreferredAction? var preferredAction: ShareControllerPreferredAction?
if focusedItem.storyItem.isPublic && !component.slice.peer.isService { if focusedItem.storyItem.isPublic && !component.slice.peer.isService {
preferredAction = .custom(action: ShareControllerAction(title: "Copy Link", action: { preferredAction = .custom(action: ShareControllerAction(title: component.strings.Story_Context_CopyLink, action: {
let _ = ((component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id)) let _ = ((component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id))
|> deliverOnMainQueue).start(next: { link in |> deliverOnMainQueue).start(next: { link in
if let link { if let link {
@ -920,7 +920,7 @@ final class StoryItemSetContainerSendMessage {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
component.presentController(UndoOverlayController( component.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .linkCopied(text: "Link copied."), content: .linkCopied(text: presentationData.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
@ -1025,7 +1025,7 @@ final class StoryItemSetContainerSendMessage {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
component.presentController(UndoOverlayController( component.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .linkCopied(text: "Link copied."), content: .linkCopied(text: presentationData.strings.Story_ToastLinkCopied),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }

View File

@ -685,6 +685,7 @@ final class StoryItemSetViewListComponent: Component {
transition: transition, transition: transition,
component: AnyComponent(StoryFooterPanelComponent( component: AnyComponent(StoryFooterPanelComponent(
context: component.context, context: component.context,
strings: component.strings,
storyItem: component.storyItem, storyItem: component.storyItem,
externalViews: externalViews, externalViews: externalViews,
expandFraction: dismissFraction, expandFraction: dismissFraction,

View File

@ -10,9 +10,11 @@ import TelegramCore
import MoreHeaderButton import MoreHeaderButton
import SemanticStatusNode import SemanticStatusNode
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData
public final class StoryFooterPanelComponent: Component { public final class StoryFooterPanelComponent: Component {
public let context: AccountContext public let context: AccountContext
public let strings: PresentationStrings
public let storyItem: EngineStoryItem? public let storyItem: EngineStoryItem?
public let externalViews: EngineStoryItem.Views? public let externalViews: EngineStoryItem.Views?
public let expandFraction: CGFloat public let expandFraction: CGFloat
@ -22,6 +24,7 @@ public final class StoryFooterPanelComponent: Component {
public init( public init(
context: AccountContext, context: AccountContext,
strings: PresentationStrings,
storyItem: EngineStoryItem?, storyItem: EngineStoryItem?,
externalViews: EngineStoryItem.Views?, externalViews: EngineStoryItem.Views?,
expandFraction: CGFloat, expandFraction: CGFloat,
@ -30,6 +33,7 @@ public final class StoryFooterPanelComponent: Component {
moreAction: @escaping (UIView, ContextGesture?) -> Void moreAction: @escaping (UIView, ContextGesture?) -> Void
) { ) {
self.context = context self.context = context
self.strings = strings
self.storyItem = storyItem self.storyItem = storyItem
self.externalViews = externalViews self.externalViews = externalViews
self.expandViewStats = expandViewStats self.expandViewStats = expandViewStats
@ -42,6 +46,9 @@ public final class StoryFooterPanelComponent: Component {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.strings !== rhs.strings {
return false
}
if lhs.storyItem != rhs.storyItem { if lhs.storyItem != rhs.storyItem {
return false return false
} }
@ -200,10 +207,9 @@ public final class StoryFooterPanelComponent: Component {
statusNode.transitionToState(.progress(value: CGFloat(max(0.08, self.uploadProgress)), cancelEnabled: true, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 0.0, lineWidth: 2.0))) statusNode.transitionToState(.progress(value: CGFloat(max(0.08, self.uploadProgress)), cancelEnabled: true, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 0.0, lineWidth: 2.0)))
//TODO:localize
let uploadingTextSize = uploadingText.update( let uploadingTextSize = uploadingText.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(Text(text: "Uploading...", font: Font.regular(15.0), color: .white)), component: AnyComponent(Text(text: component.strings.Story_Footer_Uploading, font: Font.regular(15.0), color: .white)),
environment: {}, environment: {},
containerSize: CGSize(width: 200.0, height: 100.0) containerSize: CGSize(width: 200.0, height: 100.0)
) )
@ -260,11 +266,9 @@ public final class StoryFooterPanelComponent: Component {
let viewsText: String let viewsText: String
if viewCount == 0 { if viewCount == 0 {
viewsText = "No Views" viewsText = component.strings.Story_Footer_NoViews
} else if viewCount == 1 {
viewsText = "1 view"
} else { } else {
viewsText = "\(viewCount) views" viewsText = component.strings.Story_Footer_Views(Int32(viewCount))
} }
self.viewStatsButton.isEnabled = viewCount != 0 self.viewStatsButton.isEnabled = viewCount != 0

View File

@ -778,13 +778,12 @@ public final class StoryPeerListItemComponent: Component {
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction)) Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction))
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction)) Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction))
//TODO:localize
let titleString: String let titleString: String
if component.peer.id == component.context.account.peerId { if component.peer.id == component.context.account.peerId {
if let ringAnimation = component.ringAnimation, case .progress = ringAnimation { if let ringAnimation = component.ringAnimation, case .progress = ringAnimation {
titleString = "Uploading..." titleString = component.strings.StoryFeed_MyUploading
} else { } else {
titleString = "My story" titleString = component.strings.StoryFeed_MyStory
} }
} else { } else {
titleString = component.peer.compactDisplayTitle.trimmingCharacters(in: .whitespacesAndNewlines) titleString = component.peer.compactDisplayTitle.trimmingCharacters(in: .whitespacesAndNewlines)

View File

@ -89,6 +89,7 @@ private final class ShapeImageView: UIView {
public final class StorySetIndicatorComponent: Component { public final class StorySetIndicatorComponent: Component {
public let context: AccountContext public let context: AccountContext
public let strings: PresentationStrings
public let peer: EnginePeer public let peer: EnginePeer
public let items: [EngineStoryItem] public let items: [EngineStoryItem]
public let hasUnseen: Bool public let hasUnseen: Bool
@ -99,6 +100,7 @@ public final class StorySetIndicatorComponent: Component {
public init( public init(
context: AccountContext, context: AccountContext,
strings: PresentationStrings,
peer: EnginePeer, peer: EnginePeer,
items: [EngineStoryItem], items: [EngineStoryItem],
hasUnseen: Bool, hasUnseen: Bool,
@ -108,6 +110,7 @@ public final class StorySetIndicatorComponent: Component {
action: @escaping () -> Void action: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.strings = strings
self.peer = peer self.peer = peer
self.items = items self.items = items
self.hasUnseen = hasUnseen self.hasUnseen = hasUnseen
@ -118,6 +121,9 @@ public final class StorySetIndicatorComponent: Component {
} }
public static func ==(lhs: StorySetIndicatorComponent, rhs: StorySetIndicatorComponent) -> Bool { public static func ==(lhs: StorySetIndicatorComponent, rhs: StorySetIndicatorComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.items != rhs.items { if lhs.items != rhs.items {
return false return false
} }
@ -378,14 +384,11 @@ public final class StorySetIndicatorComponent: Component {
self.imageView.setNeedsDisplay() self.imageView.setNeedsDisplay()
} }
//TODO:localize
let textValue: String let textValue: String
if component.totalCount == 0 { if component.totalCount == 0 {
textValue = "" textValue = ""
} else if component.totalCount == 1 {
textValue = "1 story"
} else { } else {
textValue = "\(component.totalCount) stories" textValue = component.strings.Story_Footer_Views(Int32(component.totalCount))
} }
let textSize = self.text.update( let textSize = self.text.update(
transition: .immediate, transition: .immediate,

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "smoothGradient 0.4.png", "filename" : "smoothGradient 0.6.png",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -4524,12 +4524,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
if let story = message.associatedStories[storyId], story.data.isEmpty { if let story = message.associatedStories[storyId], story.data.isEmpty {
//TODO:localize self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: self.presentationData.strings.Story_TooltipExpired, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: "This story is no longer available", timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
return return
} }
let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: false) let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: true)
let _ = (storyContent.state let _ = (storyContent.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in |> deliverOnMainQueue).start(next: { [weak self] _ in

View File

@ -137,15 +137,14 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
} else { } else {
titleString = arguments.strings.User_DeletedAccount titleString = arguments.strings.User_DeletedAccount
} }
//TODO:localize
isText = false isText = false
if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty { if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty {
isExpiredStory = true isExpiredStory = true
textString = NSAttributedString(string: "Expired story") textString = NSAttributedString(string: arguments.strings.Chat_ReplyExpiredStory)
isMedia = false isMedia = false
} else { } else {
isStory = true isStory = true
textString = NSAttributedString(string: "Story") textString = NSAttributedString(string: arguments.strings.Chat_ReplyStory)
isMedia = true isMedia = true
} }
} else { } else {

View File

@ -194,9 +194,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
//TODO:localize let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_StoryMentionAction, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "View Story", font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 186.0) let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 186.0)

View File

@ -37,7 +37,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
if let story { if let story {
let navigationController = params.navigationController let navigationController = params.navigationController
let context = params.context let context = params.context
let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: story.storyId, readGlobally: story.isMention) let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: story.storyId, readGlobally: true)
let _ = (storyContent.state let _ = (storyContent.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in |> deliverOnMainQueue).start(next: { [weak navigationController] _ in

View File

@ -814,7 +814,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
} }
|> deliverOnMainQueue).start(next: { exists in |> deliverOnMainQueue).start(next: { exists in
if exists { if exists {
let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id), readGlobally: false) let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id), readGlobally: true)
let _ = (storyContent.state let _ = (storyContent.state
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in |> deliverOnMainQueue).start(next: { [weak navigationController] _ in
@ -833,12 +833,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
navigationController?.pushViewController(storyContainerScreen) navigationController?.pushViewController(storyContainerScreen)
}) })
} else { } else {
//TODO:localize
var elevatedLayout = true var elevatedLayout = true
if case .chat = urlContext { if case .chat = urlContext {
elevatedLayout = false elevatedLayout = false
} }
present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "story_expired", scale: 0.066, colors: [:], title: nil, text: "This story does not exist", customUndoText: nil, timeout: nil), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "story_expired", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.Story_TooltipExpired, customUndoText: nil, timeout: nil), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in
return true return true
}), nil) }), nil)
} }

View File

@ -1050,7 +1050,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
markupNode = current markupNode = current
} else { } else {
markupNode = AvatarVideoNode(context: self.context) markupNode = AvatarVideoNode(context: self.context)
self.insertSubnode(markupNode, aboveSubnode: self.avatarNode) self.avatarNode.contentNode.addSubnode(markupNode)
self.markupNode = markupNode self.markupNode = markupNode
} }
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0)) markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
@ -1083,7 +1083,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
shape.path = maskPath.cgPath shape.path = maskPath.cgPath
videoNode.layer.mask = shape videoNode.layer.mask = shape
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode) self.avatarNode.contentNode.addSubnode(videoNode)
} }
} else { } else {
if let markupNode = self.markupNode { if let markupNode = self.markupNode {
@ -1107,15 +1107,15 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
} }
if let markupNode = self.markupNode { if let markupNode = self.markupNode {
markupNode.frame = self.avatarNode.frame markupNode.frame = self.avatarNode.bounds
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate) markupNode.updateLayout(size: self.avatarNode.bounds.size, cornerRadius: avatarCornerRadius, transition: .immediate)
} }
if let videoNode = self.videoNode { if let videoNode = self.videoNode {
if self.canAttachVideo { if self.canAttachVideo {
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate) videoNode.updateLayout(size: self.avatarNode.bounds.size, transition: .immediate)
} }
videoNode.frame = self.avatarNode.contentNode.bounds videoNode.frame = self.avatarNode.bounds
if isEditing != videoNode.canAttachContent { if isEditing != videoNode.canAttachContent {
videoNode.canAttachContent = isEditing && self.canAttachVideo videoNode.canAttachContent = isEditing && self.canAttachVideo

View File

@ -956,8 +956,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
let title: String let title: String
switch key { switch key {
case .stories: case .stories:
//TODO:localize title = presentationData.strings.PeerInfo_PaneStories
title = "Stories"
case .media: case .media:
title = presentationData.strings.PeerInfo_PaneMedia title = presentationData.strings.PeerInfo_PaneMedia
case .files: case .files:

View File

@ -792,8 +792,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
} }
} }
//TODO:localize items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyStories, icon: PresentationResourcesSettings.stories, action: {
items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "My Stories", icon: PresentationResourcesSettings.stories, action: {
interaction.openSettings(.stories) interaction.openSettings(.stories)
})) }))

View File

@ -141,8 +141,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
} else if content.type == "video" { } else if content.type == "video" {
text = stringForMediaKind(.video, strings: self.strings).0.string text = stringForMediaKind(.video, strings: self.strings).0.string
} else if content.type == "telegram_story" { } else if content.type == "telegram_story" {
//TODO:localize text = stringForMediaKind(.story, strings: self.strings).0.string
text = "Story"
} else if let _ = content.image { } else if let _ = content.image {
text = stringForMediaKind(.image, strings: self.strings).0.string text = stringForMediaKind(.image, strings: self.strings).0.string
} }