mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
271238fd21
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-12
|
||||
runs-on: macos-13
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
@ -9354,6 +9354,13 @@ Sorry for the inconvenience.";
|
||||
"ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off";
|
||||
"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.GetCodeViaFragment" = "Get a code via Fragment";
|
||||
@ -9380,3 +9387,279 @@ Sorry for the inconvenience.";
|
||||
"Conversation.StoryMentionTextIncoming" = "%@ mentioned you\nin a story";
|
||||
"Conversation.StoryExpiredMentionTextOutgoing" = "The story where you mentioned %@\n is 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";
|
||||
|
||||
|
@ -1991,7 +1991,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.displayedStoriesTooltip = true
|
||||
|
||||
let absoluteFrame = anchorView.convert(anchorRect, to: self.view)
|
||||
//TODO:localize
|
||||
|
||||
let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle)
|
||||
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))
|
||||
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||
@ -2584,13 +2583,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let text: String
|
||||
if premiumNeeded {
|
||||
text = "Posting stories is currently available only\nto subscribers of [Telegram Premium]()."
|
||||
text = self.presentationData.strings.StoryFeed_TooltipPremiumPosting
|
||||
} 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 {
|
||||
text = "You can't post stories during a call."
|
||||
text = self.presentationData.strings.StoryFeed_TooltipPostingDuringCall
|
||||
} else if hasActiveGroupCall {
|
||||
text = "You can't post stories during a voice chat."
|
||||
text = self.presentationData.strings.StoryFeed_TooltipPostingDuringGroupCall
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
@ -2730,9 +2730,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
//TODO:localize
|
||||
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)
|
||||
}, action: { [weak self] c, _ in
|
||||
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)
|
||||
}, action: { [weak self] c, _ in
|
||||
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)
|
||||
}, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: {
|
||||
@ -2768,7 +2767,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
})
|
||||
})))
|
||||
} 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)
|
||||
}, action: { [weak self] c, _ in
|
||||
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)
|
||||
}, action: { [weak self] c, _ in
|
||||
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)
|
||||
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)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
@ -2825,7 +2824,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -2839,7 +2838,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -2849,9 +2848,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let hideText: String
|
||||
if self.location == .chatList(groupId: .archive) {
|
||||
hideText = "Unhide Stories"
|
||||
hideText = self.presentationData.strings.StoryFeed_ContextUnarchive
|
||||
} 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"
|
||||
items.append(.action(ContextMenuActionItem(text: hideText, icon: { theme in
|
||||
@ -2871,9 +2870,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
undoValue = false
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
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 let self {
|
||||
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue)
|
||||
@ -2882,30 +2880,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return false
|
||||
}), 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] = []
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Archive Settings", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Archive_ContextSettings, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3341,7 +3314,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
})))
|
||||
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3359,7 +3332,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
@ -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 {
|
||||
//TODO:localize
|
||||
let storyText: String
|
||||
if storyState.stats.totalCount == 1 {
|
||||
storyText = "1 story"
|
||||
} else {
|
||||
storyText = "\(storyState.stats.totalCount) stories"
|
||||
}
|
||||
let storyText: String = item.presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyState.stats.totalCount))
|
||||
textString.append(NSAttributedString(string: storyText, font: textFont, textColor: theme.messageTextColor))
|
||||
}
|
||||
attributedText = textString
|
||||
|
@ -303,8 +303,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
messageText = strings.Conversation_StoryMentionTextOutgoing(peer.compactDisplayTitle).string
|
||||
}
|
||||
} else {
|
||||
//TODO:localize
|
||||
messageText = "Story"
|
||||
messageText = strings.Notification_Story
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -31,8 +31,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if isStories {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: strings.StoryFeed_ContextOpenProfile, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, _ in
|
||||
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)
|
||||
|
||||
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)
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
@ -69,7 +68,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -83,7 +82,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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,
|
||||
animateInAsReplacement: 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)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
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
|
||||
|
@ -207,20 +207,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
storyStats = (storyData.count, storyData.unseenCount, storyData.hasUnseenCloseFriends)
|
||||
|
||||
let text: String
|
||||
//TODO:localize
|
||||
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"
|
||||
}
|
||||
}
|
||||
text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count))
|
||||
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
|
||||
@ -404,8 +391,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
}
|
||||
|
||||
if addHeader {
|
||||
//TODO:localize
|
||||
commonHeader = ChatListSearchItemHeader(type: .text("SORTED BY LAST SEEN TIME", AnyHashable(1)), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||
commonHeader = ChatListSearchItemHeader(type: .text(strings.Contacts_SortedByPresence.uppercased(), AnyHashable(1)), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
|
||||
switch presentation {
|
||||
@ -529,15 +515,15 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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)))
|
||||
index += 1
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
var index: Int = 0
|
||||
|
@ -300,12 +300,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
let tabsNode: ASDisplayNode? = nil
|
||||
let tabsNodeIsSearch = false
|
||||
|
||||
//TODO:localize
|
||||
let primaryContent = ChatListHeaderComponent.Content(
|
||||
title: "Contacts",
|
||||
title: self.presentationData.strings.Contacts_Title,
|
||||
navigationBackTitle: 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(
|
||||
content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false),
|
||||
pressed: { [weak self] sourceView in
|
||||
|
@ -192,7 +192,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
} else if let (fetchResult, index) = self.currentAssetState {
|
||||
let asset = fetchResult.object(at: index)
|
||||
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
|
||||
return tag
|
||||
} else {
|
||||
|
@ -95,8 +95,8 @@ struct Month: Equatable {
|
||||
var timeinfo: tm = tm()
|
||||
gmtime_r(&time, &timeinfo)
|
||||
|
||||
let year = UInt32(timeinfo.tm_year)
|
||||
let month = UInt32(timeinfo.tm_mon)
|
||||
let year = UInt32(max(timeinfo.tm_year, 0))
|
||||
let month = UInt32(max(timeinfo.tm_mon, 0))
|
||||
|
||||
self.packedValue = Int32(bitPattern: year | (month << 16))
|
||||
}
|
||||
@ -1445,8 +1445,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
case .wallpaper:
|
||||
self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
|
||||
case .addImage:
|
||||
//TODO:localize
|
||||
self.titleView.title = "Add Image"
|
||||
self.titleView.title = presentationData.strings.MediaPicker_AddImage
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1265,6 +1265,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
transition: indicatorTransition,
|
||||
component: AnyComponent(StorySetIndicatorComponent(
|
||||
context: self.context,
|
||||
strings: self.context.sharedContext.currentPresentationData.with({ $0 }).strings,
|
||||
peer: storyParams.peer,
|
||||
items: storyParams.items,
|
||||
hasUnseen: storyParams.hasUnseen,
|
||||
|
@ -939,7 +939,6 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
)
|
||||
)
|
||||
)
|
||||
//TODO:localize
|
||||
availableItems[.stories] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.stories,
|
||||
@ -951,8 +950,8 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
videoFile: configuration.videos["voice_to_text"],
|
||||
decoration: .badgeStars
|
||||
)),
|
||||
title: "Story Posting",
|
||||
text: "Be one of the first to share your stories with your contacts or an unlimited audience.",
|
||||
title: strings.Premium_Stories,
|
||||
text: strings.Premium_StoriesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
@ -1049,8 +1048,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
case .translation:
|
||||
buttonText = strings.Premium_Translation_Proceed
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
buttonText = "Unlock Story Posting"
|
||||
buttonText = strings.Premium_Stories_Proceed
|
||||
buttonAnimationName = "premium_unlock"
|
||||
default:
|
||||
buttonText = strings.Common_OK
|
||||
|
@ -395,8 +395,7 @@ enum PremiumPerk: CaseIterable {
|
||||
case .translation:
|
||||
return strings.Premium_Translation
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
return "Story Posting"
|
||||
return strings.Premium_Stories
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,34 +85,33 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! ArchiveSettingsControllerArguments
|
||||
//TODO:localize
|
||||
switch self {
|
||||
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):
|
||||
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)
|
||||
})
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
})
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
}, activatedWhileDisabled: {
|
||||
arguments.updateUnknown(nil)
|
||||
})
|
||||
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 {
|
||||
let _ = context.engine.privacy.updateAccountAutoArchiveChats(value: value).start()
|
||||
} 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?()
|
||||
}))
|
||||
}
|
||||
@ -181,8 +181,7 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
|
||||
|
||||
//TODO:localize
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Archive Settings"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ArchiveSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archiveSettingsControllerEntries(
|
||||
presentationData: presentationData,
|
||||
settings: settings,
|
||||
|
@ -207,8 +207,7 @@ private func stringForAutomaticDownloadPeers(strings: PresentationStrings, decim
|
||||
if peers.contacts && peers.otherPrivate {
|
||||
return strings.AutoDownloadSettings_OnForAll
|
||||
} else if peers.contacts {
|
||||
//TODO:localize
|
||||
return "On for contacts"
|
||||
return strings.AutoDownloadSettings_OnForContacts
|
||||
} else {
|
||||
return strings.AutoDownloadSettings_OffForAll
|
||||
}
|
||||
|
@ -274,16 +274,14 @@ private func autodownloadMediaCategoryControllerEntries(presentationData: Presen
|
||||
downloadTitle = presentationData.strings.AutoDownloadSettings_AutodownloadFiles
|
||||
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
|
||||
case .story:
|
||||
//TODO:localize
|
||||
downloadTitle = "AUTO-DOWNLOAD STORIES"
|
||||
downloadTitle = presentationData.strings.AutoDownloadSettings_StoriesSectionHeader
|
||||
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
|
||||
}
|
||||
|
||||
if case .story = category {
|
||||
entries.append(.peerContacts(presentationData.theme, presentationData.strings.AutoDownloadSettings_Contacts, peers.contacts))
|
||||
//TODO:localize
|
||||
if peers.contacts {
|
||||
entries.append(.peerOtherPrivate(presentationData.theme, "Hidden Contacts", peers.otherPrivate))
|
||||
entries.append(.peerOtherPrivate(presentationData.theme, presentationData.strings.AutoDownloadSettings_StoriesArchivedContacts, peers.otherPrivate))
|
||||
}
|
||||
} else {
|
||||
entries.append(.peerHeader(presentationData.theme, downloadTitle))
|
||||
@ -463,15 +461,14 @@ func autodownloadMediaCategoryController(context: AccountContext, connectionType
|
||||
|
||||
let title: String
|
||||
switch category {
|
||||
case .photo:
|
||||
title = presentationData.strings.AutoDownloadSettings_PhotosTitle
|
||||
case .video:
|
||||
title = presentationData.strings.AutoDownloadSettings_VideosTitle
|
||||
case .file:
|
||||
title = presentationData.strings.AutoDownloadSettings_DocumentsTitle
|
||||
case .story:
|
||||
//TODO:localize
|
||||
title = "Stories"
|
||||
case .photo:
|
||||
title = presentationData.strings.AutoDownloadSettings_PhotosTitle
|
||||
case .video:
|
||||
title = presentationData.strings.AutoDownloadSettings_VideosTitle
|
||||
case .file:
|
||||
title = presentationData.strings.AutoDownloadSettings_DocumentsTitle
|
||||
case .story:
|
||||
title = presentationData.strings.AutoDownloadSettings_StoriesTitle
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
|
@ -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(.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
|
||||
switch globalSettings.privateChats.storySettings.mute {
|
||||
case .default:
|
||||
storiesValue = "Top 5"
|
||||
storiesValue = presentationData.strings.Notifications_TopChats
|
||||
case .muted:
|
||||
storiesValue = presentationData.strings.Notifications_Off
|
||||
case .unmuted:
|
||||
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(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
|
||||
|
@ -345,17 +345,16 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
importantEnabled = true
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
entries.append(.enable(presentationData.theme, "Show All Notifications", allEnabled))
|
||||
entries.append(.enable(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowAll, allEnabled))
|
||||
if !allEnabled {
|
||||
entries.append(.enableImportant(presentationData.theme, "Show Important Notifications", importantEnabled))
|
||||
entries.append(.importantInfo(presentationData.theme, "Always on for top 5 contacts."))
|
||||
entries.append(.enableImportant(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportant, importantEnabled))
|
||||
entries.append(.importantInfo(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportantFooter))
|
||||
}
|
||||
|
||||
if notificationSettings.enabled || !notificationExceptions.isEmpty {
|
||||
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)))
|
||||
}
|
||||
} else {
|
||||
@ -413,8 +412,7 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
var title: String = ""
|
||||
|
||||
if automaticSet.contains(value.peer.id) {
|
||||
//TODO:localize
|
||||
title = "\(presentationData.strings.Notification_Exceptions_AlwaysOn) (automatic)"
|
||||
title = presentationData.strings.NotificationSettings_Stories_AutomaticValue(presentationData.strings.Notification_Exceptions_AlwaysOn).string
|
||||
canRemove = false
|
||||
} else {
|
||||
if case .stories = category {
|
||||
@ -443,11 +441,10 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
if !title.isEmpty {
|
||||
title += ", "
|
||||
}
|
||||
//TODO:localize
|
||||
if case .show = value.settings.storySettings.hideSender {
|
||||
title += "Show Name"
|
||||
title += presentationData.strings.NotificationSettings_Stories_CompactShowName
|
||||
} else {
|
||||
title += "Hide Name"
|
||||
title += presentationData.strings.NotificationSettings_Stories_CompactHideName
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1068,8 +1065,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
||||
case .channel:
|
||||
title = presentationData.strings.Notifications_ChannelsTitle
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
title = "Stories"
|
||||
title = presentationData.strings.Notifications_StoriesTitle
|
||||
}
|
||||
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)
|
||||
|
@ -354,12 +354,11 @@ private func storiesSearchableItems(context: AccountContext) -> [SettingsSearcha
|
||||
|
||||
var result: [SettingsSearchableItem] = []
|
||||
|
||||
//TODO:localize
|
||||
result.append(SettingsSearchableItem(id: .stories(0), title: "My Stories", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
|
||||
result.append(SettingsSearchableItem(id: .stories(0), title: strings.Settings_MyStories, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
|
||||
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))
|
||||
}))
|
||||
|
||||
|
@ -396,8 +396,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
|
||||
case let .invoice(text):
|
||||
return (NSAttributedString(string: text), true)
|
||||
case .story:
|
||||
//TODO:localize
|
||||
return (NSAttributedString(string: "Story"), true)
|
||||
return (NSAttributedString(string: strings.Message_Story), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -934,13 +934,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
case .none:
|
||||
hintText = " "
|
||||
case .zoom:
|
||||
hintText = "Swipe up to zoom"
|
||||
hintText = environment.strings.Story_Camera_SwipeUpToZoom
|
||||
case .lock:
|
||||
hintText = "Swipe left to lock"
|
||||
hintText = environment.strings.Story_Camera_SwipeLeftToLock
|
||||
case .releaseLock:
|
||||
hintText = "Release to lock"
|
||||
hintText = environment.strings.Story_Camera_SwipeLeftRelease
|
||||
case .flip:
|
||||
hintText = "Swipe right to flip"
|
||||
hintText = environment.strings.Story_Camera_SwipeRightToFlip
|
||||
}
|
||||
if let hintText {
|
||||
let hintLabel = hintLabel.update(
|
||||
@ -967,6 +967,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
let modeControl = modeControl.update(
|
||||
component: ModeComponent(
|
||||
isTablet: isTablet,
|
||||
strings: environment.strings,
|
||||
availableModes: [.photo, .video],
|
||||
currentMode: component.cameraState.mode,
|
||||
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 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
|
||||
})
|
||||
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 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) {
|
||||
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
|
||||
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 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) {
|
||||
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
@ -3,14 +3,15 @@ import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
extension CameraMode {
|
||||
var title: String {
|
||||
func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .photo:
|
||||
return "Photo"
|
||||
return strings.Story_Camera_Photo
|
||||
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 {
|
||||
let isTablet: Bool
|
||||
let strings: PresentationStrings
|
||||
let availableModes: [CameraMode]
|
||||
let currentMode: CameraMode
|
||||
let updatedMode: (CameraMode) -> Void
|
||||
@ -26,12 +28,14 @@ final class ModeComponent: Component {
|
||||
|
||||
init(
|
||||
isTablet: Bool,
|
||||
strings: PresentationStrings,
|
||||
availableModes: [CameraMode],
|
||||
currentMode: CameraMode,
|
||||
updatedMode: @escaping (CameraMode) -> Void,
|
||||
tag: AnyObject?
|
||||
) {
|
||||
self.isTablet = isTablet
|
||||
self.strings = strings
|
||||
self.availableModes = availableModes
|
||||
self.currentMode = currentMode
|
||||
self.updatedMode = updatedMode
|
||||
@ -42,6 +46,9 @@ final class ModeComponent: Component {
|
||||
if lhs.isTablet != rhs.isTablet {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.availableModes != rhs.availableModes {
|
||||
return false
|
||||
}
|
||||
@ -145,7 +152,7 @@ final class ModeComponent: Component {
|
||||
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)
|
||||
|
||||
if isTablet {
|
||||
|
@ -55,9 +55,6 @@ final class PlaceholderComponent: Component {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
// if #available(iOS 13.0, *) {
|
||||
// self.layer.cornerCurve = .continuous
|
||||
// }
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -71,9 +68,10 @@ final class PlaceholderComponent: Component {
|
||||
let sideInset: CGFloat = 36.0
|
||||
let animationHeight: CGFloat = 120.0
|
||||
|
||||
let title: String = "Allow Telegram to access your camera and microphone"
|
||||
let text: String = "This lets you share photos and record videos."
|
||||
let buttonTitle: String = "Open Settings"
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let title = presentationData.strings.Story_Camera_AccessPlaceholderTitle
|
||||
let text = presentationData.strings.Story_Camera_AccessPlaceholderText
|
||||
let buttonTitle = presentationData.strings.Story_Camera_AccessOpenSettings
|
||||
|
||||
let animationSize = self.animation.update(
|
||||
transition: .immediate,
|
||||
|
@ -283,7 +283,6 @@ public final class ChatListNavigationBar: Component {
|
||||
placeholder = component.strings.Common_Search
|
||||
compactPlaceholder = component.strings.Common_Search
|
||||
|
||||
//TODO:localize
|
||||
searchContentNode = NavigationBarSearchContentNode(
|
||||
theme: component.theme,
|
||||
placeholder: placeholder,
|
||||
|
@ -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 func setupSource() {
|
||||
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 {
|
||||
return (self.player?.rate ?? 0.0) > 0.0
|
||||
}
|
||||
|
@ -87,6 +87,9 @@ final class MediaEditorRenderer: TextureConsumer {
|
||||
private var textureCache: CVMetalTextureCache?
|
||||
|
||||
private var currentTexture: MTLTexture?
|
||||
private var currentAdditionalTexture: MTLTexture?
|
||||
private var currentTime: CMTime = .zero
|
||||
|
||||
private var currentPixelBuffer: VideoPixelBuffer?
|
||||
private var currentAdditionalPixelBuffer: VideoPixelBuffer?
|
||||
|
||||
@ -174,8 +177,8 @@ final class MediaEditorRenderer: TextureConsumer {
|
||||
self.renderPasses.forEach { $0.setup(device: device, library: library) }
|
||||
}
|
||||
|
||||
public var displayEnabled = true
|
||||
var renderPassedEnabled = true
|
||||
|
||||
var needsDisplay = false
|
||||
|
||||
func renderFrame() {
|
||||
@ -200,7 +203,13 @@ final class MediaEditorRenderer: TextureConsumer {
|
||||
}
|
||||
|
||||
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
|
||||
} 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) {
|
||||
@ -237,7 +246,7 @@ final class MediaEditorRenderer: TextureConsumer {
|
||||
}
|
||||
commandBuffer.commit()
|
||||
|
||||
if let renderTarget = self.renderTarget {
|
||||
if let renderTarget = self.renderTarget, self.displayEnabled {
|
||||
if self.needsDisplay {
|
||||
self.didRenderFrame()
|
||||
} 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?
|
||||
func consumeVideoPixelBuffer(pixelBuffer: VideoPixelBuffer, additionalPixelBuffer: VideoPixelBuffer?, render: Bool) {
|
||||
self.willRenderFrame()
|
||||
|
@ -4,6 +4,7 @@ import Display
|
||||
import ComponentFlow
|
||||
import LegacyComponents
|
||||
import MediaEditor
|
||||
import TelegramPresentationData
|
||||
|
||||
private final class BlurModeComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
@ -115,17 +116,20 @@ private final class BlurModeComponent: Component {
|
||||
final class BlurComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let strings: PresentationStrings
|
||||
let value: BlurValue
|
||||
let hasPortrait: Bool
|
||||
let valueUpdated: (BlurValue) -> Void
|
||||
let isTrackingUpdated: (Bool) -> Void
|
||||
|
||||
init(
|
||||
strings: PresentationStrings,
|
||||
value: BlurValue,
|
||||
hasPortrait: Bool,
|
||||
valueUpdated: @escaping (BlurValue) -> Void,
|
||||
isTrackingUpdated: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.strings = strings
|
||||
self.value = value
|
||||
self.hasPortrait = hasPortrait
|
||||
self.valueUpdated = valueUpdated
|
||||
@ -133,6 +137,9 @@ final class BlurComponent: Component {
|
||||
}
|
||||
|
||||
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
@ -190,7 +197,7 @@ final class BlurComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
Text(
|
||||
text: "Blur",
|
||||
text: component.strings.Story_Editor_Blur_Title,
|
||||
font: Font.regular(14.0),
|
||||
color: UIColor(rgb: 0x808080)
|
||||
)
|
||||
@ -212,7 +219,7 @@ final class BlurComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Off",
|
||||
title: component.strings.Story_Editor_Blur_Off,
|
||||
icon: self.offImage,
|
||||
isSelected: state.value.mode == .off
|
||||
)
|
||||
@ -233,7 +240,7 @@ final class BlurComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Radial",
|
||||
title: component.strings.Story_Editor_Blur_Radial,
|
||||
icon: self.radialImage,
|
||||
isSelected: state.value.mode == .radial
|
||||
)
|
||||
@ -254,7 +261,7 @@ final class BlurComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Linear",
|
||||
title: component.strings.Story_Editor_Blur_Linear,
|
||||
icon: self.linearImage,
|
||||
isSelected: state.value.mode == .linear
|
||||
)
|
||||
@ -275,7 +282,7 @@ final class BlurComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BlurModeComponent(
|
||||
title: "Portrait",
|
||||
title: component.strings.Story_Editor_Blur_Portrait,
|
||||
icon: self.portraitImage,
|
||||
isSelected: state.value.mode == .portrait
|
||||
)
|
||||
|
@ -5,6 +5,7 @@ import ComponentFlow
|
||||
import LegacyComponents
|
||||
import MediaEditor
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
private class HistogramView: UIView {
|
||||
private var size: CGSize?
|
||||
@ -75,18 +76,24 @@ class CurvesInternalState {
|
||||
final class CurvesComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let strings: PresentationStrings
|
||||
let histogram: MediaEditorHistogram?
|
||||
let internalState: CurvesInternalState
|
||||
|
||||
init(
|
||||
strings: PresentationStrings,
|
||||
histogram: MediaEditorHistogram?,
|
||||
internalState: CurvesInternalState
|
||||
) {
|
||||
self.strings = strings
|
||||
self.histogram = histogram
|
||||
self.internalState = internalState
|
||||
}
|
||||
|
||||
static func ==(lhs: CurvesComponent, rhs: CurvesComponent) -> Bool {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.histogram != rhs.histogram {
|
||||
return false
|
||||
}
|
||||
@ -145,7 +152,7 @@ final class CurvesComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
Text(
|
||||
text: "All",
|
||||
text: component.strings.Story_Editor_Curves_All,
|
||||
font: Font.regular(14.0),
|
||||
color: state.section == .all ? .white : UIColor(rgb: 0x808080)
|
||||
)
|
||||
@ -173,7 +180,7 @@ final class CurvesComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
Text(
|
||||
text: "Red",
|
||||
text: component.strings.Story_Editor_Curves_Red,
|
||||
font: Font.regular(14.0),
|
||||
color: state.section == .red ? .white : UIColor(rgb: 0x808080)
|
||||
)
|
||||
@ -201,7 +208,7 @@ final class CurvesComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
Text(
|
||||
text: "Green",
|
||||
text: component.strings.Story_Editor_Curves_Green,
|
||||
font: Font.regular(14.0),
|
||||
color: state.section == .green ? .white : UIColor(rgb: 0x808080)
|
||||
)
|
||||
@ -229,7 +236,7 @@ final class CurvesComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
Text(
|
||||
text: "Blue",
|
||||
text: component.strings.Story_Editor_Curves_Blue,
|
||||
font: Font.regular(14.0),
|
||||
color: state.section == .blue ? .white : UIColor(rgb: 0x808080)
|
||||
)
|
||||
|
@ -725,9 +725,9 @@ final class MediaEditorScreenComponent: Component {
|
||||
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 {
|
||||
doneButtonTitle = "DONE"
|
||||
doneButtonTitle = environment.strings.Story_Editor_Done
|
||||
}
|
||||
|
||||
let doneButtonSize = self.doneButton.update(
|
||||
@ -736,7 +736,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
content: AnyComponent(DoneButtonComponent(
|
||||
backgroundColor: UIColor(rgb: 0x007aff),
|
||||
icon: UIImage(bundleImageName: "Media Editor/Next")!,
|
||||
title: doneButtonTitle)),
|
||||
title: doneButtonTitle.uppercased())),
|
||||
action: {
|
||||
guard let controller = environment.controller() as? MediaEditorScreen else {
|
||||
return
|
||||
@ -1097,7 +1097,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
style: .editor,
|
||||
placeholder: "Add a caption...",
|
||||
placeholder: environment.strings.Story_Editor_InputPlaceholderAddCaption,
|
||||
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
|
||||
queryTypes: [.mention],
|
||||
alwaysDarkWhenHasText: false,
|
||||
@ -1274,20 +1274,20 @@ final class MediaEditorScreenComponent: Component {
|
||||
var privacyText: String
|
||||
switch component.privacy.privacy.base {
|
||||
case .everyone:
|
||||
privacyText = "Everyone"
|
||||
privacyText = environment.strings.Story_ContextPrivacy_LabelEveryone
|
||||
case .closeFriends:
|
||||
privacyText = "Close Friends"
|
||||
privacyText = environment.strings.Story_ContextPrivacy_LabelCloseFriends
|
||||
case .contacts:
|
||||
privacyText = "Contacts"
|
||||
if additionalPeersCount > 0 {
|
||||
privacyText += " (-\(additionalPeersCount))"
|
||||
privacyText = environment.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalPeersCount)").string
|
||||
} else {
|
||||
privacyText = environment.strings.Story_ContextPrivacy_LabelContacts
|
||||
}
|
||||
case .nobody:
|
||||
privacyText = "Selected Contacts"
|
||||
if additionalPeersCount > 0 {
|
||||
privacyText += " (\(additionalPeersCount))"
|
||||
privacyText = environment.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalPeersCount))
|
||||
} 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 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
|
||||
})
|
||||
self.muteTooltip = tooltipController
|
||||
@ -2664,9 +2664,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let text: String
|
||||
let isVideo = self.mediaEditor?.resultIsVideo ?? false
|
||||
if isVideo {
|
||||
text = "Video saved to Photos."
|
||||
text = self.presentationData.strings.Story_Editor_TooltipVideoSavedToPhotos
|
||||
} else {
|
||||
text = "Image saved to Photos."
|
||||
text = self.presentationData.strings.Story_Editor_TooltipImageSavedToPhotos
|
||||
}
|
||||
|
||||
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 {
|
||||
tooltipController.content = .progress(text, progress)
|
||||
} 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 {
|
||||
tooltipController.content = .progress(text, progress)
|
||||
} else {
|
||||
@ -3081,7 +3079,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
ToolValueComponent(
|
||||
title: "Enhance",
|
||||
title: environment.strings.Story_Editor_Tool_Enhance,
|
||||
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)
|
||||
}
|
||||
|
||||
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 currentArchived = self.state.privacy.pin
|
||||
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: "6 Hours", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(6), icon: { theme in
|
||||
if !hasPremium {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
@ -3463,7 +3462,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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 {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} else {
|
||||
@ -3478,14 +3477,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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
|
||||
}, action: { _, a in
|
||||
a(.default)
|
||||
|
||||
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 {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||
} 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)
|
||||
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 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 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 {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
||||
self.push(controller)
|
||||
@ -3549,23 +3547,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let title: String
|
||||
let save: String
|
||||
if case .draft = self.node.subject {
|
||||
title = "Discard Draft?"
|
||||
save = "Keep Draft"
|
||||
title = presentationData.strings.Story_Editor_DraftDiscardDraft
|
||||
save = presentationData.strings.Story_Editor_DraftKeepDraft
|
||||
} else {
|
||||
title = "Discard Media?"
|
||||
save = "Save Draft"
|
||||
title = presentationData.strings.Story_Editor_DraftDiscardMedia
|
||||
save = presentationData.strings.Story_Editor_DraftKeepMedia
|
||||
}
|
||||
let theme = defaultDarkPresentationTheme
|
||||
let controller = textAlertController(
|
||||
context: self.context,
|
||||
forceTheme: theme,
|
||||
title: title,
|
||||
text: "If you go back now, you will lose any changes that you've made.",
|
||||
text: presentationData.strings.Story_Editor_DraftDiscaedText,
|
||||
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 {
|
||||
self.requestDismiss(saveDraft: false, animated: true)
|
||||
}
|
||||
@ -3575,7 +3574,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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()
|
||||
|
||||
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false)
|
||||
mediaEditor.requestRenderFrame()
|
||||
mediaEditor.stop()
|
||||
mediaEditor.invalidate()
|
||||
self.node.entitiesView.invalidate()
|
||||
|
||||
@ -3777,7 +3775,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
|
||||
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 videoResult: Result.VideoResult
|
||||
@ -3791,8 +3789,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
videoResult = .imageFile(path: tempImagePath)
|
||||
duration = 5.0
|
||||
|
||||
firstFrame = .single(image)
|
||||
case let .video(path, _, _, _, _, _, durationValue, _, _):
|
||||
firstFrame = .single((image, nil))
|
||||
case let .video(path, _, _, additionalPath, _, _, durationValue, _, _):
|
||||
videoResult = .videoFile(path: path)
|
||||
if let videoTrimRange = mediaEditor.values.videoTrimRange {
|
||||
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
|
||||
@ -3800,12 +3798,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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 avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
||||
if let cgImage {
|
||||
subscriber.putNext(UIImage(cgImage: cgImage))
|
||||
subscriber.putNext((UIImage(cgImage: cgImage), nil))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
@ -3824,14 +3825,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
} else {
|
||||
duration = 5.0
|
||||
}
|
||||
firstFrame = Signal<UIImage?, NoError> { subscriber in
|
||||
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
|
||||
if asset.mediaType == .video {
|
||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
||||
if let avAsset {
|
||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
||||
if let cgImage {
|
||||
subscriber.putNext(UIImage(cgImage: cgImage))
|
||||
subscriber.putNext((UIImage(cgImage: cgImage), nil))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
@ -3842,7 +3844,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
options.deliveryMode = .highQualityFormat
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
||||
if let image {
|
||||
subscriber.putNext(image)
|
||||
subscriber.putNext((image, nil))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
@ -3857,12 +3859,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
} else {
|
||||
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 avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
||||
if let cgImage {
|
||||
subscriber.putNext(UIImage(cgImage: cgImage))
|
||||
subscriber.putNext((UIImage(cgImage: cgImage), nil))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
@ -3875,21 +3878,34 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
duration = 5.0
|
||||
|
||||
if let image = UIImage(contentsOfFile: draft.fullPath()) {
|
||||
firstFrame = .single(image)
|
||||
firstFrame = .single((image, nil))
|
||||
} else {
|
||||
firstFrame = .single(UIImage())
|
||||
firstFrame = .single((UIImage(), nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let resultImage = mediaEditor.resultImage {
|
||||
firstFrame = .single(resultImage)
|
||||
}
|
||||
|
||||
let _ = (firstFrame
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image, additionalImage in
|
||||
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 {
|
||||
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
|
||||
|
@ -335,6 +335,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let isTablet: Bool
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
isTablet = true
|
||||
@ -577,7 +578,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
var tools: [AdjustmentTool] = [
|
||||
AdjustmentTool(
|
||||
key: .enhance,
|
||||
title: "Enhance",
|
||||
title: presentationData.strings.Story_Editor_Tool_Enhance,
|
||||
value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
@ -585,7 +586,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .brightness,
|
||||
title: "Brightness",
|
||||
title: presentationData.strings.Story_Editor_Tool_Brightness,
|
||||
value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
@ -593,7 +594,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .contrast,
|
||||
title: "Contrast",
|
||||
title: presentationData.strings.Story_Editor_Tool_Contrast,
|
||||
value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
@ -601,7 +602,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .saturation,
|
||||
title: "Saturation",
|
||||
title: presentationData.strings.Story_Editor_Tool_Saturation,
|
||||
value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
@ -609,7 +610,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .warmth,
|
||||
title: "Warmth",
|
||||
title: presentationData.strings.Story_Editor_Tool_Warmth,
|
||||
value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
@ -617,7 +618,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .fade,
|
||||
title: "Fade",
|
||||
title: presentationData.strings.Story_Editor_Tool_Fade,
|
||||
value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
@ -625,7 +626,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .highlights,
|
||||
title: "Highlights",
|
||||
title: presentationData.strings.Story_Editor_Tool_Highlights,
|
||||
value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
@ -633,7 +634,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .shadows,
|
||||
title: "Shadows",
|
||||
title: presentationData.strings.Story_Editor_Tool_Shadows,
|
||||
value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0,
|
||||
minValue: -1.0,
|
||||
maxValue: 1.0,
|
||||
@ -641,7 +642,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
),
|
||||
AdjustmentTool(
|
||||
key: .vignette,
|
||||
title: "Vignette",
|
||||
title: presentationData.strings.Story_Editor_Tool_Vignette,
|
||||
value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
@ -660,7 +661,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
if !component.mediaEditor.sourceIsVideo {
|
||||
tools.insert(AdjustmentTool(
|
||||
key: .grain,
|
||||
title: "Grain",
|
||||
title: presentationData.strings.Story_Editor_Tool_Grain,
|
||||
value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0,
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
@ -721,6 +722,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(TintComponent(
|
||||
strings: presentationData.strings,
|
||||
shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial,
|
||||
highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial,
|
||||
shadowsValueUpdated: { [weak state] value in
|
||||
@ -778,6 +780,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(BlurComponent(
|
||||
strings: presentationData.strings,
|
||||
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
|
||||
hasPortrait: mediaEditor?.hasPortraitMask ?? false,
|
||||
valueUpdated: { [weak state] value in
|
||||
@ -853,6 +856,7 @@ private final class MediaToolsScreenComponent: Component {
|
||||
optionsSize = self.toolOptions.update(
|
||||
transition: optionsTransition,
|
||||
component: AnyComponent(CurvesComponent(
|
||||
strings: presentationData.strings,
|
||||
histogram: state.histogram,
|
||||
internalState: internalState
|
||||
)),
|
||||
|
@ -218,10 +218,12 @@ final class StoryPreviewComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(
|
||||
text: "My story",
|
||||
text: presentationData.strings.Story_HeaderYourStory,
|
||||
font: Font.medium(14.0),
|
||||
color: .white
|
||||
)),
|
||||
@ -240,7 +242,6 @@ final class StoryPreviewComponent: Component {
|
||||
transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size))
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let inputPanelSize = self.inputPanel.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MessageInputPanelComponent(
|
||||
@ -249,7 +250,7 @@ final class StoryPreviewComponent: Component {
|
||||
theme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
style: .story,
|
||||
placeholder: "Reply Privately...",
|
||||
placeholder: presentationData.strings.Story_InputPlaceholderReplyPrivately,
|
||||
maxLength: nil,
|
||||
queryTypes: [],
|
||||
alwaysDarkWhenHasText: false,
|
||||
|
@ -4,6 +4,7 @@ import Display
|
||||
import ComponentFlow
|
||||
import LegacyComponents
|
||||
import MediaEditor
|
||||
import TelegramPresentationData
|
||||
|
||||
private final class TintColorComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
@ -106,6 +107,7 @@ final class TintComponent: Component {
|
||||
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let strings: PresentationStrings
|
||||
let shadowsValue: TintValue
|
||||
let highlightsValue: TintValue
|
||||
let shadowsValueUpdated: (TintValue) -> Void
|
||||
@ -113,12 +115,14 @@ final class TintComponent: Component {
|
||||
let isTrackingUpdated: (Bool) -> Void
|
||||
|
||||
init(
|
||||
strings: PresentationStrings,
|
||||
shadowsValue: TintValue,
|
||||
highlightsValue: TintValue,
|
||||
shadowsValueUpdated: @escaping (TintValue) -> Void,
|
||||
highlightsValueUpdated: @escaping (TintValue) -> Void,
|
||||
isTrackingUpdated: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.strings = strings
|
||||
self.shadowsValue = shadowsValue
|
||||
self.highlightsValue = highlightsValue
|
||||
self.shadowsValueUpdated = shadowsValueUpdated
|
||||
@ -127,6 +131,9 @@ final class TintComponent: Component {
|
||||
}
|
||||
|
||||
static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.highlightsValue != rhs.highlightsValue {
|
||||
return false
|
||||
}
|
||||
@ -185,7 +192,7 @@ final class TintComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
Text(
|
||||
text: "Shadows",
|
||||
text: component.strings.Story_Editor_Tint_Shadows,
|
||||
font: Font.regular(14.0),
|
||||
color: state.section == .shadows ? .white : UIColor(rgb: 0x808080)
|
||||
)
|
||||
@ -213,7 +220,7 @@ final class TintComponent: Component {
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
Text(
|
||||
text: "Highlights",
|
||||
text: component.strings.Story_Editor_Tint_Highlights,
|
||||
font: Font.regular(14.0),
|
||||
color: state.section == .highlights ? .white : UIColor(rgb: 0x808080)
|
||||
)
|
||||
|
@ -726,8 +726,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
||||
|
||||
if isStories == nil || isStories == true {
|
||||
if case .user = peer {
|
||||
//TODO:localize
|
||||
entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: "STORY NOTIFICATIONS"))
|
||||
entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notification_Exceptions_StoriesHeader))
|
||||
index += 1
|
||||
entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesMuted == .alwaysOn))
|
||||
index += 1
|
||||
@ -735,7 +734,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
||||
index += 1
|
||||
|
||||
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
|
||||
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn))
|
||||
index += 1
|
||||
|
@ -88,9 +88,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
let strings = presentationData.strings
|
||||
|
||||
if self.selectedCount != 0 {
|
||||
//TODO:localize
|
||||
//TODO:update icon
|
||||
items.append(.action(ContextMenuActionItem(text: "Save to Photos", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryList_ContextSaveToGallery, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -113,16 +111,10 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
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 text: String = presentationData.strings.StoryList_TooltipStoriesDeleted(Int32(paneNode.selectedIds.count))
|
||||
|
||||
environment.controller()?.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
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 {
|
||||
var ignoreNextActions = false
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Archive", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryList_ContextShowArchive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StoryArchive"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
if ignoreNextActions {
|
||||
@ -305,8 +296,8 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let saveScreen = SaveProgressScreen(context: component.context, content: .progress("Saving", 0.0))
|
||||
let strings = (component.context.sharedContext.currentPresentationData.with { $0 }).strings
|
||||
let saveScreen = SaveProgressScreen(context: component.context, content: .progress(strings.Story_TooltipSaving, 0.0))
|
||||
self.environment?.controller()?.present(saveScreen, in: .current)
|
||||
|
||||
let valueNorm: Float = 1.0 / Float(sortedItems.count)
|
||||
@ -330,12 +321,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
guard let saveScreen else {
|
||||
return
|
||||
}
|
||||
saveScreen.content = .progress("Saving", progress)
|
||||
saveScreen.content = .progress(strings.Story_TooltipSaving, progress)
|
||||
}, completed: { [weak saveScreen] in
|
||||
guard let saveScreen else {
|
||||
return
|
||||
}
|
||||
saveScreen.content = .completion("Saved")
|
||||
saveScreen.content = .completion(strings.Story_TooltipSaved)
|
||||
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
||||
saveScreen?.dismiss()
|
||||
})
|
||||
@ -376,12 +367,11 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
self.selectionPanel = selectionPanel
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let selectionPanelSize = selectionPanel.update(
|
||||
transition: selectionPanelTransition,
|
||||
component: AnyComponent(BottomButtonPanelComponent(
|
||||
theme: environment.theme,
|
||||
title: "Save to Profile",
|
||||
title: environment.strings.StoryList_SaveToProfile,
|
||||
label: nil,
|
||||
isEnabled: true,
|
||||
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()
|
||||
|
||||
//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 title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count))
|
||||
environment.controller()?.present(UndoOverlayController(
|
||||
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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -596,7 +580,8 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
func updateTitle() {
|
||||
//TODO:localize
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
switch self.scope {
|
||||
case .saved:
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||
@ -608,16 +593,16 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
||||
} else {
|
||||
title = nil
|
||||
}
|
||||
self.titleView?.titleContent = .custom("My Stories", title, false)
|
||||
self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
|
||||
case .archive:
|
||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||
return
|
||||
}
|
||||
let title: String
|
||||
if componentView.selectedCount != 0 {
|
||||
title = "\(componentView.selectedCount) Selected"
|
||||
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||
} else {
|
||||
title = "Stories Archive"
|
||||
title = presentationData.strings.StoryList_TitleArchive
|
||||
}
|
||||
self.titleView?.titleContent = .custom(title, nil, false)
|
||||
}
|
||||
|
@ -1560,17 +1560,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
let title: String
|
||||
if state.totalCount == 0 {
|
||||
title = ""
|
||||
} else if state.totalCount == 1 {
|
||||
if self.isSaved {
|
||||
title = "1 saved story"
|
||||
} else {
|
||||
title = "1 story"
|
||||
}
|
||||
} else {
|
||||
if self.isSaved {
|
||||
title = "\(state.totalCount) saved stories"
|
||||
title = self.presentationData.strings.StoryList_SubtitleSaved(Int32(state.totalCount))
|
||||
} 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)))
|
||||
@ -1607,8 +1601,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
var headerText: String?
|
||||
if strongSelf.isArchive && !mappedItems.isEmpty {
|
||||
//TODO:localize
|
||||
headerText = "Only you can see archived stories unless you choose to save them to your profile."
|
||||
headerText = strongSelf.presentationData.strings.StoryList_ArchiveDescription
|
||||
}
|
||||
|
||||
let items = SparseItemGrid.Items(
|
||||
@ -1914,16 +1907,15 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
emptyStateView = ComponentView()
|
||||
self.emptyStateView = emptyStateView
|
||||
}
|
||||
//TODO:localize
|
||||
let emptyStateSize = emptyStateView.update(
|
||||
transition: emptyStateTransition,
|
||||
component: AnyComponent(EmptyStateIndicatorComponent(
|
||||
context: self.context,
|
||||
theme: presentationData.theme,
|
||||
animationName: "StoryListEmpty",
|
||||
title: self.isArchive ? "No Archived Stories" : "No saved stories",
|
||||
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.",
|
||||
actionTitle: self.isArchive ? nil : "Open Archive",
|
||||
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title,
|
||||
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text,
|
||||
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -128,12 +128,11 @@ public final class ArchiveInfoContentComponent: Component {
|
||||
contentHeight += 15.0
|
||||
|
||||
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()
|
||||
imageAttachment.image = self.iconBackground.image
|
||||
titleString.append(NSAttributedString(attachment: imageAttachment))
|
||||
|
||||
//TODO:localize
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
@ -153,11 +152,10 @@ public final class ArchiveInfoContentComponent: Component {
|
||||
contentHeight += 16.0
|
||||
|
||||
let text: String
|
||||
//TODO:localize
|
||||
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 {
|
||||
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()
|
||||
@ -229,18 +227,18 @@ public final class ArchiveInfoContentComponent: Component {
|
||||
let itemDescs: [ItemDesc] = [
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconArchived",
|
||||
title: "Archived Chats",
|
||||
text: "Move any chat into your Archive and back by swiping on it."
|
||||
title: component.strings.ArchiveInfo_ChatsTitle,
|
||||
text: component.strings.ArchiveInfo_ChatsText
|
||||
),
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconHide",
|
||||
title: "Hiding Archive",
|
||||
text: "Hide the Archive from your Main screen by swiping on it."
|
||||
title: component.strings.ArchiveInfo_HideTitle,
|
||||
text: component.strings.ArchiveInfo_HideText
|
||||
),
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconStories",
|
||||
title: "Stories",
|
||||
text: "Archive Stories from your contacts separately from chats with them."
|
||||
title: component.strings.ArchiveInfo_StoriesTitle,
|
||||
text: component.strings.ArchiveInfo_StoriesText
|
||||
)
|
||||
]
|
||||
for i in 0 ..< itemDescs.count {
|
||||
|
@ -76,7 +76,6 @@ private final class ArchiveInfoSheetContentComponent: Component {
|
||||
contentHeight += contentSize.height
|
||||
contentHeight += 30.0
|
||||
|
||||
//TODO:localize
|
||||
let buttonSize = self.button.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
@ -86,7 +85,7 @@ private final class ArchiveInfoSheetContentComponent: Component {
|
||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||
),
|
||||
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,
|
||||
displaysProgress: false,
|
||||
|
@ -310,8 +310,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
case .misc:
|
||||
return strings.StorageManagement_SectionMiscellaneous
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
return "Stories"
|
||||
return strings.StorageManagement_SectionStories
|
||||
}
|
||||
}
|
||||
|
||||
@ -1810,8 +1809,7 @@ final class StorageUsageScreenComponent: Component {
|
||||
mappedCategory = .groups
|
||||
case 3:
|
||||
iconName = "Settings/Menu/Stories"
|
||||
//TODO:localized
|
||||
title = "Stories"
|
||||
title = environment.strings.Notifications_Stories
|
||||
mappedCategory = .stories
|
||||
default:
|
||||
iconName = "Settings/Menu/Channels"
|
||||
|
@ -6,6 +6,7 @@ import AccountContext
|
||||
import TelegramCore
|
||||
import TelegramStringFormatting
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
final class StoryAuthorInfoComponent: Component {
|
||||
struct Counters: Equatable {
|
||||
@ -14,13 +15,15 @@ final class StoryAuthorInfoComponent: Component {
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer?
|
||||
let timestamp: Int32
|
||||
let counters: Counters?
|
||||
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.strings = strings
|
||||
self.peer = peer
|
||||
self.timestamp = timestamp
|
||||
self.counters = counters
|
||||
@ -31,6 +34,9 @@ final class StoryAuthorInfoComponent: Component {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
@ -75,8 +81,7 @@ final class StoryAuthorInfoComponent: Component {
|
||||
|
||||
let title: String
|
||||
if component.peer?.id == component.context.account.peerId {
|
||||
//TODO:localize
|
||||
title = "Your story"
|
||||
title = component.strings.Story_HeaderYourStory
|
||||
} else {
|
||||
title = component.peer?.debugDisplayTitle ?? ""
|
||||
}
|
||||
@ -86,7 +91,7 @@ final class StoryAuthorInfoComponent: Component {
|
||||
|
||||
if component.isEdited {
|
||||
subtitle.append(" • ")
|
||||
subtitle.append("edited")
|
||||
subtitle.append(component.strings.Story_HeaderEdited)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
|
@ -9,6 +9,7 @@ import TextNodeWithEntities
|
||||
import TextFormat
|
||||
import InvisibleInkDustNode
|
||||
import UrlEscaping
|
||||
import TelegramPresentationData
|
||||
|
||||
final class StoryContentCaptionComponent: Component {
|
||||
enum Action {
|
||||
@ -41,6 +42,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
let externalState: ExternalState
|
||||
let context: AccountContext
|
||||
let strings: PresentationStrings
|
||||
let text: String
|
||||
let entities: [MessageTextEntity]
|
||||
let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
@ -50,6 +52,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
init(
|
||||
externalState: ExternalState,
|
||||
context: AccountContext,
|
||||
strings: PresentationStrings,
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
entityFiles: [EngineMedia.Id: TelegramMediaFile],
|
||||
@ -58,6 +61,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.entityFiles = entityFiles
|
||||
@ -72,6 +76,9 @@ final class StoryContentCaptionComponent: Component {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
@ -425,9 +432,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
let truncationToken = NSMutableAttributedString()
|
||||
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(
|
||||
attributedString: attributedText,
|
||||
maximumNumberOfLines: 3,
|
||||
|
@ -31,13 +31,15 @@ final class StoryItemContentComponent: Component {
|
||||
let peer: EnginePeer
|
||||
let item: EngineStoryItem
|
||||
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.strings = strings
|
||||
self.peer = peer
|
||||
self.item = item
|
||||
self.audioMode = audioMode
|
||||
self.isVideoBuffering = isVideoBuffering
|
||||
}
|
||||
|
||||
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
||||
@ -53,6 +55,9 @@ final class StoryItemContentComponent: Component {
|
||||
if lhs.item != rhs.item {
|
||||
return false
|
||||
}
|
||||
if lhs.isVideoBuffering != rhs.isVideoBuffering {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -592,11 +597,10 @@ final class StoryItemContentComponent: Component {
|
||||
self.unsupportedButton = unsupportedButton
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let unsupportedTextSize = unsupportedText.update(
|
||||
transition: .immediate,
|
||||
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,
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
@ -611,7 +615,7 @@ final class StoryItemContentComponent: Component {
|
||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
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,
|
||||
displaysProgress: false,
|
||||
@ -655,10 +659,13 @@ final class StoryItemContentComponent: Component {
|
||||
self.updateProgressMode(update: false)
|
||||
|
||||
if reloadMedia && synchronousLoad {
|
||||
let _ = startTime
|
||||
#if DEBUG
|
||||
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
||||
#endif
|
||||
}
|
||||
|
||||
if !self.contentLoaded {
|
||||
if !self.contentLoaded || component.isVideoBuffering {
|
||||
let loadingEffectView: StoryItemLoadingEffectView
|
||||
if let current = self.loadingEffectView {
|
||||
loadingEffectView = current
|
||||
@ -668,7 +675,7 @@ final class StoryItemContentComponent: Component {
|
||||
self.addSubview(loadingEffectView)
|
||||
}
|
||||
loadingEffectView.update(size: availableSize, transition: transition)
|
||||
} else if let loadingEffectView = self.loadingEffectView{
|
||||
} else if let loadingEffectView = self.loadingEffectView {
|
||||
self.loadingEffectView = nil
|
||||
loadingEffectView.layer.animateAlpha(from: loadingEffectView.alpha, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak loadingEffectView] _ in
|
||||
loadingEffectView?.removeFromSuperview()
|
||||
|
@ -34,11 +34,6 @@ final class StoryItemImageView: UIView {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.contentView)
|
||||
#if DEBUG
|
||||
if "".isEmpty {
|
||||
self.contentView.isHidden = true
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -292,11 +287,10 @@ final class CaptureProtectedInfoComponent: Component {
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
//TODO:localize
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
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,
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
@ -306,7 +300,7 @@ final class CaptureProtectedInfoComponent: Component {
|
||||
let textSize = self.text.update(
|
||||
transition: transition,
|
||||
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,
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
|
@ -1074,11 +1074,18 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
if visibleItem.currentProgress != progress || visibleItem.isBuffering != isBuffering || canSwitch {
|
||||
visibleItem.currentProgress = progress
|
||||
|
||||
let isBufferingUpdated = visibleItem.isBuffering != isBuffering
|
||||
visibleItem.isBuffering = isBuffering
|
||||
|
||||
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
|
||||
navigationStripView.updateCurrentItemProgress(value: progress, isBuffering: isBuffering, transition: .immediate)
|
||||
}
|
||||
|
||||
if isBufferingUpdated {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
|
||||
if progress >= 1.0 && canSwitch && !visibleItem.requestedNext {
|
||||
visibleItem.requestedNext = true
|
||||
|
||||
@ -1102,7 +1109,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
strings: component.strings,
|
||||
peer: component.slice.peer,
|
||||
item: item.storyItem,
|
||||
audioMode: component.audioMode
|
||||
audioMode: component.audioMode,
|
||||
isVideoBuffering: visibleItem.isBuffering
|
||||
)),
|
||||
environment: {
|
||||
itemEnvironment
|
||||
@ -1761,10 +1769,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
var isUnsupported = false
|
||||
var disabledPlaceholder: String?
|
||||
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 {
|
||||
isUnsupported = true
|
||||
disabledPlaceholder = "You can't reply to this story"
|
||||
disabledPlaceholder = component.strings.Story_FooterReplyUnavailable
|
||||
}
|
||||
|
||||
var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
|
||||
@ -1779,7 +1787,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
style: .story,
|
||||
placeholder: "Reply Privately...",
|
||||
placeholder: component.strings.Story_InputPlaceholderReplyPrivately,
|
||||
maxLength: 4096,
|
||||
queryTypes: [.mention, .emoji],
|
||||
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
||||
@ -2120,7 +2128,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
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()
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
@ -2463,9 +2471,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
let tooltipText: String
|
||||
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 {
|
||||
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(
|
||||
account: component.context.account,
|
||||
@ -2541,6 +2549,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(
|
||||
context: component.context,
|
||||
strings: component.strings,
|
||||
peer: component.slice.peer,
|
||||
timestamp: component.slice.item.storyItem.timestamp,
|
||||
counters: counters,
|
||||
@ -2679,6 +2688,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
component: AnyComponent(StoryContentCaptionComponent(
|
||||
externalState: captionItem.externalState,
|
||||
context: component.context,
|
||||
strings: component.strings,
|
||||
text: component.slice.item.storyItem.text,
|
||||
entities: component.slice.item.storyItem.entities,
|
||||
entityFiles: component.slice.item.entityFiles,
|
||||
@ -2923,10 +2933,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
|
||||
if let animation, let self {
|
||||
if let animation, let self, let component = self.component {
|
||||
let controller = UndoOverlayController(
|
||||
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 {
|
||||
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
|
||||
if privacy.base == .contacts {
|
||||
text = "This story is shown to all your contacts."
|
||||
text = component.strings.Story_PrivacyTooltipContacts
|
||||
} else if privacy.base == .closeFriends {
|
||||
text = "This story is shown to your close friends."
|
||||
text = component.strings.Story_PrivacyTooltipCloseFriends
|
||||
} else if privacy.base == .nobody {
|
||||
if !privacy.additionallyIncludePeers.isEmpty {
|
||||
text = "This story is shown to selected contacts."
|
||||
text = component.strings.Story_PrivacyTooltipSelectedContacts
|
||||
} else {
|
||||
text = "This story is shown only to you."
|
||||
text = component.strings.Story_PrivacyTooltipNobody
|
||||
}
|
||||
} else {
|
||||
text = "This story is shown to everyone."
|
||||
text = component.strings.Story_PrivacyTooltipEveryone
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -3560,20 +3570,23 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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)
|
||||
|
||||
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()))
|
||||
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
|
||||
guard let saveScreen else {
|
||||
return
|
||||
}
|
||||
saveScreen.content = .progress("Saving", progress)
|
||||
saveScreen.content = .progress(stringSaving, progress)
|
||||
}, completed: { [weak saveScreen] in
|
||||
guard let saveScreen else {
|
||||
return
|
||||
}
|
||||
saveScreen.content = .completion("Saved")
|
||||
saveScreen.content = .completion(stringSaved)
|
||||
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
||||
saveScreen?.dismiss()
|
||||
})
|
||||
@ -3624,28 +3637,24 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let privacyText: String
|
||||
switch component.slice.item.storyItem.privacy?.base {
|
||||
case .closeFriends:
|
||||
privacyText = "Close Friends"
|
||||
privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends
|
||||
case .contacts:
|
||||
if additionalCount != 0 {
|
||||
privacyText = "Contacts (-\(additionalCount))"
|
||||
privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string
|
||||
} else {
|
||||
privacyText = "Contacts"
|
||||
privacyText = component.strings.Story_ContextPrivacy_LabelContacts
|
||||
}
|
||||
case .nobody:
|
||||
if additionalCount != 0 {
|
||||
if additionalCount == 1 {
|
||||
privacyText = "\(additionalCount) Person"
|
||||
} else {
|
||||
privacyText = "\(additionalCount) People"
|
||||
}
|
||||
privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount))
|
||||
} else {
|
||||
privacyText = "Only Me"
|
||||
privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe
|
||||
}
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3656,7 +3665,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3669,7 +3678,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3684,7 +3693,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if component.slice.item.storyItem.isPinned {
|
||||
self.component?.presentController(UndoOverlayController(
|
||||
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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -3692,7 +3701,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
} else {
|
||||
self.component?.presentController(UndoOverlayController(
|
||||
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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -3700,12 +3709,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
})))
|
||||
|
||||
let saveText: String
|
||||
if case .file = component.slice.item.storyItem.media {
|
||||
saveText = "Save Video"
|
||||
} else {
|
||||
saveText = "Save Image"
|
||||
}
|
||||
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||
}, 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) {
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3737,7 +3741,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
component.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .linkCopied(text: "Link copied."),
|
||||
content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: 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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3782,7 +3786,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
tipSignal = packsPromise.get()
|
||||
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
||||
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 {
|
||||
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||
|> filter { result in
|
||||
@ -3796,7 +3801,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if case let .result(info, items, _) = result {
|
||||
let isEmoji = info.flags.contains(.isEmoji)
|
||||
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(
|
||||
context: context,
|
||||
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)
|
||||
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3876,7 +3881,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -3890,7 +3895,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -3903,7 +3908,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
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 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(
|
||||
context: component.context,
|
||||
account: component.context.account,
|
||||
@ -3923,7 +3928,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0),
|
||||
icon: .peer(peer: component.slice.peer, isStory: true),
|
||||
action: TooltipScreen.Action(
|
||||
title: "Undo",
|
||||
title: component.strings.Undo_Undo,
|
||||
action: {
|
||||
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 {
|
||||
let saveText: String = "Save to Gallery"
|
||||
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||
}, 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) {
|
||||
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)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
@ -3979,7 +3984,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
component.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .linkCopied(text: "Link copied."),
|
||||
content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: 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 {
|
||||
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)
|
||||
}, action: { [weak self] c, a in
|
||||
guard let self, let component = self.component, let controller = component.controller() else {
|
||||
@ -4060,7 +4055,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
tipSignal = packsPromise.get()
|
||||
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
||||
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 {
|
||||
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||
|> filter { result in
|
||||
@ -4074,7 +4070,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if case let .result(info, items, _) = result {
|
||||
let isEmoji = info.flags.contains(.isEmoji)
|
||||
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(
|
||||
context: context,
|
||||
cache: context.animationCache,
|
||||
@ -4121,7 +4117,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let tooltipScreen = TooltipScreen(
|
||||
account: component.context.account,
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
@ -344,11 +344,11 @@ final class StoryItemSetContainerSendMessage {
|
||||
|
||||
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(
|
||||
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,
|
||||
animateInAsReplacement: false,
|
||||
action: { [weak view, weak self] action in
|
||||
@ -881,7 +881,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
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()
|
||||
|
||||
guard let self, let view else {
|
||||
@ -911,7 +911,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
} else {
|
||||
var preferredAction: ShareControllerPreferredAction?
|
||||
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))
|
||||
|> deliverOnMainQueue).start(next: { link in
|
||||
if let link {
|
||||
@ -920,7 +920,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
component.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .linkCopied(text: "Link copied."),
|
||||
content: .linkCopied(text: presentationData.strings.Story_ToastLinkCopied),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
@ -1025,7 +1025,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
component.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .linkCopied(text: "Link copied."),
|
||||
content: .linkCopied(text: presentationData.strings.Story_ToastLinkCopied),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
|
@ -685,6 +685,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(StoryFooterPanelComponent(
|
||||
context: component.context,
|
||||
strings: component.strings,
|
||||
storyItem: component.storyItem,
|
||||
externalViews: externalViews,
|
||||
expandFraction: dismissFraction,
|
||||
|
@ -10,9 +10,11 @@ import TelegramCore
|
||||
import MoreHeaderButton
|
||||
import SemanticStatusNode
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
|
||||
public final class StoryFooterPanelComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let strings: PresentationStrings
|
||||
public let storyItem: EngineStoryItem?
|
||||
public let externalViews: EngineStoryItem.Views?
|
||||
public let expandFraction: CGFloat
|
||||
@ -22,6 +24,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
strings: PresentationStrings,
|
||||
storyItem: EngineStoryItem?,
|
||||
externalViews: EngineStoryItem.Views?,
|
||||
expandFraction: CGFloat,
|
||||
@ -30,6 +33,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
moreAction: @escaping (UIView, ContextGesture?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.storyItem = storyItem
|
||||
self.externalViews = externalViews
|
||||
self.expandViewStats = expandViewStats
|
||||
@ -42,6 +46,9 @@ public final class StoryFooterPanelComponent: Component {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.storyItem != rhs.storyItem {
|
||||
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)))
|
||||
|
||||
//TODO:localize
|
||||
let uploadingTextSize = uploadingText.update(
|
||||
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: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
)
|
||||
@ -260,11 +266,9 @@ public final class StoryFooterPanelComponent: Component {
|
||||
|
||||
let viewsText: String
|
||||
if viewCount == 0 {
|
||||
viewsText = "No Views"
|
||||
} else if viewCount == 1 {
|
||||
viewsText = "1 view"
|
||||
viewsText = component.strings.Story_Footer_NoViews
|
||||
} else {
|
||||
viewsText = "\(viewCount) views"
|
||||
viewsText = component.strings.Story_Footer_Views(Int32(viewCount))
|
||||
}
|
||||
|
||||
self.viewStatsButton.isEnabled = viewCount != 0
|
||||
|
@ -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.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
|
||||
if component.peer.id == component.context.account.peerId {
|
||||
if let ringAnimation = component.ringAnimation, case .progress = ringAnimation {
|
||||
titleString = "Uploading..."
|
||||
titleString = component.strings.StoryFeed_MyUploading
|
||||
} else {
|
||||
titleString = "My story"
|
||||
titleString = component.strings.StoryFeed_MyStory
|
||||
}
|
||||
} else {
|
||||
titleString = component.peer.compactDisplayTitle.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
@ -89,6 +89,7 @@ private final class ShapeImageView: UIView {
|
||||
|
||||
public final class StorySetIndicatorComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let strings: PresentationStrings
|
||||
public let peer: EnginePeer
|
||||
public let items: [EngineStoryItem]
|
||||
public let hasUnseen: Bool
|
||||
@ -99,6 +100,7 @@ public final class StorySetIndicatorComponent: Component {
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer,
|
||||
items: [EngineStoryItem],
|
||||
hasUnseen: Bool,
|
||||
@ -108,6 +110,7 @@ public final class StorySetIndicatorComponent: Component {
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
self.items = items
|
||||
self.hasUnseen = hasUnseen
|
||||
@ -118,6 +121,9 @@ public final class StorySetIndicatorComponent: Component {
|
||||
}
|
||||
|
||||
public static func ==(lhs: StorySetIndicatorComponent, rhs: StorySetIndicatorComponent) -> Bool {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
@ -378,14 +384,11 @@ public final class StorySetIndicatorComponent: Component {
|
||||
self.imageView.setNeedsDisplay()
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let textValue: String
|
||||
if component.totalCount == 0 {
|
||||
textValue = ""
|
||||
} else if component.totalCount == 1 {
|
||||
textValue = "1 story"
|
||||
} else {
|
||||
textValue = "\(component.totalCount) stories"
|
||||
textValue = component.strings.Story_Footer_Views(Int32(component.totalCount))
|
||||
}
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "smoothGradient 0.4.png",
|
||||
"filename" : "smoothGradient 0.6.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Stories/PanelGradient.imageset/smoothGradient 0.6.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Stories/PanelGradient.imageset/smoothGradient 0.6.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -4524,12 +4524,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
if let story = message.associatedStories[storyId], story.data.isEmpty {
|
||||
//TODO:localize
|
||||
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)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: false)
|
||||
let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: true)
|
||||
let _ = (storyContent.state
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
|
@ -137,15 +137,14 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
} else {
|
||||
titleString = arguments.strings.User_DeletedAccount
|
||||
}
|
||||
//TODO:localize
|
||||
isText = false
|
||||
if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty {
|
||||
isExpiredStory = true
|
||||
textString = NSAttributedString(string: "Expired story")
|
||||
textString = NSAttributedString(string: arguments.strings.Chat_ReplyExpiredStory)
|
||||
isMedia = false
|
||||
} else {
|
||||
isStory = true
|
||||
textString = NSAttributedString(string: "Story")
|
||||
textString = NSAttributedString(string: arguments.strings.Chat_ReplyStory)
|
||||
isMedia = true
|
||||
}
|
||||
} else {
|
||||
|
@ -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()))
|
||||
|
||||
//TODO:localize
|
||||
|
||||
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 (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 backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 186.0)
|
||||
|
||||
|
@ -37,7 +37,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
if let story {
|
||||
let navigationController = params.navigationController
|
||||
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
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
|
||||
|
@ -814,7 +814,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { exists in
|
||||
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
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
|
||||
@ -833,12 +833,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
navigationController?.pushViewController(storyContainerScreen)
|
||||
})
|
||||
} else {
|
||||
//TODO:localize
|
||||
var elevatedLayout = true
|
||||
if case .chat = urlContext {
|
||||
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
|
||||
}), nil)
|
||||
}
|
||||
|
@ -1050,7 +1050,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
||||
markupNode = current
|
||||
} else {
|
||||
markupNode = AvatarVideoNode(context: self.context)
|
||||
self.insertSubnode(markupNode, aboveSubnode: self.avatarNode)
|
||||
self.avatarNode.contentNode.addSubnode(markupNode)
|
||||
self.markupNode = markupNode
|
||||
}
|
||||
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
|
||||
@ -1083,7 +1083,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
||||
shape.path = maskPath.cgPath
|
||||
videoNode.layer.mask = shape
|
||||
|
||||
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||
self.avatarNode.contentNode.addSubnode(videoNode)
|
||||
}
|
||||
} else {
|
||||
if let markupNode = self.markupNode {
|
||||
@ -1107,15 +1107,15 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if let markupNode = self.markupNode {
|
||||
markupNode.frame = self.avatarNode.frame
|
||||
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate)
|
||||
markupNode.frame = self.avatarNode.bounds
|
||||
markupNode.updateLayout(size: self.avatarNode.bounds.size, cornerRadius: avatarCornerRadius, transition: .immediate)
|
||||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
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 {
|
||||
videoNode.canAttachContent = isEditing && self.canAttachVideo
|
||||
|
@ -956,8 +956,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
let title: String
|
||||
switch key {
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
title = "Stories"
|
||||
title = presentationData.strings.PeerInfo_PaneStories
|
||||
case .media:
|
||||
title = presentationData.strings.PeerInfo_PaneMedia
|
||||
case .files:
|
||||
|
@ -792,8 +792,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "My Stories", icon: PresentationResourcesSettings.stories, action: {
|
||||
items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyStories, icon: PresentationResourcesSettings.stories, action: {
|
||||
interaction.openSettings(.stories)
|
||||
}))
|
||||
|
||||
|
@ -141,8 +141,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
} else if content.type == "video" {
|
||||
text = stringForMediaKind(.video, strings: self.strings).0.string
|
||||
} else if content.type == "telegram_story" {
|
||||
//TODO:localize
|
||||
text = "Story"
|
||||
text = stringForMediaKind(.story, strings: self.strings).0.string
|
||||
} else if let _ = content.image {
|
||||
text = stringForMediaKind(.image, strings: self.strings).0.string
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user