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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-12
|
runs-on: macos-13
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -9354,6 +9354,13 @@ Sorry for the inconvenience.";
|
|||||||
"ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off";
|
"ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off";
|
||||||
"ChatList.PremiumRestoreDiscountText" = "Your Telegram Premium has recently expired. Tap here to extend it.";
|
"ChatList.PremiumRestoreDiscountText" = "Your Telegram Premium has recently expired. Tap here to extend it.";
|
||||||
|
|
||||||
|
"Notification.LockScreenReactionPlaceholder" = "Reaction";
|
||||||
|
|
||||||
|
"UserInfo.BotNamePlaceholder" = "Bot Name";
|
||||||
|
|
||||||
|
"ChatList.PremiumRestoreDiscountTitle" = "Get Premium back with up to %@ off";
|
||||||
|
"ChatList.PremiumRestoreDiscountText" = "Your Telegram Premium has recently expired. Tap here to extend it.";
|
||||||
|
|
||||||
"Login.ErrorAppOutdated" = "Please update Telegram to the latest version to log in.";
|
"Login.ErrorAppOutdated" = "Please update Telegram to the latest version to log in.";
|
||||||
|
|
||||||
"Login.GetCodeViaFragment" = "Get a code via Fragment";
|
"Login.GetCodeViaFragment" = "Get a code via Fragment";
|
||||||
@ -9380,3 +9387,279 @@ Sorry for the inconvenience.";
|
|||||||
"Conversation.StoryMentionTextIncoming" = "%@ mentioned you\nin a story";
|
"Conversation.StoryMentionTextIncoming" = "%@ mentioned you\nin a story";
|
||||||
"Conversation.StoryExpiredMentionTextOutgoing" = "The story where you mentioned %@\n is no longer available";
|
"Conversation.StoryExpiredMentionTextOutgoing" = "The story where you mentioned %@\n is no longer available";
|
||||||
"Conversation.StoryExpiredMentionTextIncoming" = "The story you were mentioned in\nis no longer available";
|
"Conversation.StoryExpiredMentionTextIncoming" = "The story you were mentioned in\nis no longer available";
|
||||||
|
|
||||||
|
"ChatList.ArchiveStoryCount_1" = "1 story";
|
||||||
|
"ChatList.ArchiveStoryCount_any" = "%d stories";
|
||||||
|
|
||||||
|
"Notification.Story" = "Story";
|
||||||
|
|
||||||
|
"ChatList.StoryFeedTooltip" = "Tap above to view updates\nfrom %@";
|
||||||
|
|
||||||
|
"StoryFeed.ContextAddStory" = "Add Story";
|
||||||
|
"StoryFeed.ContextSavedStories" = "Saved Stories";
|
||||||
|
"StoryFeed.ContextArchivedStories" = "Archived Stories";
|
||||||
|
"StoryFeed.ContextOpenChat" = "Send Message";
|
||||||
|
"StoryFeed.ContextOpenProfile" = "View Profile";
|
||||||
|
"StoryFeed.ContextNotifyOn" = "Notify About Stories";
|
||||||
|
"StoryFeed.ContextNotifyOff" = "Do Not Notify About Stories";
|
||||||
|
"StoryFeed.ContextArchive" = "Hide Stories";
|
||||||
|
"StoryFeed.ContextUnarchive" = "Unhide Stories";
|
||||||
|
|
||||||
|
"StoryFeed.TooltipNotifyOn" = "You will now get a notification whenever **%@** posts a story.";
|
||||||
|
"StoryFeed.TooltipNotifyOff" = "You will no longer receive a notification when **%@** posts a story.";
|
||||||
|
"StoryFeed.TooltipArchive" = "Stories from **%@** will now be shown in Archived Chats.";
|
||||||
|
"StoryFeed.TooltipUnarchive" = "Stories from **%@** will now be shown in Chats.";
|
||||||
|
|
||||||
|
"ChatList.Archive.ContextSettings" = "Archive Settings";
|
||||||
|
"ChatList.Archive.ContextInfo" = "How Does It Work?";
|
||||||
|
"ChatList.ContextSelectChats" = "Select Chats";
|
||||||
|
|
||||||
|
"StoryFeed.TooltipPremiumPosting" = "Posting stories is currently available only\nto subscribers of [Telegram Premium]().";
|
||||||
|
"StoryFeed.TooltipStoryLimitValue_1" = "1 story";
|
||||||
|
"StoryFeed.TooltipStoryLimitValue_any" = "%d stories";
|
||||||
|
"StoryFeed.TooltipStoryLimit" = "You can't post more than **%@** stories in **24 hours**.";
|
||||||
|
|
||||||
|
"StoryFeed.TooltipPostingDuringCall" = "You can't post stories during a call.";
|
||||||
|
"StoryFeed.TooltipPostingDuringGroupCall" = "You can't post stories during a voice chat.";
|
||||||
|
|
||||||
|
"StoryFeed.MyStory" = "My Story";
|
||||||
|
"StoryFeed.MyUploading" = "Uploading...";
|
||||||
|
|
||||||
|
"MediaPicker.AddImage" = "Add Image";
|
||||||
|
|
||||||
|
"Premium.Stories" = "Story Posting";
|
||||||
|
"Premium.StoriesInfo" = "Be one of the first to share your stories with your contacts or an unlimited audience.";
|
||||||
|
"Premium.Stories.Proceed" = "Unlock Story Posting";
|
||||||
|
|
||||||
|
"AutoDownloadSettings.OnForContacts" = "On for contacts";
|
||||||
|
|
||||||
|
"AutoDownloadSettings.StoriesSectionHeader" = "AUTO-DOWNLOAD STORIES";
|
||||||
|
"AutoDownloadSettings.StoriesArchivedContacts" = "Archived Contacts";
|
||||||
|
|
||||||
|
"AutoDownloadSettings.StoriesTitle" = "Stories";
|
||||||
|
|
||||||
|
"Notifications.TopChats" = "Top 5";
|
||||||
|
"Notifications.Stories" = "Stories";
|
||||||
|
|
||||||
|
"Settings.MyStories" = "My Stories";
|
||||||
|
"Settings.StoriesArchive" = "Stories Archive";
|
||||||
|
|
||||||
|
"ArchiveSettings.Title" = "Archive Settings";
|
||||||
|
|
||||||
|
"ArchiveSettings.UnmutedChatsHeader" = "UNMUTED CHATS";
|
||||||
|
"ArchiveSettings.UnmutedChatsFooter" = "Keep archived chats in the Archive even if they are unmuted and get a new message.";
|
||||||
|
"ArchiveSettings.FolderChatsHeader" = "CHATS FROM FOLDERS";
|
||||||
|
"ArchiveSettings.FolderChatsFooter" = "Keep archived chats from folders in the Archive even if they are unmuted and get a new message.";
|
||||||
|
|
||||||
|
"ArchiveSettings.UnknownChatsHeader" = "NEW CHATS FROM UNKNOWN USERS";
|
||||||
|
"ArchiveSettings.UnknownChatsFooter" = "Automatically archive and mute new private chats, groups and channels from non-contacts.";
|
||||||
|
|
||||||
|
"ArchiveSettings.KeepArchived" = "Always Keep Archived";
|
||||||
|
"ArchiveSettings.TooltipPremiumRequired" = "This setting is available only to the subscribers of [Telegram Premium]().";
|
||||||
|
|
||||||
|
"NotificationSettings.Stories.ShowAll" = "Show All Notifications";
|
||||||
|
"NotificationSettings.Stories.ShowImportant" = "Show Important Notifications";
|
||||||
|
"NotificationSettings.Stories.ShowImportantFooter" = "Always on for top 5 contacts.";
|
||||||
|
"NotificationSettings.Stories.DisplayAuthorName" = "Display Author Name";
|
||||||
|
|
||||||
|
"NotificationSettings.Stories.AutomaticValue" = "%@ (automatic)";
|
||||||
|
"NotificationSettings.Stories.CompactShowName" = "Show name";
|
||||||
|
"NotificationSettings.Stories.CompactHideName" = "Hide name";
|
||||||
|
|
||||||
|
"Notifications.StoriesTitle" = "Stories";
|
||||||
|
|
||||||
|
"Message.Story" = "Story";
|
||||||
|
|
||||||
|
"Notification.Exceptions.StoriesHeader" = "STORY NOTIFICATIONS";
|
||||||
|
"Notification.Exceptions.StoriesDisplayAuthorName" = "DISPLAY AUTHOR NAME";
|
||||||
|
|
||||||
|
"StorageManagement.SectionStories" = "Stories";
|
||||||
|
|
||||||
|
"PeerInfo.PaneStories" = "Stories";
|
||||||
|
|
||||||
|
"Story.TooltipExpired" = "This story is no longer available";
|
||||||
|
|
||||||
|
"Chat.ReplyExpiredStory" = "Expired story";
|
||||||
|
"Chat.ReplyStory" = "Story";
|
||||||
|
|
||||||
|
"Chat.StoryMentionAction" = "View Story";
|
||||||
|
|
||||||
|
"StoryList.ContextSaveToGallery" = "Save to Gallery";
|
||||||
|
"StoryList.ContextShowArchive" = "Show Archive";
|
||||||
|
|
||||||
|
"StoryList.TooltipStoriesDeleted_1" = "1 story deleted.";
|
||||||
|
"StoryList.TooltipStoriesDeleted_any" = "%d stories deleted.";
|
||||||
|
|
||||||
|
"Story.TooltipSaving" = "Saving";
|
||||||
|
"Story.TooltipSaved" = "Saved";
|
||||||
|
|
||||||
|
"StoryList.SaveToProfile" = "Save to Profile";
|
||||||
|
"StoryList.TooltipStoriesSavedToProfile_1" = "Story saved to your profile";
|
||||||
|
"StoryList.TooltipStoriesSavedToProfile_any" = "%d stories saved to your profile.";
|
||||||
|
"StoryList.TooltipStoriesSavedToProfileText" = "Saved stories can be viewed by others on your profile until you remove them.";
|
||||||
|
|
||||||
|
"StoryList.TitleSaved" = "My Stories";
|
||||||
|
"StoryList.TitleArchive" = "Stories Archive";
|
||||||
|
"StoryList.SubtitleSelected_1" = "1 story selected";
|
||||||
|
"StoryList.SubtitleSelected_any" = "%d stories selected";
|
||||||
|
|
||||||
|
"StoryList.SubtitleSaved_1" = "1 saved story";
|
||||||
|
"StoryList.SubtitleSaved_any" = "%d saved stories";
|
||||||
|
|
||||||
|
"StoryList.SubtitleCount_1" = "1 story";
|
||||||
|
"StoryList.SubtitleCount_any" = "%d stories";
|
||||||
|
|
||||||
|
"StoryList.ArchiveDescription" = "Only you can see archived stories unless you choose to save them to your profile.";
|
||||||
|
|
||||||
|
"StoryList.SavedEmptyState.Title" = "No saved stories";
|
||||||
|
"StoryList.SavedEmptyState.Text" = "Open the Archive to select stories you\nwant to be displayed in your profile.";
|
||||||
|
"StoryList.ArchivedEmptyState.Title" = "No Archived Stories";
|
||||||
|
"StoryList.ArchivedEmptyState.Text" = "Upload a new story to view it here";
|
||||||
|
"StoryList.SavedEmptyAction" = "Open Archive";
|
||||||
|
|
||||||
|
"ArchiveInfo.Title" = "This is Your Archive";
|
||||||
|
|
||||||
|
"ArchiveInfo.TextKeepArchivedUnmuted" = "Archived chats will remain in the Archive when you receive a new message. [Tap to change >]()";
|
||||||
|
"ArchiveInfo.TextKeepArchivedDefault" = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change >]()";
|
||||||
|
|
||||||
|
"ArchiveInfo.ChatsTitle" = "Archived Chats";
|
||||||
|
"ArchiveInfo.ChatsText" = "Move any chat into your Archive and back by swiping on it.";
|
||||||
|
"ArchiveInfo.HideTitle" = "Hiding Archive";
|
||||||
|
"ArchiveInfo.HideText" = "Hide the Archive from your Main screen by swiping on it.";
|
||||||
|
"ArchiveInfo.StoriesTitle" = "Stories";
|
||||||
|
"ArchiveInfo.StoriesText" = "Archive Stories from your contacts separately from chats with them.";
|
||||||
|
"ArchiveInfo.CloseAction" = "Got it";
|
||||||
|
|
||||||
|
"Story.HeaderYourStory" = "Your story";
|
||||||
|
"Story.HeaderEdited" = "edited";
|
||||||
|
"Story.CaptionShowMore" = "Show more";
|
||||||
|
|
||||||
|
"Story.UnsupportedText" = "This story is not supported by\nyour version of Telegram.";
|
||||||
|
"Story.UnsupportedAction" = "Update Telegram";
|
||||||
|
|
||||||
|
"Story.ScreenshotBlockedTitle" = "Screenshot Blocked";
|
||||||
|
"Story.ScreenshotBlockedText" = "The story you tried to take a\nscreenshot of is protected from\ncopying by its creator.";
|
||||||
|
|
||||||
|
"Story.Footer.NoViews" = "No views";
|
||||||
|
"Story.Footer.Views_1" = "1 view";
|
||||||
|
"Story.Footer.Views_any" = "%d views";
|
||||||
|
|
||||||
|
"Story.Footer.Uploading" = "Uploading...";
|
||||||
|
|
||||||
|
"Story.FooterReplyUnavailable" = "You can't reply to this story";
|
||||||
|
"Story.InputPlaceholderReplyPrivately" = "Reply Privately...";
|
||||||
|
|
||||||
|
"Story.ContextDeleteStory" = "Delete Story";
|
||||||
|
|
||||||
|
"Story.TooltipPrivacyCloseFriendsMy" = "Only people from your close friends list will see this story.";
|
||||||
|
"Story.TooltipPrivacyCloseFriends" = "You are seeing this story because you have\nbeen added to %@'s list of close friends.";
|
||||||
|
|
||||||
|
"Story.ToastViewInChat" = "View in Chat";
|
||||||
|
"Story.ToastReactionSent" = "Reaction Sent.";
|
||||||
|
|
||||||
|
"Story.PrivacyTooltipContacts" = "This story is shown to all your contacts.";
|
||||||
|
"Story.PrivacyTooltipCloseFriends" = "This story is shown to your close friends.";
|
||||||
|
"Story.PrivacyTooltipSelectedContacts" = "This story is shown to selected contacts.";
|
||||||
|
"Story.PrivacyTooltipNobody" = "This story is shown only to you.";
|
||||||
|
"Story.PrivacyTooltipEveryone" = "This story is shown to everyone.";
|
||||||
|
|
||||||
|
"Story.ContextPrivacy.LabelCloseFriends" = "Close Friends";
|
||||||
|
"Story.ContextPrivacy.LabelContactsExcept" = "Contacts (-%@)";
|
||||||
|
"Story.ContextPrivacy.LabelContacts" = "Contacts";
|
||||||
|
"Story.ContextPrivacy.LabelOnlySelected_1" = "1 Person";
|
||||||
|
"Story.ContextPrivacy.LabelOnlySelected_any" = "%d People";
|
||||||
|
"Story.ContextPrivacy.LabelOnlyMe" = "Only Me";
|
||||||
|
"Story.ContextPrivacy.LabelEveryone" = "Everyone";
|
||||||
|
"Story.Context.Privacy" = "Who Can See";
|
||||||
|
"Story.Context.Edit" = "Edit Story";
|
||||||
|
"Story.Context.SaveToProfile" = "Save to Profile";
|
||||||
|
"Story.Context.RemoveFromProfile" = "Remove from Profile";
|
||||||
|
"Story.ToastRemovedFromProfileText" = "Story removed from your profile";
|
||||||
|
"Story.ToastSavedToProfileTitle" = "Story saved to your profile";
|
||||||
|
"Story.ToastSavedToProfileText" = "Saved stories can be viewed by others on your profile until you remove them.";
|
||||||
|
"Story.Context.SaveToGallery" = "Save to Gallery";
|
||||||
|
"Story.Context.CopyLink" = "Copy Link";
|
||||||
|
"Story.ToastLinkCopied" = "Link copied.";
|
||||||
|
"Story.Context.Share" = "Share";
|
||||||
|
"Story.Context.Report" = "Report";
|
||||||
|
|
||||||
|
"Story.Context.EmbeddedStickersValue_1" = "1 pack";
|
||||||
|
"Story.Context.EmbeddedStickersValue_any" = "%d packs";
|
||||||
|
"Story.Context.EmbeddedStickers" = "This story contains stickers from [%@]().";
|
||||||
|
"Story.Context.EmbeddedEmojiPack" = "This story contains\n#[%@]() emoji.";
|
||||||
|
"Story.Context.EmbeddedStickerPack" = "This story contains\n#[%@]() stickers.";
|
||||||
|
|
||||||
|
"Story.TooltipVideoHasNoSound" = "This video has no sound";
|
||||||
|
|
||||||
|
"Story.TooltipMessageScheduled" = "Message Scheduled";
|
||||||
|
"Story.TooltipMessageSent" = "Message Sent";
|
||||||
|
|
||||||
|
"Story.Camera.Photo" = "Photo";
|
||||||
|
"Story.Camera.Video" = "Video";
|
||||||
|
|
||||||
|
"Story.Camera.TooltipTakePhotos" = "Take photos or videos to share with all\nyour contacts or close friends at once.";
|
||||||
|
"Story.Camera.TooltipDisableDual" = "Tap here to disable\nthe selfie camera";
|
||||||
|
"Story.Camera.TooltipDraftSaved" = "Draft Saved";
|
||||||
|
|
||||||
|
"Story.Camera.SwipeUpToZoom" = "Swipe up to zoom";
|
||||||
|
"Story.Camera.SwipeLeftToLock" = "Swipe left to lock";
|
||||||
|
"Story.Camera.SwipeLeftRelease" = "Release to lock";
|
||||||
|
"Story.Camera.SwipeRightToFlip" = "Swipe right to flip";
|
||||||
|
|
||||||
|
"Story.Camera.AccessPlaceholderTitle" = "Allow Telegram to access your camera and microphone";
|
||||||
|
"Story.Camera.AccessPlaceholderText" = "This lets you share photos and record videos.";
|
||||||
|
"Story.Camera.AccessOpenSettings" = "Open Settings";
|
||||||
|
|
||||||
|
"Story.Editor.Next" = "Next";
|
||||||
|
"Story.Editor.Done" = "Done";
|
||||||
|
|
||||||
|
"Story.Editor.DraftDiscardMedia" = "Discard Media?";
|
||||||
|
"Story.Editor.DraftDiscardDraft" = "Discard Draft?";
|
||||||
|
"Story.Editor.DraftKeepMedia" = "Save Draft";
|
||||||
|
"Story.Editor.DraftKeepDraft" = "Keep Draft";
|
||||||
|
"Story.Editor.DraftDiscaedText" = "If you go back now, you will lose any changes that you've made.";
|
||||||
|
"Story.Editor.DraftDiscard" = "Discard";
|
||||||
|
|
||||||
|
"Story.Editor.ExpirationText" = "Choose how long the story will be visible.";
|
||||||
|
"Story.Editor.ExpirationValue_1" = "1 Hour";
|
||||||
|
"Story.Editor.ExpirationValue_any" = "%d Hours";
|
||||||
|
|
||||||
|
"Story.Editor.TooltipPremiumCustomExpiration" = "Subscribe to **Telegram Premium** to make your stories disappear %@.";
|
||||||
|
"Story.Editor.TooltipPremiumMore" = "More";
|
||||||
|
|
||||||
|
"Story.Editor.InputPlaceholderAddCaption" = "Add a caption...";
|
||||||
|
|
||||||
|
"Story.Editor.TooltipMuted" = "The story will have no sound";
|
||||||
|
"Story.Editor.TooltipUnmuted" = "The story will have sound";
|
||||||
|
|
||||||
|
"Story.Editor.PreparingVideo" = "Preparing Video...";
|
||||||
|
"Story.Editor.TooltipImageSavedToPhotos" = "Image saved to Photos.";
|
||||||
|
"Story.Editor.TooltipVideoSavedToPhotos" = "Video saved to Photos.";
|
||||||
|
|
||||||
|
"Story.Editor.Uploading" = "Uploading...";
|
||||||
|
|
||||||
|
"Story.Editor.Tool.Enhance" = "Enhance";
|
||||||
|
"Story.Editor.Tool.Brightness" = "Brightness";
|
||||||
|
"Story.Editor.Tool.Contrast" = "Contrast";
|
||||||
|
"Story.Editor.Tool.Saturation" = "Saturation";
|
||||||
|
"Story.Editor.Tool.Warmth" = "Warmth";
|
||||||
|
"Story.Editor.Tool.Fade" = "Fade";
|
||||||
|
"Story.Editor.Tool.Highlights" = "Highlights";
|
||||||
|
"Story.Editor.Tool.Shadows" = "Shadows";
|
||||||
|
"Story.Editor.Tool.Vignette" = "Vignette";
|
||||||
|
"Story.Editor.Tool.Grain" = "Grain";
|
||||||
|
|
||||||
|
"Story.Editor.Tint.Shadows" = "Shadows";
|
||||||
|
"Story.Editor.Tint.Highlights" = "Highlights";
|
||||||
|
|
||||||
|
"Story.Editor.Blur.Title" = "Blur";
|
||||||
|
"Story.Editor.Blur.Off" = "Off";
|
||||||
|
"Story.Editor.Blur.Radial" = "Radial";
|
||||||
|
"Story.Editor.Blur.Linear" = "Linear";
|
||||||
|
"Story.Editor.Blur.Portrait" = "Portrait";
|
||||||
|
|
||||||
|
"Story.Editor.Curves.All" = "All";
|
||||||
|
"Story.Editor.Curves.Red" = "Red";
|
||||||
|
"Story.Editor.Curves.Green" = "Green";
|
||||||
|
"Story.Editor.Curves.Blue" = "Blue";
|
||||||
|
|
||||||
|
@ -1991,7 +1991,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
self.displayedStoriesTooltip = true
|
self.displayedStoriesTooltip = true
|
||||||
|
|
||||||
let absoluteFrame = anchorView.convert(anchorRect, to: self.view)
|
let absoluteFrame = anchorView.convert(anchorRect, to: self.view)
|
||||||
//TODO:localize
|
|
||||||
|
|
||||||
let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle)
|
let itemList = orderedStorySubscriptions.items.prefix(3).map(\.peer.compactDisplayTitle)
|
||||||
var itemListString: String = itemList.joined(separator: ", ")
|
var itemListString: String = itemList.joined(separator: ", ")
|
||||||
@ -2003,7 +2002,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let text: String = "Tap above to view updates\nfrom \(itemListString)"
|
let text: String = self.presentationData.strings.ChatList_StoryFeedTooltip(itemListString).string
|
||||||
|
|
||||||
let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
|
let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
|
||||||
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||||
@ -2584,13 +2583,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
if premiumNeeded {
|
if premiumNeeded {
|
||||||
text = "Posting stories is currently available only\nto subscribers of [Telegram Premium]()."
|
text = self.presentationData.strings.StoryFeed_TooltipPremiumPosting
|
||||||
} else if reachedCountLimit {
|
} else if reachedCountLimit {
|
||||||
text = "You can't post more than **\(storiesCountLimit)** stories in **24 hours**."
|
let valueText = self.presentationData.strings.StoryFeed_TooltipStoryLimitValue(Int32(storiesCountLimit))
|
||||||
|
text = self.presentationData.strings.StoryFeed_TooltipStoryLimit(valueText).string
|
||||||
} else if hasActiveCall {
|
} else if hasActiveCall {
|
||||||
text = "You can't post stories during a call."
|
text = self.presentationData.strings.StoryFeed_TooltipPostingDuringCall
|
||||||
} else if hasActiveGroupCall {
|
} else if hasActiveGroupCall {
|
||||||
text = "You can't post stories during a voice chat."
|
text = self.presentationData.strings.StoryFeed_TooltipPostingDuringGroupCall
|
||||||
} else {
|
} else {
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
@ -2730,9 +2730,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
if peer.id == self.context.account.peerId {
|
if peer.id == self.context.account.peerId {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Add Story", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextAddStory, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
@ -2744,7 +2743,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Saved Stories", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextSavedStories, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
@ -2756,7 +2755,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Archived Stories", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextArchivedStories, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
@ -2768,7 +2767,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
} else {
|
} else {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenChat, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
@ -2780,7 +2779,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenProfile, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
@ -2804,7 +2803,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})))
|
})))
|
||||||
|
|
||||||
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers)
|
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers)
|
||||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify About Stories" : "Do Not Notify About Stories", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: isMuted ? self.presentationData.strings.StoryFeed_ContextNotifyOn : self.presentationData.strings.StoryFeed_ContextNotifyOff, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
@ -2825,7 +2824,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: "You will now get a notification whenever **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
|
], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOn(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -2839,7 +2838,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: "You will no longer receive a notification when **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
|
], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOff(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -2849,9 +2848,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
let hideText: String
|
let hideText: String
|
||||||
if self.location == .chatList(groupId: .archive) {
|
if self.location == .chatList(groupId: .archive) {
|
||||||
hideText = "Unhide Stories"
|
hideText = self.presentationData.strings.StoryFeed_ContextUnarchive
|
||||||
} else {
|
} else {
|
||||||
hideText = "Hide Stories"
|
hideText = self.presentationData.strings.StoryFeed_ContextArchive
|
||||||
}
|
}
|
||||||
let iconName = self.location == .chatList(groupId: .archive) ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"
|
let iconName = self.location == .chatList(groupId: .archive) ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"
|
||||||
items.append(.action(ContextMenuActionItem(text: hideText, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: hideText, icon: { theme in
|
||||||
@ -2871,9 +2870,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
undoValue = false
|
undoValue = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
if self.location != .chatList(groupId: .archive) {
|
if self.location != .chatList(groupId: .archive) {
|
||||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Archived Chats.", undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in
|
self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: self.presentationData.strings.StoryFeed_TooltipArchive(peer.compactDisplayTitle).string, undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in
|
||||||
if case .undo = action {
|
if case .undo = action {
|
||||||
if let self {
|
if let self {
|
||||||
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue)
|
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue)
|
||||||
@ -2882,30 +2880,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return false
|
return false
|
||||||
}), in: .current)
|
}), in: .current)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*guard let parentController = self.parent as? TabBarController, let contactsController = (self.navigationController as? TelegramRootControllerInterface)?.getContactsController(), let sourceFrame = parentController.frameForControllerTab(controller: contactsController) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
|
|
||||||
let tooltipController = TooltipScreen(
|
|
||||||
context: self.context,
|
|
||||||
account: self.context.account,
|
|
||||||
sharedContext: self.context.sharedContext,
|
|
||||||
text: .markdown(text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Contacts, not Chats."),
|
|
||||||
icon: .peer(peer: peer, isStory: true),
|
|
||||||
action: TooltipScreen.Action(
|
|
||||||
title: "Undo",
|
|
||||||
action: { [weak self] in
|
|
||||||
if let self {
|
|
||||||
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
location: .point(location, .bottom),
|
|
||||||
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
|
|
||||||
)
|
|
||||||
self.present(tooltipController, in: .window(.root))*/
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3328,8 +3302,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Archive_ContextSettings, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Archive Settings", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3341,7 +3314,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})))
|
})))
|
||||||
|
|
||||||
if !archiveChatList.items.isEmpty {
|
if !archiveChatList.items.isEmpty {
|
||||||
items.append(.action(ContextMenuActionItem(text: "How Does It Work?", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Archive_ContextInfo, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/Question"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/Question"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3359,7 +3332,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
self.push(ArchiveInfoScreen(context: self.context, settings: settings))
|
self.push(ArchiveInfoScreen(context: self.context, settings: settings))
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Select Chats", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_ContextSelectChats, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
@ -2156,13 +2156,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if textString.length == 0, case let .groupReference(data) = item.content, let storyState = data.storyState, storyState.stats.totalCount != 0 {
|
if textString.length == 0, case let .groupReference(data) = item.content, let storyState = data.storyState, storyState.stats.totalCount != 0 {
|
||||||
//TODO:localize
|
let storyText: String = item.presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyState.stats.totalCount))
|
||||||
let storyText: String
|
|
||||||
if storyState.stats.totalCount == 1 {
|
|
||||||
storyText = "1 story"
|
|
||||||
} else {
|
|
||||||
storyText = "\(storyState.stats.totalCount) stories"
|
|
||||||
}
|
|
||||||
textString.append(NSAttributedString(string: storyText, font: textFont, textColor: theme.messageTextColor))
|
textString.append(NSAttributedString(string: storyText, font: textFont, textColor: theme.messageTextColor))
|
||||||
}
|
}
|
||||||
attributedText = textString
|
attributedText = textString
|
||||||
|
@ -303,8 +303,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
|||||||
messageText = strings.Conversation_StoryMentionTextOutgoing(peer.compactDisplayTitle).string
|
messageText = strings.Conversation_StoryMentionTextOutgoing(peer.compactDisplayTitle).string
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO:localize
|
messageText = strings.Notification_Story
|
||||||
messageText = "Story"
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -31,8 +31,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
|||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
if isStories {
|
if isStories {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: strings.StoryFeed_ContextOpenProfile, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { c, _ in
|
}, action: { c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
@ -50,7 +49,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
|||||||
|
|
||||||
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers)
|
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers)
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.StoryFeed_ContextNotifyOn : strings.StoryFeed_ContextNotifyOff, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
@ -69,7 +68,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
|||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: "You will now get a notification whenever **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
|
], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOn(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -83,7 +82,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
|||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: "You will no longer receive a notification when **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
|
], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOff(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -92,36 +91,12 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Move to Chats", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strings.StoryFeed_ContextUnarchive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToChats"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToChats"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
context.engine.peers.updatePeerStoriesHidden(id: peerId, isHidden: false)
|
context.engine.peers.updatePeerStoriesHidden(id: peerId, isHidden: false)
|
||||||
|
|
||||||
guard let parentController = contactsController?.parent as? TabBarController, let chatsController = (contactsController?.navigationController as? TelegramRootControllerInterface)?.getChatsController(), let sourceFrame = parentController.frameForControllerTab(controller: chatsController) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
|
|
||||||
let tooltipController = TooltipScreen(
|
|
||||||
context: context,
|
|
||||||
account: context.account,
|
|
||||||
sharedContext: context.sharedContext,
|
|
||||||
text: .markdown(text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Chats, not Contacts."),
|
|
||||||
icon: .peer(peer: peer, isStory: true),
|
|
||||||
action: TooltipScreen.Action(
|
|
||||||
title: "Undo",
|
|
||||||
action: {
|
|
||||||
context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
location: .point(location, .bottom),
|
|
||||||
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
|
|
||||||
)
|
|
||||||
contactsController?.present(tooltipController, in: .window(.root))
|
|
||||||
}
|
|
||||||
})))
|
})))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
@ -207,20 +207,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
storyStats = (storyData.count, storyData.unseenCount, storyData.hasUnseenCloseFriends)
|
storyStats = (storyData.count, storyData.unseenCount, storyData.hasUnseenCloseFriends)
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
//TODO:localize
|
text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count))
|
||||||
if storyData.unseenCount != 0 {
|
|
||||||
if storyData.unseenCount == 1 {
|
|
||||||
text = "1 unseen story"
|
|
||||||
} else {
|
|
||||||
text = "\(storyData.unseenCount) unseen stories"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if storyData.count == 1 {
|
|
||||||
text = "1 story"
|
|
||||||
} else {
|
|
||||||
text = "\(storyData.count) stories"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,8 +391,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
if addHeader {
|
if addHeader {
|
||||||
//TODO:localize
|
commonHeader = ChatListSearchItemHeader(type: .text(strings.Contacts_SortedByPresence.uppercased(), AnyHashable(1)), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||||
commonHeader = ChatListSearchItemHeader(type: .text("SORTED BY LAST SEEN TIME", AnyHashable(1)), theme: theme, strings: strings, actionTitle: nil, action: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch presentation {
|
switch presentation {
|
||||||
@ -529,15 +515,15 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||||||
|
|
||||||
|
|
||||||
if let storySubscriptions {
|
if let storySubscriptions {
|
||||||
var index: Int = 0
|
let _ = storySubscriptions
|
||||||
|
/*var index: Int = 0
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings)
|
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings)
|
||||||
|
|
||||||
for item in storySubscriptions.items {
|
for item in storySubscriptions.items {
|
||||||
entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends)))
|
entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends)))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
var index: Int = 0
|
var index: Int = 0
|
||||||
|
@ -300,12 +300,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
let tabsNode: ASDisplayNode? = nil
|
let tabsNode: ASDisplayNode? = nil
|
||||||
let tabsNodeIsSearch = false
|
let tabsNodeIsSearch = false
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let primaryContent = ChatListHeaderComponent.Content(
|
let primaryContent = ChatListHeaderComponent.Content(
|
||||||
title: "Contacts",
|
title: self.presentationData.strings.Contacts_Title,
|
||||||
navigationBackTitle: nil,
|
navigationBackTitle: nil,
|
||||||
titleComponent: nil,
|
titleComponent: nil,
|
||||||
chatListTitle: NetworkStatusTitle(text: "Contacts", activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil),
|
chatListTitle: NetworkStatusTitle(text: self.presentationData.strings.Contacts_Title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil),
|
||||||
leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent(
|
leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent(
|
||||||
content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false),
|
content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false),
|
||||||
pressed: { [weak self] sourceView in
|
pressed: { [weak self] sourceView in
|
||||||
|
@ -192,7 +192,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
} else if let (fetchResult, index) = self.currentAssetState {
|
} else if let (fetchResult, index) = self.currentAssetState {
|
||||||
let asset = fetchResult.object(at: index)
|
let asset = fetchResult.object(at: index)
|
||||||
if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||||
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
let tag = Month(localTimestamp: Int32(exactly: floor(localTimestamp)) ?? 0).packedValue
|
||||||
self._cachedTag = tag
|
self._cachedTag = tag
|
||||||
return tag
|
return tag
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,8 +95,8 @@ struct Month: Equatable {
|
|||||||
var timeinfo: tm = tm()
|
var timeinfo: tm = tm()
|
||||||
gmtime_r(&time, &timeinfo)
|
gmtime_r(&time, &timeinfo)
|
||||||
|
|
||||||
let year = UInt32(timeinfo.tm_year)
|
let year = UInt32(max(timeinfo.tm_year, 0))
|
||||||
let month = UInt32(timeinfo.tm_mon)
|
let month = UInt32(max(timeinfo.tm_mon, 0))
|
||||||
|
|
||||||
self.packedValue = Int32(bitPattern: year | (month << 16))
|
self.packedValue = Int32(bitPattern: year | (month << 16))
|
||||||
}
|
}
|
||||||
@ -1445,8 +1445,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
case .wallpaper:
|
case .wallpaper:
|
||||||
self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
|
self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
|
||||||
case .addImage:
|
case .addImage:
|
||||||
//TODO:localize
|
self.titleView.title = presentationData.strings.MediaPicker_AddImage
|
||||||
self.titleView.title = "Add Image"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1265,6 +1265,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
|||||||
transition: indicatorTransition,
|
transition: indicatorTransition,
|
||||||
component: AnyComponent(StorySetIndicatorComponent(
|
component: AnyComponent(StorySetIndicatorComponent(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
|
strings: self.context.sharedContext.currentPresentationData.with({ $0 }).strings,
|
||||||
peer: storyParams.peer,
|
peer: storyParams.peer,
|
||||||
items: storyParams.items,
|
items: storyParams.items,
|
||||||
hasUnseen: storyParams.hasUnseen,
|
hasUnseen: storyParams.hasUnseen,
|
||||||
|
@ -939,7 +939,6 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
//TODO:localize
|
|
||||||
availableItems[.stories] = DemoPagerComponent.Item(
|
availableItems[.stories] = DemoPagerComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
id: PremiumDemoScreen.Subject.stories,
|
id: PremiumDemoScreen.Subject.stories,
|
||||||
@ -951,8 +950,8 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
videoFile: configuration.videos["voice_to_text"],
|
videoFile: configuration.videos["voice_to_text"],
|
||||||
decoration: .badgeStars
|
decoration: .badgeStars
|
||||||
)),
|
)),
|
||||||
title: "Story Posting",
|
title: strings.Premium_Stories,
|
||||||
text: "Be one of the first to share your stories with your contacts or an unlimited audience.",
|
text: strings.Premium_StoriesInfo,
|
||||||
textColor: textColor
|
textColor: textColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1049,8 +1048,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
case .translation:
|
case .translation:
|
||||||
buttonText = strings.Premium_Translation_Proceed
|
buttonText = strings.Premium_Translation_Proceed
|
||||||
case .stories:
|
case .stories:
|
||||||
//TODO:localize
|
buttonText = strings.Premium_Stories_Proceed
|
||||||
buttonText = "Unlock Story Posting"
|
|
||||||
buttonAnimationName = "premium_unlock"
|
buttonAnimationName = "premium_unlock"
|
||||||
default:
|
default:
|
||||||
buttonText = strings.Common_OK
|
buttonText = strings.Common_OK
|
||||||
|
@ -395,8 +395,7 @@ enum PremiumPerk: CaseIterable {
|
|||||||
case .translation:
|
case .translation:
|
||||||
return strings.Premium_Translation
|
return strings.Premium_Translation
|
||||||
case .stories:
|
case .stories:
|
||||||
//TODO:localize
|
return strings.Premium_Stories
|
||||||
return "Story Posting"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,34 +85,33 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! ArchiveSettingsControllerArguments
|
let arguments = arguments as! ArchiveSettingsControllerArguments
|
||||||
//TODO:localize
|
|
||||||
switch self {
|
switch self {
|
||||||
case .unmutedHeader:
|
case .unmutedHeader:
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "UNMUTED CHATS", sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnmutedChatsHeader, sectionId: self.section)
|
||||||
case let .unmutedValue(value):
|
case let .unmutedValue(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateUnmuted(value)
|
arguments.updateUnmuted(value)
|
||||||
})
|
})
|
||||||
case .unmutedFooter:
|
case .unmutedFooter:
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("Keep archived chats in the Archive even if they are unmuted and get a new message."), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.ArchiveSettings_UnmutedChatsFooter), sectionId: self.section)
|
||||||
case .foldersHeader:
|
case .foldersHeader:
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "CHATS FROM FOLDERS", sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_FolderChatsHeader, sectionId: self.section)
|
||||||
case let .foldersValue(value):
|
case let .foldersValue(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateFolders(value)
|
arguments.updateFolders(value)
|
||||||
})
|
})
|
||||||
case .foldersFooter:
|
case .foldersFooter:
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("Keep archived chats from folders in the Archive even if they are unmuted and get a new message."), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.ArchiveSettings_FolderChatsFooter), sectionId: self.section)
|
||||||
case .unknownHeader:
|
case .unknownHeader:
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "NEW CHATS FROM UNKNOWN USERS", sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnknownChatsHeader, sectionId: self.section)
|
||||||
case let .unknownValue(isOn, isLocked):
|
case let .unknownValue(isOn, isLocked):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Always Keep Archived", value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updateUnknown(value)
|
arguments.updateUnknown(value)
|
||||||
}, activatedWhileDisabled: {
|
}, activatedWhileDisabled: {
|
||||||
arguments.updateUnknown(nil)
|
arguments.updateUnknown(nil)
|
||||||
})
|
})
|
||||||
case .unknownFooter:
|
case .unknownFooter:
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("Automatically archive and mute new private chats, groups and channels from non-contacts."), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.ArchiveSettings_UnknownChatsFooter), sectionId: self.section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +162,8 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
|
|||||||
if let value {
|
if let value {
|
||||||
let _ = context.engine.privacy.updateAccountAutoArchiveChats(value: value).start()
|
let _ = context.engine.privacy.updateAccountAutoArchiveChats(value: value).start()
|
||||||
} else {
|
} else {
|
||||||
presentUndoImpl?(.premiumPaywall(title: nil, text: "This setting is available only to the subscribers of [Telegram Premium]().", customUndoText: nil, timeout: nil, linkAction: { _ in
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
presentUndoImpl?(.premiumPaywall(title: nil, text: presentationData.strings.ArchiveSettings_TooltipPremiumRequired, customUndoText: nil, timeout: nil, linkAction: { _ in
|
||||||
presentPremiumImpl?()
|
presentPremiumImpl?()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -181,8 +181,7 @@ public func archiveSettingsController(context: AccountContext) -> ViewController
|
|||||||
let isPremium = accountPeer?.isPremium ?? false
|
let isPremium = accountPeer?.isPremium ?? false
|
||||||
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
|
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled
|
||||||
|
|
||||||
//TODO:localize
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ArchiveSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Archive Settings"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archiveSettingsControllerEntries(
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archiveSettingsControllerEntries(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
|
@ -207,8 +207,7 @@ private func stringForAutomaticDownloadPeers(strings: PresentationStrings, decim
|
|||||||
if peers.contacts && peers.otherPrivate {
|
if peers.contacts && peers.otherPrivate {
|
||||||
return strings.AutoDownloadSettings_OnForAll
|
return strings.AutoDownloadSettings_OnForAll
|
||||||
} else if peers.contacts {
|
} else if peers.contacts {
|
||||||
//TODO:localize
|
return strings.AutoDownloadSettings_OnForContacts
|
||||||
return "On for contacts"
|
|
||||||
} else {
|
} else {
|
||||||
return strings.AutoDownloadSettings_OffForAll
|
return strings.AutoDownloadSettings_OffForAll
|
||||||
}
|
}
|
||||||
|
@ -274,16 +274,14 @@ private func autodownloadMediaCategoryControllerEntries(presentationData: Presen
|
|||||||
downloadTitle = presentationData.strings.AutoDownloadSettings_AutodownloadFiles
|
downloadTitle = presentationData.strings.AutoDownloadSettings_AutodownloadFiles
|
||||||
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
|
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
|
||||||
case .story:
|
case .story:
|
||||||
//TODO:localize
|
downloadTitle = presentationData.strings.AutoDownloadSettings_StoriesSectionHeader
|
||||||
downloadTitle = "AUTO-DOWNLOAD STORIES"
|
|
||||||
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
|
sizeTitle = presentationData.strings.AutoDownloadSettings_MaxFileSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .story = category {
|
if case .story = category {
|
||||||
entries.append(.peerContacts(presentationData.theme, presentationData.strings.AutoDownloadSettings_Contacts, peers.contacts))
|
entries.append(.peerContacts(presentationData.theme, presentationData.strings.AutoDownloadSettings_Contacts, peers.contacts))
|
||||||
//TODO:localize
|
|
||||||
if peers.contacts {
|
if peers.contacts {
|
||||||
entries.append(.peerOtherPrivate(presentationData.theme, "Hidden Contacts", peers.otherPrivate))
|
entries.append(.peerOtherPrivate(presentationData.theme, presentationData.strings.AutoDownloadSettings_StoriesArchivedContacts, peers.otherPrivate))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
entries.append(.peerHeader(presentationData.theme, downloadTitle))
|
entries.append(.peerHeader(presentationData.theme, downloadTitle))
|
||||||
@ -470,8 +468,7 @@ func autodownloadMediaCategoryController(context: AccountContext, connectionType
|
|||||||
case .file:
|
case .file:
|
||||||
title = presentationData.strings.AutoDownloadSettings_DocumentsTitle
|
title = presentationData.strings.AutoDownloadSettings_DocumentsTitle
|
||||||
case .story:
|
case .story:
|
||||||
//TODO:localize
|
title = presentationData.strings.AutoDownloadSettings_StoriesTitle
|
||||||
title = "Stories"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||||
|
@ -552,18 +552,17 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
|
|||||||
entries.append(.groupChats(presentationData.theme, presentationData.strings.Notifications_GroupChats, !exceptions.groups.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.groups.peerIds.count)) : "", globalSettings.groupChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
|
entries.append(.groupChats(presentationData.theme, presentationData.strings.Notifications_GroupChats, !exceptions.groups.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.groups.peerIds.count)) : "", globalSettings.groupChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
|
||||||
entries.append(.channels(presentationData.theme, presentationData.strings.Notifications_Channels, !exceptions.channels.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.channels.peerIds.count)) : "", globalSettings.channels.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
|
entries.append(.channels(presentationData.theme, presentationData.strings.Notifications_Channels, !exceptions.channels.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.channels.peerIds.count)) : "", globalSettings.channels.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let storiesValue: String
|
let storiesValue: String
|
||||||
switch globalSettings.privateChats.storySettings.mute {
|
switch globalSettings.privateChats.storySettings.mute {
|
||||||
case .default:
|
case .default:
|
||||||
storiesValue = "Top 5"
|
storiesValue = presentationData.strings.Notifications_TopChats
|
||||||
case .muted:
|
case .muted:
|
||||||
storiesValue = presentationData.strings.Notifications_Off
|
storiesValue = presentationData.strings.Notifications_Off
|
||||||
case .unmuted:
|
case .unmuted:
|
||||||
storiesValue = presentationData.strings.Notifications_On
|
storiesValue = presentationData.strings.Notifications_On
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.stories(presentationData.theme, "Stories", !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue))
|
entries.append(.stories(presentationData.theme, presentationData.strings.Notifications_Stories, !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue))
|
||||||
|
|
||||||
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
|
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
|
||||||
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
|
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
|
||||||
|
@ -345,17 +345,16 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
|||||||
importantEnabled = true
|
importantEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
entries.append(.enable(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowAll, allEnabled))
|
||||||
entries.append(.enable(presentationData.theme, "Show All Notifications", allEnabled))
|
|
||||||
if !allEnabled {
|
if !allEnabled {
|
||||||
entries.append(.enableImportant(presentationData.theme, "Show Important Notifications", importantEnabled))
|
entries.append(.enableImportant(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportant, importantEnabled))
|
||||||
entries.append(.importantInfo(presentationData.theme, "Always on for top 5 contacts."))
|
entries.append(.importantInfo(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportantFooter))
|
||||||
}
|
}
|
||||||
|
|
||||||
if notificationSettings.enabled || !notificationExceptions.isEmpty {
|
if notificationSettings.enabled || !notificationExceptions.isEmpty {
|
||||||
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased()))
|
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased()))
|
||||||
|
|
||||||
entries.append(.previews(presentationData.theme, "Display Author Name", notificationSettings.storySettings.hideSender != .hide))
|
entries.append(.previews(presentationData.theme, presentationData.strings.NotificationSettings_Stories_DisplayAuthorName, notificationSettings.storySettings.hideSender != .hide))
|
||||||
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound)))
|
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -413,8 +412,7 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
|||||||
var title: String = ""
|
var title: String = ""
|
||||||
|
|
||||||
if automaticSet.contains(value.peer.id) {
|
if automaticSet.contains(value.peer.id) {
|
||||||
//TODO:localize
|
title = presentationData.strings.NotificationSettings_Stories_AutomaticValue(presentationData.strings.Notification_Exceptions_AlwaysOn).string
|
||||||
title = "\(presentationData.strings.Notification_Exceptions_AlwaysOn) (automatic)"
|
|
||||||
canRemove = false
|
canRemove = false
|
||||||
} else {
|
} else {
|
||||||
if case .stories = category {
|
if case .stories = category {
|
||||||
@ -443,11 +441,10 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
|||||||
if !title.isEmpty {
|
if !title.isEmpty {
|
||||||
title += ", "
|
title += ", "
|
||||||
}
|
}
|
||||||
//TODO:localize
|
|
||||||
if case .show = value.settings.storySettings.hideSender {
|
if case .show = value.settings.storySettings.hideSender {
|
||||||
title += "Show Name"
|
title += presentationData.strings.NotificationSettings_Stories_CompactShowName
|
||||||
} else {
|
} else {
|
||||||
title += "Hide Name"
|
title += presentationData.strings.NotificationSettings_Stories_CompactHideName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1068,8 +1065,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
|||||||
case .channel:
|
case .channel:
|
||||||
title = presentationData.strings.Notifications_ChannelsTitle
|
title = presentationData.strings.Notifications_ChannelsTitle
|
||||||
case .stories:
|
case .stories:
|
||||||
//TODO:localize
|
title = presentationData.strings.Notifications_StoriesTitle
|
||||||
title = "Stories"
|
|
||||||
}
|
}
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem)
|
||||||
|
@ -354,12 +354,11 @@ private func storiesSearchableItems(context: AccountContext) -> [SettingsSearcha
|
|||||||
|
|
||||||
var result: [SettingsSearchableItem] = []
|
var result: [SettingsSearchableItem] = []
|
||||||
|
|
||||||
//TODO:localize
|
result.append(SettingsSearchableItem(id: .stories(0), title: strings.Settings_MyStories, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
|
||||||
result.append(SettingsSearchableItem(id: .stories(0), title: "My Stories", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
|
|
||||||
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .saved))
|
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .saved))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
result.append(SettingsSearchableItem(id: .stories(1), title: "Stories Archive", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
|
result.append(SettingsSearchableItem(id: .stories(1), title: strings.Settings_StoriesArchive, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
|
||||||
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .archive))
|
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .archive))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -396,8 +396,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
|
|||||||
case let .invoice(text):
|
case let .invoice(text):
|
||||||
return (NSAttributedString(string: text), true)
|
return (NSAttributedString(string: text), true)
|
||||||
case .story:
|
case .story:
|
||||||
//TODO:localize
|
return (NSAttributedString(string: strings.Message_Story), true)
|
||||||
return (NSAttributedString(string: "Story"), true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,13 +934,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
case .none:
|
case .none:
|
||||||
hintText = " "
|
hintText = " "
|
||||||
case .zoom:
|
case .zoom:
|
||||||
hintText = "Swipe up to zoom"
|
hintText = environment.strings.Story_Camera_SwipeUpToZoom
|
||||||
case .lock:
|
case .lock:
|
||||||
hintText = "Swipe left to lock"
|
hintText = environment.strings.Story_Camera_SwipeLeftToLock
|
||||||
case .releaseLock:
|
case .releaseLock:
|
||||||
hintText = "Release to lock"
|
hintText = environment.strings.Story_Camera_SwipeLeftRelease
|
||||||
case .flip:
|
case .flip:
|
||||||
hintText = "Swipe right to flip"
|
hintText = environment.strings.Story_Camera_SwipeRightToFlip
|
||||||
}
|
}
|
||||||
if let hintText {
|
if let hintText {
|
||||||
let hintLabel = hintLabel.update(
|
let hintLabel = hintLabel.update(
|
||||||
@ -967,6 +967,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let modeControl = modeControl.update(
|
let modeControl = modeControl.update(
|
||||||
component: ModeComponent(
|
component: ModeComponent(
|
||||||
isTablet: isTablet,
|
isTablet: isTablet,
|
||||||
|
strings: environment.strings,
|
||||||
availableModes: [.photo, .video],
|
availableModes: [.photo, .video],
|
||||||
currentMode: component.cameraState.mode,
|
currentMode: component.cameraState.mode,
|
||||||
updatedMode: { [weak state] mode in
|
updatedMode: { [weak state] mode in
|
||||||
@ -1948,7 +1949,7 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 29.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 29.0), size: CGSize())
|
||||||
|
|
||||||
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Draft Saved"), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipDraftSaved), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||||
return .ignore
|
return .ignore
|
||||||
})
|
})
|
||||||
self.controller?.present(controller, in: .current)
|
self.controller?.present(controller, in: .current)
|
||||||
@ -1963,7 +1964,7 @@ public class CameraScreen: ViewController {
|
|||||||
let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
|
|
||||||
let accountManager = self.context.sharedContext.accountManager
|
let accountManager = self.context.sharedContext.accountManager
|
||||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Tap here to disable\nthe selfie camera"), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
|
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipDisableDual), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
|
||||||
if containerFrame.contains(point) {
|
if containerFrame.contains(point) {
|
||||||
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
|
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
|
||||||
return .dismiss(consume: true)
|
return .dismiss(consume: true)
|
||||||
@ -1983,7 +1984,7 @@ public class CameraScreen: ViewController {
|
|||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
|
||||||
|
|
||||||
let accountManager = self.context.sharedContext.accountManager
|
let accountManager = self.context.sharedContext.accountManager
|
||||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in
|
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.Story_Camera_TooltipTakePhotos), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in
|
||||||
if containerFrame.contains(point) {
|
if containerFrame.contains(point) {
|
||||||
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
|
@ -3,14 +3,15 @@ import UIKit
|
|||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
extension CameraMode {
|
extension CameraMode {
|
||||||
var title: String {
|
func title(strings: PresentationStrings) -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .photo:
|
case .photo:
|
||||||
return "Photo"
|
return strings.Story_Camera_Photo
|
||||||
case .video:
|
case .video:
|
||||||
return "Video"
|
return strings.Story_Camera_Video
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,6 +20,7 @@ private let buttonSize = CGSize(width: 55.0, height: 44.0)
|
|||||||
|
|
||||||
final class ModeComponent: Component {
|
final class ModeComponent: Component {
|
||||||
let isTablet: Bool
|
let isTablet: Bool
|
||||||
|
let strings: PresentationStrings
|
||||||
let availableModes: [CameraMode]
|
let availableModes: [CameraMode]
|
||||||
let currentMode: CameraMode
|
let currentMode: CameraMode
|
||||||
let updatedMode: (CameraMode) -> Void
|
let updatedMode: (CameraMode) -> Void
|
||||||
@ -26,12 +28,14 @@ final class ModeComponent: Component {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
isTablet: Bool,
|
isTablet: Bool,
|
||||||
|
strings: PresentationStrings,
|
||||||
availableModes: [CameraMode],
|
availableModes: [CameraMode],
|
||||||
currentMode: CameraMode,
|
currentMode: CameraMode,
|
||||||
updatedMode: @escaping (CameraMode) -> Void,
|
updatedMode: @escaping (CameraMode) -> Void,
|
||||||
tag: AnyObject?
|
tag: AnyObject?
|
||||||
) {
|
) {
|
||||||
self.isTablet = isTablet
|
self.isTablet = isTablet
|
||||||
|
self.strings = strings
|
||||||
self.availableModes = availableModes
|
self.availableModes = availableModes
|
||||||
self.currentMode = currentMode
|
self.currentMode = currentMode
|
||||||
self.updatedMode = updatedMode
|
self.updatedMode = updatedMode
|
||||||
@ -42,6 +46,9 @@ final class ModeComponent: Component {
|
|||||||
if lhs.isTablet != rhs.isTablet {
|
if lhs.isTablet != rhs.isTablet {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.availableModes != rhs.availableModes {
|
if lhs.availableModes != rhs.availableModes {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -145,7 +152,7 @@ final class ModeComponent: Component {
|
|||||||
updatedMode(mode)
|
updatedMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.update(value: mode.title, selected: mode == component.currentMode)
|
itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode)
|
||||||
itemView.bounds = CGRect(origin: .zero, size: itemFrame.size)
|
itemView.bounds = CGRect(origin: .zero, size: itemFrame.size)
|
||||||
|
|
||||||
if isTablet {
|
if isTablet {
|
||||||
|
@ -55,9 +55,6 @@ final class PlaceholderComponent: Component {
|
|||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.backgroundColor = UIColor(rgb: 0x1c1c1e)
|
self.backgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||||
// if #available(iOS 13.0, *) {
|
|
||||||
// self.layer.cornerCurve = .continuous
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -71,9 +68,10 @@ final class PlaceholderComponent: Component {
|
|||||||
let sideInset: CGFloat = 36.0
|
let sideInset: CGFloat = 36.0
|
||||||
let animationHeight: CGFloat = 120.0
|
let animationHeight: CGFloat = 120.0
|
||||||
|
|
||||||
let title: String = "Allow Telegram to access your camera and microphone"
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let text: String = "This lets you share photos and record videos."
|
let title = presentationData.strings.Story_Camera_AccessPlaceholderTitle
|
||||||
let buttonTitle: String = "Open Settings"
|
let text = presentationData.strings.Story_Camera_AccessPlaceholderText
|
||||||
|
let buttonTitle = presentationData.strings.Story_Camera_AccessOpenSettings
|
||||||
|
|
||||||
let animationSize = self.animation.update(
|
let animationSize = self.animation.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
|
@ -283,7 +283,6 @@ public final class ChatListNavigationBar: Component {
|
|||||||
placeholder = component.strings.Common_Search
|
placeholder = component.strings.Common_Search
|
||||||
compactPlaceholder = component.strings.Common_Search
|
compactPlaceholder = component.strings.Common_Search
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
searchContentNode = NavigationBarSearchContentNode(
|
searchContentNode = NavigationBarSearchContentNode(
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
placeholder: placeholder,
|
placeholder: placeholder,
|
||||||
|
@ -309,6 +309,25 @@ public final class MediaEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func replaceSource(_ image: UIImage, additionalImage: UIImage?, time: CMTime) {
|
||||||
|
func fixImageOrientation(_ image: UIImage) -> UIImage {
|
||||||
|
UIGraphicsBeginImageContext(image.size)
|
||||||
|
image.draw(at: .zero)
|
||||||
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return newImage ?? image
|
||||||
|
}
|
||||||
|
let image = fixImageOrientation(image)
|
||||||
|
|
||||||
|
guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice, let texture = loadTexture(image: image, device: device) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let additionalImage = additionalImage.flatMap { fixImageOrientation($0) }
|
||||||
|
let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) }
|
||||||
|
self.renderer.consumeTexture(texture, additionalTexture: additionalTexture, time: time, render: true)
|
||||||
|
}
|
||||||
|
|
||||||
private var volumeFade: SwiftSignalKit.Timer?
|
private var volumeFade: SwiftSignalKit.Timer?
|
||||||
private func setupSource() {
|
private func setupSource() {
|
||||||
guard let renderTarget = self.previewView else {
|
guard let renderTarget = self.previewView else {
|
||||||
@ -633,6 +652,23 @@ public final class MediaEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func seek(_ position: Double, completion: @escaping () -> Void) {
|
||||||
|
guard let player = self.player else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.pause()
|
||||||
|
self.additionalPlayer?.pause()
|
||||||
|
|
||||||
|
let targetPosition = CMTime(seconds: position, preferredTimescale: CMTimeScale(60.0))
|
||||||
|
player.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: { _ in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.additionalPlayer?.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
public var isPlaying: Bool {
|
public var isPlaying: Bool {
|
||||||
return (self.player?.rate ?? 0.0) > 0.0
|
return (self.player?.rate ?? 0.0) > 0.0
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,9 @@ final class MediaEditorRenderer: TextureConsumer {
|
|||||||
private var textureCache: CVMetalTextureCache?
|
private var textureCache: CVMetalTextureCache?
|
||||||
|
|
||||||
private var currentTexture: MTLTexture?
|
private var currentTexture: MTLTexture?
|
||||||
|
private var currentAdditionalTexture: MTLTexture?
|
||||||
|
private var currentTime: CMTime = .zero
|
||||||
|
|
||||||
private var currentPixelBuffer: VideoPixelBuffer?
|
private var currentPixelBuffer: VideoPixelBuffer?
|
||||||
private var currentAdditionalPixelBuffer: VideoPixelBuffer?
|
private var currentAdditionalPixelBuffer: VideoPixelBuffer?
|
||||||
|
|
||||||
@ -174,8 +177,8 @@ final class MediaEditorRenderer: TextureConsumer {
|
|||||||
self.renderPasses.forEach { $0.setup(device: device, library: library) }
|
self.renderPasses.forEach { $0.setup(device: device, library: library) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var displayEnabled = true
|
||||||
var renderPassedEnabled = true
|
var renderPassedEnabled = true
|
||||||
|
|
||||||
var needsDisplay = false
|
var needsDisplay = false
|
||||||
|
|
||||||
func renderFrame() {
|
func renderFrame() {
|
||||||
@ -200,7 +203,13 @@ final class MediaEditorRenderer: TextureConsumer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var texture: MTLTexture
|
var texture: MTLTexture
|
||||||
if let currentTexture = self.currentTexture {
|
if let currentAdditionalTexture = self.currentAdditionalTexture, let currentTexture = self.currentTexture {
|
||||||
|
if let result = self.videoFinishPass.process(input: currentTexture, secondInput: currentAdditionalTexture, timestamp: self.currentTime, device: device, commandBuffer: commandBuffer) {
|
||||||
|
texture = result
|
||||||
|
} else {
|
||||||
|
texture = currentTexture
|
||||||
|
}
|
||||||
|
} else if let currentTexture = self.currentTexture {
|
||||||
texture = currentTexture
|
texture = currentTexture
|
||||||
} else if let currentPixelBuffer = self.currentPixelBuffer, let currentAdditionalPixelBuffer = self.currentAdditionalPixelBuffer, let videoTexture = self.videoInputPass.processPixelBuffer(currentPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer), let additionalVideoTexture = self.additionalVideoInputPass.processPixelBuffer(currentAdditionalPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer) {
|
} else if let currentPixelBuffer = self.currentPixelBuffer, let currentAdditionalPixelBuffer = self.currentAdditionalPixelBuffer, let videoTexture = self.videoInputPass.processPixelBuffer(currentPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer), let additionalVideoTexture = self.additionalVideoInputPass.processPixelBuffer(currentAdditionalPixelBuffer, textureCache: textureCache, device: device, commandBuffer: commandBuffer) {
|
||||||
if let result = self.videoFinishPass.process(input: videoTexture, secondInput: additionalVideoTexture, timestamp: currentPixelBuffer.timestamp, device: device, commandBuffer: commandBuffer) {
|
if let result = self.videoFinishPass.process(input: videoTexture, secondInput: additionalVideoTexture, timestamp: currentPixelBuffer.timestamp, device: device, commandBuffer: commandBuffer) {
|
||||||
@ -237,7 +246,7 @@ final class MediaEditorRenderer: TextureConsumer {
|
|||||||
}
|
}
|
||||||
commandBuffer.commit()
|
commandBuffer.commit()
|
||||||
|
|
||||||
if let renderTarget = self.renderTarget {
|
if let renderTarget = self.renderTarget, self.displayEnabled {
|
||||||
if self.needsDisplay {
|
if self.needsDisplay {
|
||||||
self.didRenderFrame()
|
self.didRenderFrame()
|
||||||
} else {
|
} else {
|
||||||
@ -299,6 +308,19 @@ final class MediaEditorRenderer: TextureConsumer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func consumeTexture(_ texture: MTLTexture, additionalTexture: MTLTexture?, time: CMTime, render: Bool) {
|
||||||
|
if render {
|
||||||
|
self.willRenderFrame()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.currentTexture = texture
|
||||||
|
self.currentAdditionalTexture = additionalTexture
|
||||||
|
self.currentTime = time
|
||||||
|
if render {
|
||||||
|
self.renderFrame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var previousPresentationTimestamp: CMTime?
|
var previousPresentationTimestamp: CMTime?
|
||||||
func consumeVideoPixelBuffer(pixelBuffer: VideoPixelBuffer, additionalPixelBuffer: VideoPixelBuffer?, render: Bool) {
|
func consumeVideoPixelBuffer(pixelBuffer: VideoPixelBuffer, additionalPixelBuffer: VideoPixelBuffer?, render: Bool) {
|
||||||
self.willRenderFrame()
|
self.willRenderFrame()
|
||||||
|
@ -4,6 +4,7 @@ import Display
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
private final class BlurModeComponent: Component {
|
private final class BlurModeComponent: Component {
|
||||||
typealias EnvironmentType = Empty
|
typealias EnvironmentType = Empty
|
||||||
@ -115,17 +116,20 @@ private final class BlurModeComponent: Component {
|
|||||||
final class BlurComponent: Component {
|
final class BlurComponent: Component {
|
||||||
typealias EnvironmentType = Empty
|
typealias EnvironmentType = Empty
|
||||||
|
|
||||||
|
let strings: PresentationStrings
|
||||||
let value: BlurValue
|
let value: BlurValue
|
||||||
let hasPortrait: Bool
|
let hasPortrait: Bool
|
||||||
let valueUpdated: (BlurValue) -> Void
|
let valueUpdated: (BlurValue) -> Void
|
||||||
let isTrackingUpdated: (Bool) -> Void
|
let isTrackingUpdated: (Bool) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
|
strings: PresentationStrings,
|
||||||
value: BlurValue,
|
value: BlurValue,
|
||||||
hasPortrait: Bool,
|
hasPortrait: Bool,
|
||||||
valueUpdated: @escaping (BlurValue) -> Void,
|
valueUpdated: @escaping (BlurValue) -> Void,
|
||||||
isTrackingUpdated: @escaping (Bool) -> Void
|
isTrackingUpdated: @escaping (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
|
self.strings = strings
|
||||||
self.value = value
|
self.value = value
|
||||||
self.hasPortrait = hasPortrait
|
self.hasPortrait = hasPortrait
|
||||||
self.valueUpdated = valueUpdated
|
self.valueUpdated = valueUpdated
|
||||||
@ -133,6 +137,9 @@ final class BlurComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
|
static func ==(lhs: BlurComponent, rhs: BlurComponent) -> Bool {
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.value != rhs.value {
|
if lhs.value != rhs.value {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -190,7 +197,7 @@ final class BlurComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "Blur",
|
text: component.strings.Story_Editor_Blur_Title,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: UIColor(rgb: 0x808080)
|
color: UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
@ -212,7 +219,7 @@ final class BlurComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
BlurModeComponent(
|
BlurModeComponent(
|
||||||
title: "Off",
|
title: component.strings.Story_Editor_Blur_Off,
|
||||||
icon: self.offImage,
|
icon: self.offImage,
|
||||||
isSelected: state.value.mode == .off
|
isSelected: state.value.mode == .off
|
||||||
)
|
)
|
||||||
@ -233,7 +240,7 @@ final class BlurComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
BlurModeComponent(
|
BlurModeComponent(
|
||||||
title: "Radial",
|
title: component.strings.Story_Editor_Blur_Radial,
|
||||||
icon: self.radialImage,
|
icon: self.radialImage,
|
||||||
isSelected: state.value.mode == .radial
|
isSelected: state.value.mode == .radial
|
||||||
)
|
)
|
||||||
@ -254,7 +261,7 @@ final class BlurComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
BlurModeComponent(
|
BlurModeComponent(
|
||||||
title: "Linear",
|
title: component.strings.Story_Editor_Blur_Linear,
|
||||||
icon: self.linearImage,
|
icon: self.linearImage,
|
||||||
isSelected: state.value.mode == .linear
|
isSelected: state.value.mode == .linear
|
||||||
)
|
)
|
||||||
@ -275,7 +282,7 @@ final class BlurComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
BlurModeComponent(
|
BlurModeComponent(
|
||||||
title: "Portrait",
|
title: component.strings.Story_Editor_Blur_Portrait,
|
||||||
icon: self.portraitImage,
|
icon: self.portraitImage,
|
||||||
isSelected: state.value.mode == .portrait
|
isSelected: state.value.mode == .portrait
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ import ComponentFlow
|
|||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
private class HistogramView: UIView {
|
private class HistogramView: UIView {
|
||||||
private var size: CGSize?
|
private var size: CGSize?
|
||||||
@ -75,18 +76,24 @@ class CurvesInternalState {
|
|||||||
final class CurvesComponent: Component {
|
final class CurvesComponent: Component {
|
||||||
typealias EnvironmentType = Empty
|
typealias EnvironmentType = Empty
|
||||||
|
|
||||||
|
let strings: PresentationStrings
|
||||||
let histogram: MediaEditorHistogram?
|
let histogram: MediaEditorHistogram?
|
||||||
let internalState: CurvesInternalState
|
let internalState: CurvesInternalState
|
||||||
|
|
||||||
init(
|
init(
|
||||||
|
strings: PresentationStrings,
|
||||||
histogram: MediaEditorHistogram?,
|
histogram: MediaEditorHistogram?,
|
||||||
internalState: CurvesInternalState
|
internalState: CurvesInternalState
|
||||||
) {
|
) {
|
||||||
|
self.strings = strings
|
||||||
self.histogram = histogram
|
self.histogram = histogram
|
||||||
self.internalState = internalState
|
self.internalState = internalState
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: CurvesComponent, rhs: CurvesComponent) -> Bool {
|
static func ==(lhs: CurvesComponent, rhs: CurvesComponent) -> Bool {
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.histogram != rhs.histogram {
|
if lhs.histogram != rhs.histogram {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -145,7 +152,7 @@ final class CurvesComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "All",
|
text: component.strings.Story_Editor_Curves_All,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: state.section == .all ? .white : UIColor(rgb: 0x808080)
|
color: state.section == .all ? .white : UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
@ -173,7 +180,7 @@ final class CurvesComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "Red",
|
text: component.strings.Story_Editor_Curves_Red,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: state.section == .red ? .white : UIColor(rgb: 0x808080)
|
color: state.section == .red ? .white : UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
@ -201,7 +208,7 @@ final class CurvesComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "Green",
|
text: component.strings.Story_Editor_Curves_Green,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: state.section == .green ? .white : UIColor(rgb: 0x808080)
|
color: state.section == .green ? .white : UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
@ -229,7 +236,7 @@ final class CurvesComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "Blue",
|
text: component.strings.Story_Editor_Curves_Blue,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: state.section == .blue ? .white : UIColor(rgb: 0x808080)
|
color: state.section == .blue ? .white : UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
|
@ -725,9 +725,9 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
transition.setAlpha(view: cancelButtonView, alpha: component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0)
|
transition.setAlpha(view: cancelButtonView, alpha: component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var doneButtonTitle = "NEXT"
|
var doneButtonTitle = environment.strings.Story_Editor_Next
|
||||||
if let controller = environment.controller() as? MediaEditorScreen, controller.isEditingStory {
|
if let controller = environment.controller() as? MediaEditorScreen, controller.isEditingStory {
|
||||||
doneButtonTitle = "DONE"
|
doneButtonTitle = environment.strings.Story_Editor_Done
|
||||||
}
|
}
|
||||||
|
|
||||||
let doneButtonSize = self.doneButton.update(
|
let doneButtonSize = self.doneButton.update(
|
||||||
@ -736,7 +736,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
content: AnyComponent(DoneButtonComponent(
|
content: AnyComponent(DoneButtonComponent(
|
||||||
backgroundColor: UIColor(rgb: 0x007aff),
|
backgroundColor: UIColor(rgb: 0x007aff),
|
||||||
icon: UIImage(bundleImageName: "Media Editor/Next")!,
|
icon: UIImage(bundleImageName: "Media Editor/Next")!,
|
||||||
title: doneButtonTitle)),
|
title: doneButtonTitle.uppercased())),
|
||||||
action: {
|
action: {
|
||||||
guard let controller = environment.controller() as? MediaEditorScreen else {
|
guard let controller = environment.controller() as? MediaEditorScreen else {
|
||||||
return
|
return
|
||||||
@ -1097,7 +1097,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
style: .editor,
|
style: .editor,
|
||||||
placeholder: "Add a caption...",
|
placeholder: environment.strings.Story_Editor_InputPlaceholderAddCaption,
|
||||||
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
|
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
|
||||||
queryTypes: [.mention],
|
queryTypes: [.mention],
|
||||||
alwaysDarkWhenHasText: false,
|
alwaysDarkWhenHasText: false,
|
||||||
@ -1274,20 +1274,20 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
var privacyText: String
|
var privacyText: String
|
||||||
switch component.privacy.privacy.base {
|
switch component.privacy.privacy.base {
|
||||||
case .everyone:
|
case .everyone:
|
||||||
privacyText = "Everyone"
|
privacyText = environment.strings.Story_ContextPrivacy_LabelEveryone
|
||||||
case .closeFriends:
|
case .closeFriends:
|
||||||
privacyText = "Close Friends"
|
privacyText = environment.strings.Story_ContextPrivacy_LabelCloseFriends
|
||||||
case .contacts:
|
case .contacts:
|
||||||
privacyText = "Contacts"
|
|
||||||
if additionalPeersCount > 0 {
|
if additionalPeersCount > 0 {
|
||||||
privacyText += " (-\(additionalPeersCount))"
|
privacyText = environment.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalPeersCount)").string
|
||||||
|
} else {
|
||||||
|
privacyText = environment.strings.Story_ContextPrivacy_LabelContacts
|
||||||
}
|
}
|
||||||
case .nobody:
|
case .nobody:
|
||||||
privacyText = "Selected Contacts"
|
|
||||||
if additionalPeersCount > 0 {
|
if additionalPeersCount > 0 {
|
||||||
privacyText += " (\(additionalPeersCount))"
|
privacyText = environment.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalPeersCount))
|
||||||
} else {
|
} else {
|
||||||
privacyText = "Only You"
|
privacyText = environment.strings.Story_ContextPrivacy_LabelOnlyMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2641,7 +2641,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
|
||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
|
||||||
|
|
||||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound" : "The story will have sound"), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? self.presentationData.strings.Story_Editor_TooltipMuted : self.presentationData.strings.Story_Editor_TooltipUnmuted), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||||
return .ignore
|
return .ignore
|
||||||
})
|
})
|
||||||
self.muteTooltip = tooltipController
|
self.muteTooltip = tooltipController
|
||||||
@ -2664,9 +2664,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
let text: String
|
let text: String
|
||||||
let isVideo = self.mediaEditor?.resultIsVideo ?? false
|
let isVideo = self.mediaEditor?.resultIsVideo ?? false
|
||||||
if isVideo {
|
if isVideo {
|
||||||
text = "Video saved to Photos."
|
text = self.presentationData.strings.Story_Editor_TooltipVideoSavedToPhotos
|
||||||
} else {
|
} else {
|
||||||
text = "Image saved to Photos."
|
text = self.presentationData.strings.Story_Editor_TooltipImageSavedToPhotos
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tooltipController = self.saveTooltip {
|
if let tooltipController = self.saveTooltip {
|
||||||
@ -2690,8 +2690,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = "Uploading..."
|
let text = self.presentationData.strings.Story_Editor_Uploading
|
||||||
|
|
||||||
if let tooltipController = self.saveTooltip {
|
if let tooltipController = self.saveTooltip {
|
||||||
tooltipController.content = .progress(text, progress)
|
tooltipController.content = .progress(text, progress)
|
||||||
} else {
|
} else {
|
||||||
@ -2718,8 +2717,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = "Preparing video..."
|
let text = self.presentationData.strings.Story_Editor_PreparingVideo
|
||||||
|
|
||||||
if let tooltipController = self.saveTooltip {
|
if let tooltipController = self.saveTooltip {
|
||||||
tooltipController.content = .progress(text, progress)
|
tooltipController.content = .progress(text, progress)
|
||||||
} else {
|
} else {
|
||||||
@ -3081,7 +3079,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
ToolValueComponent(
|
ToolValueComponent(
|
||||||
title: "Enhance",
|
title: environment.strings.Story_Editor_Tool_Enhance,
|
||||||
value: "\(Int(abs(enhanceValue) * 100.0))"
|
value: "\(Int(abs(enhanceValue) * 100.0))"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -3441,14 +3439,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, isForwardingDisabled: self.state.privacy.isForwardingDisabled, pin: self.state.privacy.pin)
|
self.state.privacy = MediaEditorResultPrivacy(privacy: self.state.privacy.privacy, timeout: timeout ?? 86400, isForwardingDisabled: self.state.privacy.isForwardingDisabled, pin: self.state.privacy.pin)
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = "Choose how long the story will be visible."
|
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
||||||
|
let title = presentationData.strings.Story_Editor_ExpirationText
|
||||||
let currentValue = self.state.privacy.timeout
|
let currentValue = self.state.privacy.timeout
|
||||||
let currentArchived = self.state.privacy.pin
|
let currentArchived = self.state.privacy.pin
|
||||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "6 Hours", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(6), icon: { theme in
|
||||||
if !hasPremium {
|
if !hasPremium {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||||
} else {
|
} else {
|
||||||
@ -3463,7 +3462,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self?.presentTimeoutPremiumSuggestion(3600 * 6)
|
self?.presentTimeoutPremiumSuggestion(3600 * 6)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "12 Hours", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(12), icon: { theme in
|
||||||
if !hasPremium {
|
if !hasPremium {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||||
} else {
|
} else {
|
||||||
@ -3478,14 +3477,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self?.presentTimeoutPremiumSuggestion(3600 * 12)
|
self?.presentTimeoutPremiumSuggestion(3600 * 12)
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "24 Hours", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(24), icon: { theme in
|
||||||
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
return currentValue == 86400 && !currentArchived ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
}, action: { _, a in
|
}, action: { _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
|
|
||||||
updateTimeout(86400)
|
updateTimeout(86400)
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "48 Hours", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(48), icon: { theme in
|
||||||
if !hasPremium {
|
if !hasPremium {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
||||||
} else {
|
} else {
|
||||||
@ -3501,7 +3500,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
|
||||||
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
let contextController = ContextController(account: self.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||||
self.present(contextController, in: .window(.root))
|
self.present(contextController, in: .window(.root))
|
||||||
}
|
}
|
||||||
@ -3510,10 +3508,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let timeoutString = presentationData.strings.MuteExpires_Hours(max(1, timeout / (60 * 60)))
|
let timeoutString = presentationData.strings.MuteExpires_Hours(max(1, timeout / (60 * 60)))
|
||||||
let text = "Subscribe to **Telegram Premium** to make your stories disappear \(timeoutString)."
|
let text = presentationData.strings.Story_Editor_TooltipPremiumCustomExpiration(timeoutString).string
|
||||||
|
|
||||||
let context = self.context
|
let context = self.context
|
||||||
let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: "More"), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
|
let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: presentationData.strings.Story_Editor_TooltipPremiumMore), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in
|
||||||
if case .undo = action, let self {
|
if case .undo = action, let self {
|
||||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
|
||||||
self.push(controller)
|
self.push(controller)
|
||||||
@ -3549,23 +3547,24 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let title: String
|
let title: String
|
||||||
let save: String
|
let save: String
|
||||||
if case .draft = self.node.subject {
|
if case .draft = self.node.subject {
|
||||||
title = "Discard Draft?"
|
title = presentationData.strings.Story_Editor_DraftDiscardDraft
|
||||||
save = "Keep Draft"
|
save = presentationData.strings.Story_Editor_DraftKeepDraft
|
||||||
} else {
|
} else {
|
||||||
title = "Discard Media?"
|
title = presentationData.strings.Story_Editor_DraftDiscardMedia
|
||||||
save = "Save Draft"
|
save = presentationData.strings.Story_Editor_DraftKeepMedia
|
||||||
}
|
}
|
||||||
let theme = defaultDarkPresentationTheme
|
let theme = defaultDarkPresentationTheme
|
||||||
let controller = textAlertController(
|
let controller = textAlertController(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
forceTheme: theme,
|
forceTheme: theme,
|
||||||
title: title,
|
title: title,
|
||||||
text: "If you go back now, you will lose any changes that you've made.",
|
text: presentationData.strings.Story_Editor_DraftDiscaedText,
|
||||||
actions: [
|
actions: [
|
||||||
TextAlertAction(type: .destructiveAction, title: "Discard", action: { [weak self] in
|
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in
|
||||||
if let self {
|
if let self {
|
||||||
self.requestDismiss(saveDraft: false, animated: true)
|
self.requestDismiss(saveDraft: false, animated: true)
|
||||||
}
|
}
|
||||||
@ -3575,7 +3574,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
self.requestDismiss(saveDraft: true, animated: true)
|
self.requestDismiss(saveDraft: true, animated: true)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
TextAlertAction(type: .genericAction, title: "Cancel", action: {
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||||
|
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
@ -3715,8 +3714,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false)
|
mediaEditor.stop()
|
||||||
mediaEditor.requestRenderFrame()
|
|
||||||
mediaEditor.invalidate()
|
mediaEditor.invalidate()
|
||||||
self.node.entitiesView.invalidate()
|
self.node.entitiesView.invalidate()
|
||||||
|
|
||||||
@ -3777,7 +3775,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mediaEditor.resultIsVideo {
|
if mediaEditor.resultIsVideo {
|
||||||
var firstFrame: Signal<UIImage?, NoError>
|
var firstFrame: Signal<(UIImage?, UIImage?), NoError>
|
||||||
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
|
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
|
||||||
|
|
||||||
let videoResult: Result.VideoResult
|
let videoResult: Result.VideoResult
|
||||||
@ -3791,8 +3789,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
videoResult = .imageFile(path: tempImagePath)
|
videoResult = .imageFile(path: tempImagePath)
|
||||||
duration = 5.0
|
duration = 5.0
|
||||||
|
|
||||||
firstFrame = .single(image)
|
firstFrame = .single((image, nil))
|
||||||
case let .video(path, _, _, _, _, _, durationValue, _, _):
|
case let .video(path, _, _, additionalPath, _, _, durationValue, _, _):
|
||||||
videoResult = .videoFile(path: path)
|
videoResult = .videoFile(path: path)
|
||||||
if let videoTrimRange = mediaEditor.values.videoTrimRange {
|
if let videoTrimRange = mediaEditor.values.videoTrimRange {
|
||||||
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
|
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
|
||||||
@ -3800,12 +3798,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
duration = durationValue
|
duration = durationValue
|
||||||
}
|
}
|
||||||
|
|
||||||
firstFrame = Signal<UIImage?, NoError> { subscriber in
|
let _ = additionalPath
|
||||||
|
|
||||||
|
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
|
||||||
let avAsset = AVURLAsset(url: URL(fileURLWithPath: path))
|
let avAsset = AVURLAsset(url: URL(fileURLWithPath: path))
|
||||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||||
|
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
||||||
if let cgImage {
|
if let cgImage {
|
||||||
subscriber.putNext(UIImage(cgImage: cgImage))
|
subscriber.putNext((UIImage(cgImage: cgImage), nil))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -3824,14 +3825,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
} else {
|
} else {
|
||||||
duration = 5.0
|
duration = 5.0
|
||||||
}
|
}
|
||||||
firstFrame = Signal<UIImage?, NoError> { subscriber in
|
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
|
||||||
if asset.mediaType == .video {
|
if asset.mediaType == .video {
|
||||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
||||||
if let avAsset {
|
if let avAsset {
|
||||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||||
|
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
||||||
if let cgImage {
|
if let cgImage {
|
||||||
subscriber.putNext(UIImage(cgImage: cgImage))
|
subscriber.putNext((UIImage(cgImage: cgImage), nil))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -3842,7 +3844,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
options.deliveryMode = .highQualityFormat
|
options.deliveryMode = .highQualityFormat
|
||||||
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
||||||
if let image {
|
if let image {
|
||||||
subscriber.putNext(image)
|
subscriber.putNext((image, nil))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3857,12 +3859,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
} else {
|
} else {
|
||||||
duration = min(draft.duration ?? 5.0, storyMaxVideoDuration)
|
duration = min(draft.duration ?? 5.0, storyMaxVideoDuration)
|
||||||
}
|
}
|
||||||
firstFrame = Signal<UIImage?, NoError> { subscriber in
|
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
|
||||||
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath()))
|
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath()))
|
||||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||||
|
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
|
||||||
if let cgImage {
|
if let cgImage {
|
||||||
subscriber.putNext(UIImage(cgImage: cgImage))
|
subscriber.putNext((UIImage(cgImage: cgImage), nil))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -3875,21 +3878,34 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
duration = 5.0
|
duration = 5.0
|
||||||
|
|
||||||
if let image = UIImage(contentsOfFile: draft.fullPath()) {
|
if let image = UIImage(contentsOfFile: draft.fullPath()) {
|
||||||
firstFrame = .single(image)
|
firstFrame = .single((image, nil))
|
||||||
} else {
|
} else {
|
||||||
firstFrame = .single(UIImage())
|
firstFrame = .single((UIImage(), nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let resultImage = mediaEditor.resultImage {
|
|
||||||
firstFrame = .single(resultImage)
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = (firstFrame
|
let _ = (firstFrame
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
|> deliverOnMainQueue).start(next: { [weak self] image, additionalImage in
|
||||||
if let self {
|
if let self {
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
|
var currentImage = mediaEditor.resultImage
|
||||||
|
if let image {
|
||||||
|
mediaEditor.replaceSource(image, additionalImage: additionalImage, time: firstFrameTime)
|
||||||
|
if let updatedImage = mediaEditor.resultImage {
|
||||||
|
currentImage = updatedImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputImage: UIImage
|
||||||
|
if let currentImage {
|
||||||
|
inputImage = currentImage
|
||||||
|
} else if let image {
|
||||||
|
inputImage = image
|
||||||
|
} else {
|
||||||
|
inputImage = UIImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: inputImage, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
|
||||||
if let self {
|
if let self {
|
||||||
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
||||||
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||||
|
@ -335,6 +335,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let isTablet: Bool
|
let isTablet: Bool
|
||||||
if case .regular = environment.metrics.widthClass {
|
if case .regular = environment.metrics.widthClass {
|
||||||
isTablet = true
|
isTablet = true
|
||||||
@ -577,7 +578,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
var tools: [AdjustmentTool] = [
|
var tools: [AdjustmentTool] = [
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .enhance,
|
key: .enhance,
|
||||||
title: "Enhance",
|
title: presentationData.strings.Story_Editor_Tool_Enhance,
|
||||||
value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.enhance) as? Float ?? 0.0,
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -585,7 +586,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .brightness,
|
key: .brightness,
|
||||||
title: "Brightness",
|
title: presentationData.strings.Story_Editor_Tool_Brightness,
|
||||||
value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.brightness) as? Float ?? 0.0,
|
||||||
minValue: -1.0,
|
minValue: -1.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -593,7 +594,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .contrast,
|
key: .contrast,
|
||||||
title: "Contrast",
|
title: presentationData.strings.Story_Editor_Tool_Contrast,
|
||||||
value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.contrast) as? Float ?? 0.0,
|
||||||
minValue: -1.0,
|
minValue: -1.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -601,7 +602,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .saturation,
|
key: .saturation,
|
||||||
title: "Saturation",
|
title: presentationData.strings.Story_Editor_Tool_Saturation,
|
||||||
value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.saturation) as? Float ?? 0.0,
|
||||||
minValue: -1.0,
|
minValue: -1.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -609,7 +610,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .warmth,
|
key: .warmth,
|
||||||
title: "Warmth",
|
title: presentationData.strings.Story_Editor_Tool_Warmth,
|
||||||
value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.warmth) as? Float ?? 0.0,
|
||||||
minValue: -1.0,
|
minValue: -1.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -617,7 +618,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .fade,
|
key: .fade,
|
||||||
title: "Fade",
|
title: presentationData.strings.Story_Editor_Tool_Fade,
|
||||||
value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.fade) as? Float ?? 0.0,
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -625,7 +626,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .highlights,
|
key: .highlights,
|
||||||
title: "Highlights",
|
title: presentationData.strings.Story_Editor_Tool_Highlights,
|
||||||
value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.highlights) as? Float ?? 0.0,
|
||||||
minValue: -1.0,
|
minValue: -1.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -633,7 +634,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .shadows,
|
key: .shadows,
|
||||||
title: "Shadows",
|
title: presentationData.strings.Story_Editor_Tool_Shadows,
|
||||||
value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.shadows) as? Float ?? 0.0,
|
||||||
minValue: -1.0,
|
minValue: -1.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -641,7 +642,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
AdjustmentTool(
|
AdjustmentTool(
|
||||||
key: .vignette,
|
key: .vignette,
|
||||||
title: "Vignette",
|
title: presentationData.strings.Story_Editor_Tool_Vignette,
|
||||||
value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.vignette) as? Float ?? 0.0,
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -660,7 +661,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
if !component.mediaEditor.sourceIsVideo {
|
if !component.mediaEditor.sourceIsVideo {
|
||||||
tools.insert(AdjustmentTool(
|
tools.insert(AdjustmentTool(
|
||||||
key: .grain,
|
key: .grain,
|
||||||
title: "Grain",
|
title: presentationData.strings.Story_Editor_Tool_Grain,
|
||||||
value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0,
|
value: mediaEditor?.getToolValue(.grain) as? Float ?? 0.0,
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
maxValue: 1.0,
|
maxValue: 1.0,
|
||||||
@ -721,6 +722,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
optionsSize = self.toolOptions.update(
|
optionsSize = self.toolOptions.update(
|
||||||
transition: optionsTransition,
|
transition: optionsTransition,
|
||||||
component: AnyComponent(TintComponent(
|
component: AnyComponent(TintComponent(
|
||||||
|
strings: presentationData.strings,
|
||||||
shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial,
|
shadowsValue: mediaEditor?.getToolValue(.shadowsTint) as? TintValue ?? TintValue.initial,
|
||||||
highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial,
|
highlightsValue: mediaEditor?.getToolValue(.highlightsTint) as? TintValue ?? TintValue.initial,
|
||||||
shadowsValueUpdated: { [weak state] value in
|
shadowsValueUpdated: { [weak state] value in
|
||||||
@ -778,6 +780,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
optionsSize = self.toolOptions.update(
|
optionsSize = self.toolOptions.update(
|
||||||
transition: optionsTransition,
|
transition: optionsTransition,
|
||||||
component: AnyComponent(BlurComponent(
|
component: AnyComponent(BlurComponent(
|
||||||
|
strings: presentationData.strings,
|
||||||
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
|
value: mediaEditor?.getToolValue(.blur) as? BlurValue ?? BlurValue.initial,
|
||||||
hasPortrait: mediaEditor?.hasPortraitMask ?? false,
|
hasPortrait: mediaEditor?.hasPortraitMask ?? false,
|
||||||
valueUpdated: { [weak state] value in
|
valueUpdated: { [weak state] value in
|
||||||
@ -853,6 +856,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
optionsSize = self.toolOptions.update(
|
optionsSize = self.toolOptions.update(
|
||||||
transition: optionsTransition,
|
transition: optionsTransition,
|
||||||
component: AnyComponent(CurvesComponent(
|
component: AnyComponent(CurvesComponent(
|
||||||
|
strings: presentationData.strings,
|
||||||
histogram: state.histogram,
|
histogram: state.histogram,
|
||||||
internalState: internalState
|
internalState: internalState
|
||||||
)),
|
)),
|
||||||
|
@ -218,10 +218,12 @@ final class StoryPreviewComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(Text(
|
component: AnyComponent(Text(
|
||||||
text: "My story",
|
text: presentationData.strings.Story_HeaderYourStory,
|
||||||
font: Font.medium(14.0),
|
font: Font.medium(14.0),
|
||||||
color: .white
|
color: .white
|
||||||
)),
|
)),
|
||||||
@ -240,7 +242,6 @@ final class StoryPreviewComponent: Component {
|
|||||||
transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size))
|
transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let inputPanelSize = self.inputPanel.update(
|
let inputPanelSize = self.inputPanel.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(MessageInputPanelComponent(
|
component: AnyComponent(MessageInputPanelComponent(
|
||||||
@ -249,7 +250,7 @@ final class StoryPreviewComponent: Component {
|
|||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
strings: presentationData.strings,
|
strings: presentationData.strings,
|
||||||
style: .story,
|
style: .story,
|
||||||
placeholder: "Reply Privately...",
|
placeholder: presentationData.strings.Story_InputPlaceholderReplyPrivately,
|
||||||
maxLength: nil,
|
maxLength: nil,
|
||||||
queryTypes: [],
|
queryTypes: [],
|
||||||
alwaysDarkWhenHasText: false,
|
alwaysDarkWhenHasText: false,
|
||||||
|
@ -4,6 +4,7 @@ import Display
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
private final class TintColorComponent: Component {
|
private final class TintColorComponent: Component {
|
||||||
typealias EnvironmentType = Empty
|
typealias EnvironmentType = Empty
|
||||||
@ -106,6 +107,7 @@ final class TintComponent: Component {
|
|||||||
|
|
||||||
typealias EnvironmentType = Empty
|
typealias EnvironmentType = Empty
|
||||||
|
|
||||||
|
let strings: PresentationStrings
|
||||||
let shadowsValue: TintValue
|
let shadowsValue: TintValue
|
||||||
let highlightsValue: TintValue
|
let highlightsValue: TintValue
|
||||||
let shadowsValueUpdated: (TintValue) -> Void
|
let shadowsValueUpdated: (TintValue) -> Void
|
||||||
@ -113,12 +115,14 @@ final class TintComponent: Component {
|
|||||||
let isTrackingUpdated: (Bool) -> Void
|
let isTrackingUpdated: (Bool) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
|
strings: PresentationStrings,
|
||||||
shadowsValue: TintValue,
|
shadowsValue: TintValue,
|
||||||
highlightsValue: TintValue,
|
highlightsValue: TintValue,
|
||||||
shadowsValueUpdated: @escaping (TintValue) -> Void,
|
shadowsValueUpdated: @escaping (TintValue) -> Void,
|
||||||
highlightsValueUpdated: @escaping (TintValue) -> Void,
|
highlightsValueUpdated: @escaping (TintValue) -> Void,
|
||||||
isTrackingUpdated: @escaping (Bool) -> Void
|
isTrackingUpdated: @escaping (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
|
self.strings = strings
|
||||||
self.shadowsValue = shadowsValue
|
self.shadowsValue = shadowsValue
|
||||||
self.highlightsValue = highlightsValue
|
self.highlightsValue = highlightsValue
|
||||||
self.shadowsValueUpdated = shadowsValueUpdated
|
self.shadowsValueUpdated = shadowsValueUpdated
|
||||||
@ -127,6 +131,9 @@ final class TintComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool {
|
static func ==(lhs: TintComponent, rhs: TintComponent) -> Bool {
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.highlightsValue != rhs.highlightsValue {
|
if lhs.highlightsValue != rhs.highlightsValue {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -185,7 +192,7 @@ final class TintComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "Shadows",
|
text: component.strings.Story_Editor_Tint_Shadows,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: state.section == .shadows ? .white : UIColor(rgb: 0x808080)
|
color: state.section == .shadows ? .white : UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
@ -213,7 +220,7 @@ final class TintComponent: Component {
|
|||||||
Button(
|
Button(
|
||||||
content: AnyComponent(
|
content: AnyComponent(
|
||||||
Text(
|
Text(
|
||||||
text: "Highlights",
|
text: component.strings.Story_Editor_Tint_Highlights,
|
||||||
font: Font.regular(14.0),
|
font: Font.regular(14.0),
|
||||||
color: state.section == .highlights ? .white : UIColor(rgb: 0x808080)
|
color: state.section == .highlights ? .white : UIColor(rgb: 0x808080)
|
||||||
)
|
)
|
||||||
|
@ -726,8 +726,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
|||||||
|
|
||||||
if isStories == nil || isStories == true {
|
if isStories == nil || isStories == true {
|
||||||
if case .user = peer {
|
if case .user = peer {
|
||||||
//TODO:localize
|
entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notification_Exceptions_StoriesHeader))
|
||||||
entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: "STORY NOTIFICATIONS"))
|
|
||||||
index += 1
|
index += 1
|
||||||
entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesMuted == .alwaysOn))
|
entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesMuted == .alwaysOn))
|
||||||
index += 1
|
index += 1
|
||||||
@ -735,7 +734,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
|||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
if state.storiesMuted != .alwaysOff {
|
if state.storiesMuted != .alwaysOff {
|
||||||
entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: "DISPLAY AUTHOR NAME"))
|
entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: presentationData.strings.Notification_Exceptions_StoriesDisplayAuthorName))
|
||||||
index += 1
|
index += 1
|
||||||
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn))
|
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn))
|
||||||
index += 1
|
index += 1
|
||||||
|
@ -88,9 +88,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
let strings = presentationData.strings
|
let strings = presentationData.strings
|
||||||
|
|
||||||
if self.selectedCount != 0 {
|
if self.selectedCount != 0 {
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryList_ContextSaveToGallery, icon: { theme in
|
||||||
//TODO:update icon
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Save to Photos", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -114,15 +112,9 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
let _ = component.context.engine.messages.deleteStories(ids: Array(paneNode.selectedIds)).start()
|
let _ = component.context.engine.messages.deleteStories(ids: Array(paneNode.selectedIds)).start()
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let text: String
|
|
||||||
if paneNode.selectedIds.count == 1 {
|
|
||||||
text = "1 story deleted."
|
|
||||||
} else {
|
|
||||||
text = "\(paneNode.selectedIds.count) stories deleted."
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||||
|
let text: String = presentationData.strings.StoryList_TooltipStoriesDeleted(Int32(paneNode.selectedIds.count))
|
||||||
|
|
||||||
environment.controller()?.present(UndoOverlayController(
|
environment.controller()?.present(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .info(title: nil, text: text, timeout: nil),
|
content: .info(title: nil, text: text, timeout: nil),
|
||||||
@ -163,8 +155,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
|
|
||||||
if component.peerId == component.context.account.peerId, case .saved = component.scope {
|
if component.peerId == component.context.account.peerId, case .saved = component.scope {
|
||||||
var ignoreNextActions = false
|
var ignoreNextActions = false
|
||||||
//TODO:localize
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryList_ContextShowArchive, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: "Show Archive", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StoryArchive"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StoryArchive"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
if ignoreNextActions {
|
if ignoreNextActions {
|
||||||
@ -305,8 +296,8 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
let strings = (component.context.sharedContext.currentPresentationData.with { $0 }).strings
|
||||||
let saveScreen = SaveProgressScreen(context: component.context, content: .progress("Saving", 0.0))
|
let saveScreen = SaveProgressScreen(context: component.context, content: .progress(strings.Story_TooltipSaving, 0.0))
|
||||||
self.environment?.controller()?.present(saveScreen, in: .current)
|
self.environment?.controller()?.present(saveScreen, in: .current)
|
||||||
|
|
||||||
let valueNorm: Float = 1.0 / Float(sortedItems.count)
|
let valueNorm: Float = 1.0 / Float(sortedItems.count)
|
||||||
@ -330,12 +321,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
guard let saveScreen else {
|
guard let saveScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
saveScreen.content = .progress("Saving", progress)
|
saveScreen.content = .progress(strings.Story_TooltipSaving, progress)
|
||||||
}, completed: { [weak saveScreen] in
|
}, completed: { [weak saveScreen] in
|
||||||
guard let saveScreen else {
|
guard let saveScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
saveScreen.content = .completion("Saved")
|
saveScreen.content = .completion(strings.Story_TooltipSaved)
|
||||||
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
||||||
saveScreen?.dismiss()
|
saveScreen?.dismiss()
|
||||||
})
|
})
|
||||||
@ -376,12 +367,11 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
self.selectionPanel = selectionPanel
|
self.selectionPanel = selectionPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let selectionPanelSize = selectionPanel.update(
|
let selectionPanelSize = selectionPanel.update(
|
||||||
transition: selectionPanelTransition,
|
transition: selectionPanelTransition,
|
||||||
component: AnyComponent(BottomButtonPanelComponent(
|
component: AnyComponent(BottomButtonPanelComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
title: "Save to Profile",
|
title: environment.strings.StoryList_SaveToProfile,
|
||||||
label: nil,
|
label: nil,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset),
|
insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset),
|
||||||
@ -395,18 +385,12 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
|||||||
|
|
||||||
let _ = component.context.engine.messages.updateStoriesArePinned(ids: paneNode.selectedItems, isPinned: true).start()
|
let _ = component.context.engine.messages.updateStoriesArePinned(ids: paneNode.selectedItems, isPinned: true).start()
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let title: String
|
|
||||||
if paneNode.selectedIds.count == 1 {
|
|
||||||
title = "Story saved to your profile"
|
|
||||||
} else {
|
|
||||||
title = "\(paneNode.selectedIds.count) saved to your profile"
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||||
|
|
||||||
|
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(paneNode.selectedIds.count))
|
||||||
environment.controller()?.present(UndoOverlayController(
|
environment.controller()?.present(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .info(title: title, text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil),
|
content: .info(title: title, text: presentationData.strings.StoryList_TooltipStoriesSavedToProfileText, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -596,7 +580,8 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateTitle() {
|
func updateTitle() {
|
||||||
//TODO:localize
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
switch self.scope {
|
switch self.scope {
|
||||||
case .saved:
|
case .saved:
|
||||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||||
@ -608,16 +593,16 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer {
|
|||||||
} else {
|
} else {
|
||||||
title = nil
|
title = nil
|
||||||
}
|
}
|
||||||
self.titleView?.titleContent = .custom("My Stories", title, false)
|
self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false)
|
||||||
case .archive:
|
case .archive:
|
||||||
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
guard let componentView = self.node.hostView.componentView as? PeerInfoStoryGridScreenComponent.View else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let title: String
|
let title: String
|
||||||
if componentView.selectedCount != 0 {
|
if componentView.selectedCount != 0 {
|
||||||
title = "\(componentView.selectedCount) Selected"
|
title = presentationData.strings.StoryList_SubtitleSelected(Int32(componentView.selectedCount))
|
||||||
} else {
|
} else {
|
||||||
title = "Stories Archive"
|
title = presentationData.strings.StoryList_TitleArchive
|
||||||
}
|
}
|
||||||
self.titleView?.titleContent = .custom(title, nil, false)
|
self.titleView?.titleContent = .custom(title, nil, false)
|
||||||
}
|
}
|
||||||
|
@ -1560,17 +1560,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
let title: String
|
let title: String
|
||||||
if state.totalCount == 0 {
|
if state.totalCount == 0 {
|
||||||
title = ""
|
title = ""
|
||||||
} else if state.totalCount == 1 {
|
|
||||||
if self.isSaved {
|
|
||||||
title = "1 saved story"
|
|
||||||
} else {
|
|
||||||
title = "1 story"
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if self.isSaved {
|
if self.isSaved {
|
||||||
title = "\(state.totalCount) saved stories"
|
title = self.presentationData.strings.StoryList_SubtitleSaved(Int32(state.totalCount))
|
||||||
} else {
|
} else {
|
||||||
title = "\(state.totalCount) stories"
|
title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: .stories)))
|
self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: .stories)))
|
||||||
@ -1607,8 +1601,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
|
|
||||||
var headerText: String?
|
var headerText: String?
|
||||||
if strongSelf.isArchive && !mappedItems.isEmpty {
|
if strongSelf.isArchive && !mappedItems.isEmpty {
|
||||||
//TODO:localize
|
headerText = strongSelf.presentationData.strings.StoryList_ArchiveDescription
|
||||||
headerText = "Only you can see archived stories unless you choose to save them to your profile."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let items = SparseItemGrid.Items(
|
let items = SparseItemGrid.Items(
|
||||||
@ -1914,16 +1907,15 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
emptyStateView = ComponentView()
|
emptyStateView = ComponentView()
|
||||||
self.emptyStateView = emptyStateView
|
self.emptyStateView = emptyStateView
|
||||||
}
|
}
|
||||||
//TODO:localize
|
|
||||||
let emptyStateSize = emptyStateView.update(
|
let emptyStateSize = emptyStateView.update(
|
||||||
transition: emptyStateTransition,
|
transition: emptyStateTransition,
|
||||||
component: AnyComponent(EmptyStateIndicatorComponent(
|
component: AnyComponent(EmptyStateIndicatorComponent(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
animationName: "StoryListEmpty",
|
animationName: "StoryListEmpty",
|
||||||
title: self.isArchive ? "No Archived Stories" : "No saved stories",
|
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title,
|
||||||
text: self.isArchive ? "Upload a new story to view it here" : "Open the Archive to select stories you\nwant to be displayed in your profile.",
|
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text,
|
||||||
actionTitle: self.isArchive ? nil : "Open Archive",
|
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
@ -128,12 +128,11 @@ public final class ArchiveInfoContentComponent: Component {
|
|||||||
contentHeight += 15.0
|
contentHeight += 15.0
|
||||||
|
|
||||||
let titleString = NSMutableAttributedString()
|
let titleString = NSMutableAttributedString()
|
||||||
titleString.append(NSAttributedString(string: "This is Your Archive", font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor))
|
titleString.append(NSAttributedString(string: component.strings.ArchiveInfo_Title, font: Font.semibold(19.0), textColor: component.theme.list.itemPrimaryTextColor))
|
||||||
let imageAttachment = NSTextAttachment()
|
let imageAttachment = NSTextAttachment()
|
||||||
imageAttachment.image = self.iconBackground.image
|
imageAttachment.image = self.iconBackground.image
|
||||||
titleString.append(NSAttributedString(attachment: imageAttachment))
|
titleString.append(NSAttributedString(attachment: imageAttachment))
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
@ -153,11 +152,10 @@ public final class ArchiveInfoContentComponent: Component {
|
|||||||
contentHeight += 16.0
|
contentHeight += 16.0
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
//TODO:localize
|
|
||||||
if component.settings.keepArchivedUnmuted {
|
if component.settings.keepArchivedUnmuted {
|
||||||
text = "Archived chats will remain in the Archive when you receive a new message. [Tap to change >]()"
|
text = component.strings.ArchiveInfo_TextKeepArchivedUnmuted
|
||||||
} else {
|
} else {
|
||||||
text = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change >]()"
|
text = component.strings.ArchiveInfo_TextKeepArchivedDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainText = NSMutableAttributedString()
|
let mainText = NSMutableAttributedString()
|
||||||
@ -229,18 +227,18 @@ public final class ArchiveInfoContentComponent: Component {
|
|||||||
let itemDescs: [ItemDesc] = [
|
let itemDescs: [ItemDesc] = [
|
||||||
ItemDesc(
|
ItemDesc(
|
||||||
icon: "Chat List/Archive/IconArchived",
|
icon: "Chat List/Archive/IconArchived",
|
||||||
title: "Archived Chats",
|
title: component.strings.ArchiveInfo_ChatsTitle,
|
||||||
text: "Move any chat into your Archive and back by swiping on it."
|
text: component.strings.ArchiveInfo_ChatsText
|
||||||
),
|
),
|
||||||
ItemDesc(
|
ItemDesc(
|
||||||
icon: "Chat List/Archive/IconHide",
|
icon: "Chat List/Archive/IconHide",
|
||||||
title: "Hiding Archive",
|
title: component.strings.ArchiveInfo_HideTitle,
|
||||||
text: "Hide the Archive from your Main screen by swiping on it."
|
text: component.strings.ArchiveInfo_HideText
|
||||||
),
|
),
|
||||||
ItemDesc(
|
ItemDesc(
|
||||||
icon: "Chat List/Archive/IconStories",
|
icon: "Chat List/Archive/IconStories",
|
||||||
title: "Stories",
|
title: component.strings.ArchiveInfo_StoriesTitle,
|
||||||
text: "Archive Stories from your contacts separately from chats with them."
|
text: component.strings.ArchiveInfo_StoriesText
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
for i in 0 ..< itemDescs.count {
|
for i in 0 ..< itemDescs.count {
|
||||||
|
@ -76,7 +76,6 @@ private final class ArchiveInfoSheetContentComponent: Component {
|
|||||||
contentHeight += contentSize.height
|
contentHeight += contentSize.height
|
||||||
contentHeight += 30.0
|
contentHeight += 30.0
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let buttonSize = self.button.update(
|
let buttonSize = self.button.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ButtonComponent(
|
component: AnyComponent(ButtonComponent(
|
||||||
@ -86,7 +85,7 @@ private final class ArchiveInfoSheetContentComponent: Component {
|
|||||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
||||||
Text(text: "Got it", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
|
Text(text: environment.strings.ArchiveInfo_CloseAction, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
|
||||||
)),
|
)),
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
displaysProgress: false,
|
displaysProgress: false,
|
||||||
|
@ -310,8 +310,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
case .misc:
|
case .misc:
|
||||||
return strings.StorageManagement_SectionMiscellaneous
|
return strings.StorageManagement_SectionMiscellaneous
|
||||||
case .stories:
|
case .stories:
|
||||||
//TODO:localize
|
return strings.StorageManagement_SectionStories
|
||||||
return "Stories"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1810,8 +1809,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
mappedCategory = .groups
|
mappedCategory = .groups
|
||||||
case 3:
|
case 3:
|
||||||
iconName = "Settings/Menu/Stories"
|
iconName = "Settings/Menu/Stories"
|
||||||
//TODO:localized
|
title = environment.strings.Notifications_Stories
|
||||||
title = "Stories"
|
|
||||||
mappedCategory = .stories
|
mappedCategory = .stories
|
||||||
default:
|
default:
|
||||||
iconName = "Settings/Menu/Channels"
|
iconName = "Settings/Menu/Channels"
|
||||||
|
@ -6,6 +6,7 @@ import AccountContext
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
final class StoryAuthorInfoComponent: Component {
|
final class StoryAuthorInfoComponent: Component {
|
||||||
struct Counters: Equatable {
|
struct Counters: Equatable {
|
||||||
@ -14,13 +15,15 @@ final class StoryAuthorInfoComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
let strings: PresentationStrings
|
||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
let timestamp: Int32
|
let timestamp: Int32
|
||||||
let counters: Counters?
|
let counters: Counters?
|
||||||
let isEdited: Bool
|
let isEdited: Bool
|
||||||
|
|
||||||
init(context: AccountContext, peer: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) {
|
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.counters = counters
|
self.counters = counters
|
||||||
@ -31,6 +34,9 @@ final class StoryAuthorInfoComponent: Component {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.peer != rhs.peer {
|
if lhs.peer != rhs.peer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -75,8 +81,7 @@ final class StoryAuthorInfoComponent: Component {
|
|||||||
|
|
||||||
let title: String
|
let title: String
|
||||||
if component.peer?.id == component.context.account.peerId {
|
if component.peer?.id == component.context.account.peerId {
|
||||||
//TODO:localize
|
title = component.strings.Story_HeaderYourStory
|
||||||
title = "Your story"
|
|
||||||
} else {
|
} else {
|
||||||
title = component.peer?.debugDisplayTitle ?? ""
|
title = component.peer?.debugDisplayTitle ?? ""
|
||||||
}
|
}
|
||||||
@ -86,7 +91,7 @@ final class StoryAuthorInfoComponent: Component {
|
|||||||
|
|
||||||
if component.isEdited {
|
if component.isEdited {
|
||||||
subtitle.append(" • ")
|
subtitle.append(" • ")
|
||||||
subtitle.append("edited")
|
subtitle.append(component.strings.Story_HeaderEdited)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
|
@ -9,6 +9,7 @@ import TextNodeWithEntities
|
|||||||
import TextFormat
|
import TextFormat
|
||||||
import InvisibleInkDustNode
|
import InvisibleInkDustNode
|
||||||
import UrlEscaping
|
import UrlEscaping
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
final class StoryContentCaptionComponent: Component {
|
final class StoryContentCaptionComponent: Component {
|
||||||
enum Action {
|
enum Action {
|
||||||
@ -41,6 +42,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
|
|
||||||
let externalState: ExternalState
|
let externalState: ExternalState
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
let strings: PresentationStrings
|
||||||
let text: String
|
let text: String
|
||||||
let entities: [MessageTextEntity]
|
let entities: [MessageTextEntity]
|
||||||
let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||||
@ -50,6 +52,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
init(
|
init(
|
||||||
externalState: ExternalState,
|
externalState: ExternalState,
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
strings: PresentationStrings,
|
||||||
text: String,
|
text: String,
|
||||||
entities: [MessageTextEntity],
|
entities: [MessageTextEntity],
|
||||||
entityFiles: [EngineMedia.Id: TelegramMediaFile],
|
entityFiles: [EngineMedia.Id: TelegramMediaFile],
|
||||||
@ -58,6 +61,7 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
) {
|
) {
|
||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.strings = strings
|
||||||
self.text = text
|
self.text = text
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
self.entityFiles = entityFiles
|
self.entityFiles = entityFiles
|
||||||
@ -72,6 +76,9 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -425,9 +432,8 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
|
|
||||||
let truncationToken = NSMutableAttributedString()
|
let truncationToken = NSMutableAttributedString()
|
||||||
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(16.0), textColor: .white))
|
truncationToken.append(NSAttributedString(string: "\u{2026} ", font: Font.regular(16.0), textColor: .white))
|
||||||
truncationToken.append(NSAttributedString(string: "Show more", font: Font.semibold(16.0), textColor: .white))
|
truncationToken.append(NSAttributedString(string: component.strings.Story_CaptionShowMore, font: Font.semibold(16.0), textColor: .white))
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let collapsedTextLayout = TextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(TextNodeLayoutArguments(
|
let collapsedTextLayout = TextNodeWithEntities.asyncLayout(self.collapsedText.textNode)(TextNodeLayoutArguments(
|
||||||
attributedString: attributedText,
|
attributedString: attributedText,
|
||||||
maximumNumberOfLines: 3,
|
maximumNumberOfLines: 3,
|
||||||
|
@ -31,13 +31,15 @@ final class StoryItemContentComponent: Component {
|
|||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
let item: EngineStoryItem
|
let item: EngineStoryItem
|
||||||
let audioMode: StoryContentItem.AudioMode
|
let audioMode: StoryContentItem.AudioMode
|
||||||
|
let isVideoBuffering: Bool
|
||||||
|
|
||||||
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, audioMode: StoryContentItem.AudioMode) {
|
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.item = item
|
self.item = item
|
||||||
self.audioMode = audioMode
|
self.audioMode = audioMode
|
||||||
|
self.isVideoBuffering = isVideoBuffering
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
|
||||||
@ -53,6 +55,9 @@ final class StoryItemContentComponent: Component {
|
|||||||
if lhs.item != rhs.item {
|
if lhs.item != rhs.item {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isVideoBuffering != rhs.isVideoBuffering {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,11 +597,10 @@ final class StoryItemContentComponent: Component {
|
|||||||
self.unsupportedButton = unsupportedButton
|
self.unsupportedButton = unsupportedButton
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let unsupportedTextSize = unsupportedText.update(
|
let unsupportedTextSize = unsupportedText.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: "This story is not supported by\nyour version of Telegram.", font: Font.regular(17.0), textColor: .white)),
|
text: .plain(NSAttributedString(string: component.strings.Story_UnsupportedText, font: Font.regular(17.0), textColor: .white)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)),
|
)),
|
||||||
@ -611,7 +615,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.7)
|
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.7)
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(id: AnyHashable(""), component: AnyComponent(Text(text: "Update Telegram", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor
|
content: AnyComponentWithIdentity(id: AnyHashable(""), component: AnyComponent(Text(text: component.strings.Story_UnsupportedAction, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor
|
||||||
))),
|
))),
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
displaysProgress: false,
|
displaysProgress: false,
|
||||||
@ -655,10 +659,13 @@ final class StoryItemContentComponent: Component {
|
|||||||
self.updateProgressMode(update: false)
|
self.updateProgressMode(update: false)
|
||||||
|
|
||||||
if reloadMedia && synchronousLoad {
|
if reloadMedia && synchronousLoad {
|
||||||
|
let _ = startTime
|
||||||
|
#if DEBUG
|
||||||
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
print("\(CFAbsoluteTimeGetCurrent()) Synchronous: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.contentLoaded {
|
if !self.contentLoaded || component.isVideoBuffering {
|
||||||
let loadingEffectView: StoryItemLoadingEffectView
|
let loadingEffectView: StoryItemLoadingEffectView
|
||||||
if let current = self.loadingEffectView {
|
if let current = self.loadingEffectView {
|
||||||
loadingEffectView = current
|
loadingEffectView = current
|
||||||
|
@ -34,11 +34,6 @@ final class StoryItemImageView: UIView {
|
|||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.contentView)
|
self.addSubview(self.contentView)
|
||||||
#if DEBUG
|
|
||||||
if "".isEmpty {
|
|
||||||
self.contentView.isHidden = true
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -292,11 +287,10 @@ final class CaptureProtectedInfoComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
)
|
)
|
||||||
//TODO:localize
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: "Screenshot Blocked", font: Font.semibold(20.0), textColor: .white)),
|
text: .plain(NSAttributedString(string: component.strings.Story_ScreenshotBlockedTitle, font: Font.semibold(20.0), textColor: .white)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)),
|
)),
|
||||||
@ -306,7 +300,7 @@ final class CaptureProtectedInfoComponent: Component {
|
|||||||
let textSize = self.text.update(
|
let textSize = self.text.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: "The story you tried to take a\nscreenshot of is protected from\ncopying by its creator.", font: Font.regular(17.0), textColor: UIColor(white: 1.0, alpha: 0.6))),
|
text: .plain(NSAttributedString(string: component.strings.Story_ScreenshotBlockedText, font: Font.regular(17.0), textColor: UIColor(white: 1.0, alpha: 0.6))),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)),
|
)),
|
||||||
|
@ -1074,11 +1074,18 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
if visibleItem.currentProgress != progress || visibleItem.isBuffering != isBuffering || canSwitch {
|
if visibleItem.currentProgress != progress || visibleItem.isBuffering != isBuffering || canSwitch {
|
||||||
visibleItem.currentProgress = progress
|
visibleItem.currentProgress = progress
|
||||||
|
|
||||||
|
let isBufferingUpdated = visibleItem.isBuffering != isBuffering
|
||||||
visibleItem.isBuffering = isBuffering
|
visibleItem.isBuffering = isBuffering
|
||||||
|
|
||||||
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
|
if let navigationStripView = self.navigationStrip.view as? MediaNavigationStripComponent.View {
|
||||||
navigationStripView.updateCurrentItemProgress(value: progress, isBuffering: isBuffering, transition: .immediate)
|
navigationStripView.updateCurrentItemProgress(value: progress, isBuffering: isBuffering, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isBufferingUpdated {
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
if progress >= 1.0 && canSwitch && !visibleItem.requestedNext {
|
if progress >= 1.0 && canSwitch && !visibleItem.requestedNext {
|
||||||
visibleItem.requestedNext = true
|
visibleItem.requestedNext = true
|
||||||
|
|
||||||
@ -1102,7 +1109,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
strings: component.strings,
|
strings: component.strings,
|
||||||
peer: component.slice.peer,
|
peer: component.slice.peer,
|
||||||
item: item.storyItem,
|
item: item.storyItem,
|
||||||
audioMode: component.audioMode
|
audioMode: component.audioMode,
|
||||||
|
isVideoBuffering: visibleItem.isBuffering
|
||||||
)),
|
)),
|
||||||
environment: {
|
environment: {
|
||||||
itemEnvironment
|
itemEnvironment
|
||||||
@ -1761,10 +1769,10 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
var isUnsupported = false
|
var isUnsupported = false
|
||||||
var disabledPlaceholder: String?
|
var disabledPlaceholder: String?
|
||||||
if component.slice.peer.isService {
|
if component.slice.peer.isService {
|
||||||
disabledPlaceholder = "You can't reply to this story"
|
disabledPlaceholder = component.strings.Story_FooterReplyUnavailable
|
||||||
} else if case .unsupported = component.slice.item.storyItem.media {
|
} else if case .unsupported = component.slice.item.storyItem.media {
|
||||||
isUnsupported = true
|
isUnsupported = true
|
||||||
disabledPlaceholder = "You can't reply to this story"
|
disabledPlaceholder = component.strings.Story_FooterReplyUnavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
|
var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
|
||||||
@ -1779,7 +1787,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
strings: component.strings,
|
strings: component.strings,
|
||||||
style: .story,
|
style: .story,
|
||||||
placeholder: "Reply Privately...",
|
placeholder: component.strings.Story_InputPlaceholderReplyPrivately,
|
||||||
maxLength: 4096,
|
maxLength: 4096,
|
||||||
queryTypes: [.mention, .emoji],
|
queryTypes: [.mention, .emoji],
|
||||||
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
||||||
@ -2120,7 +2128,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
actionSheet.setItemGroups([
|
actionSheet.setItemGroups([
|
||||||
ActionSheetItemGroup(items: [
|
ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: "Delete Story", color: .destructive, action: { [weak self, weak actionSheet] in
|
ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
|
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
@ -2463,9 +2471,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
let tooltipText: String
|
let tooltipText: String
|
||||||
if component.slice.peer.id == component.context.account.peerId {
|
if component.slice.peer.id == component.context.account.peerId {
|
||||||
tooltipText = "Only people from your close friends list will see this story."
|
tooltipText = component.strings.Story_TooltipPrivacyCloseFriendsMy
|
||||||
} else {
|
} else {
|
||||||
tooltipText = "You are seeing this story because you have\nbeen added to \(component.slice.peer.compactDisplayTitle)'s list of close friends."
|
tooltipText = component.strings.Story_TooltipPrivacyCloseFriends(component.slice.peer.compactDisplayTitle).string
|
||||||
}
|
}
|
||||||
let tooltipScreen = TooltipScreen(
|
let tooltipScreen = TooltipScreen(
|
||||||
account: component.context.account,
|
account: component.context.account,
|
||||||
@ -2541,6 +2549,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(
|
let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
|
strings: component.strings,
|
||||||
peer: component.slice.peer,
|
peer: component.slice.peer,
|
||||||
timestamp: component.slice.item.storyItem.timestamp,
|
timestamp: component.slice.item.storyItem.timestamp,
|
||||||
counters: counters,
|
counters: counters,
|
||||||
@ -2679,6 +2688,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
component: AnyComponent(StoryContentCaptionComponent(
|
component: AnyComponent(StoryContentCaptionComponent(
|
||||||
externalState: captionItem.externalState,
|
externalState: captionItem.externalState,
|
||||||
context: component.context,
|
context: component.context,
|
||||||
|
strings: component.strings,
|
||||||
text: component.slice.item.storyItem.text,
|
text: component.slice.item.storyItem.text,
|
||||||
entities: component.slice.item.storyItem.entities,
|
entities: component.slice.item.storyItem.entities,
|
||||||
entityFiles: component.slice.item.entityFiles,
|
entityFiles: component.slice.item.entityFiles,
|
||||||
@ -2923,10 +2933,10 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message])
|
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message])
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
|
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
|
||||||
if let animation, let self {
|
if let animation, let self, let component = self.component {
|
||||||
let controller = UndoOverlayController(
|
let controller = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .sticker(context: context, file: animation, loop: false, title: nil, text: "Reaction Sent.", undoText: "View in Chat", customAction: { [weak self] in
|
content: .sticker(context: context, file: animation, loop: false, title: nil, text: component.strings.Story_ToastReactionSent, undoText: component.strings.Story_ToastViewInChat, customAction: { [weak self] in
|
||||||
if let messageId = messageIds.first, let self {
|
if let messageId = messageIds.first, let self {
|
||||||
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
|
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
|
||||||
}
|
}
|
||||||
@ -3153,17 +3163,17 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
if privacy.base == .contacts {
|
if privacy.base == .contacts {
|
||||||
text = "This story is shown to all your contacts."
|
text = component.strings.Story_PrivacyTooltipContacts
|
||||||
} else if privacy.base == .closeFriends {
|
} else if privacy.base == .closeFriends {
|
||||||
text = "This story is shown to your close friends."
|
text = component.strings.Story_PrivacyTooltipCloseFriends
|
||||||
} else if privacy.base == .nobody {
|
} else if privacy.base == .nobody {
|
||||||
if !privacy.additionallyIncludePeers.isEmpty {
|
if !privacy.additionallyIncludePeers.isEmpty {
|
||||||
text = "This story is shown to selected contacts."
|
text = component.strings.Story_PrivacyTooltipSelectedContacts
|
||||||
} else {
|
} else {
|
||||||
text = "This story is shown only to you."
|
text = component.strings.Story_PrivacyTooltipNobody
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text = "This story is shown to everyone."
|
text = component.strings.Story_PrivacyTooltipEveryone
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -3560,20 +3570,23 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let saveScreen = SaveProgressScreen(context: component.context, content: .progress("Saving", 0.0))
|
let saveScreen = SaveProgressScreen(context: component.context, content: .progress(component.strings.Story_TooltipSaving, 0.0))
|
||||||
component.controller()?.present(saveScreen, in: .current)
|
component.controller()?.present(saveScreen, in: .current)
|
||||||
|
|
||||||
|
let stringSaving = component.strings.Story_TooltipSaving
|
||||||
|
let stringSaved = component.strings.Story_TooltipSaved
|
||||||
|
|
||||||
let disposable = (saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: component.slice.item.storyItem.media._asMedia()))
|
let disposable = (saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: component.slice.item.storyItem.media._asMedia()))
|
||||||
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
|
|> deliverOnMainQueue).start(next: { [weak saveScreen] progress in
|
||||||
guard let saveScreen else {
|
guard let saveScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
saveScreen.content = .progress("Saving", progress)
|
saveScreen.content = .progress(stringSaving, progress)
|
||||||
}, completed: { [weak saveScreen] in
|
}, completed: { [weak saveScreen] in
|
||||||
guard let saveScreen else {
|
guard let saveScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
saveScreen.content = .completion("Saved")
|
saveScreen.content = .completion(stringSaved)
|
||||||
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
Queue.mainQueue().after(3.0, { [weak saveScreen] in
|
||||||
saveScreen?.dismiss()
|
saveScreen?.dismiss()
|
||||||
})
|
})
|
||||||
@ -3624,28 +3637,24 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let privacyText: String
|
let privacyText: String
|
||||||
switch component.slice.item.storyItem.privacy?.base {
|
switch component.slice.item.storyItem.privacy?.base {
|
||||||
case .closeFriends:
|
case .closeFriends:
|
||||||
privacyText = "Close Friends"
|
privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends
|
||||||
case .contacts:
|
case .contacts:
|
||||||
if additionalCount != 0 {
|
if additionalCount != 0 {
|
||||||
privacyText = "Contacts (-\(additionalCount))"
|
privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string
|
||||||
} else {
|
} else {
|
||||||
privacyText = "Contacts"
|
privacyText = component.strings.Story_ContextPrivacy_LabelContacts
|
||||||
}
|
}
|
||||||
case .nobody:
|
case .nobody:
|
||||||
if additionalCount != 0 {
|
if additionalCount != 0 {
|
||||||
if additionalCount == 1 {
|
privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount))
|
||||||
privacyText = "\(additionalCount) Person"
|
|
||||||
} else {
|
} else {
|
||||||
privacyText = "\(additionalCount) People"
|
privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe
|
||||||
}
|
|
||||||
} else {
|
|
||||||
privacyText = "Only Me"
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
privacyText = "Everyone"
|
privacyText = component.strings.Story_ContextPrivacy_LabelEveryone
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Who can see", textLayout: .secondLineWithValue(privacyText), icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Privacy, textLayout: .secondLineWithValue(privacyText), icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3656,7 +3665,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.openItemPrivacySettings()
|
self.openItemPrivacySettings()
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Edit Story", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3669,7 +3678,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? "Remove from Profile" : "Save to Profile", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromProfile : component.strings.Story_Context_SaveToProfile, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Check" : "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Chat/Context Menu/Check" : "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3684,7 +3693,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if component.slice.item.storyItem.isPinned {
|
if component.slice.item.storyItem.isPinned {
|
||||||
self.component?.presentController(UndoOverlayController(
|
self.component?.presentController(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .info(title: nil, text: "Story removed from your profile", timeout: nil),
|
content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -3692,7 +3701,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
self.component?.presentController(UndoOverlayController(
|
self.component?.presentController(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .info(title: "Story saved to your profile", text: "Saved stories can be viewed by others on your profile until you remove them.", timeout: nil),
|
content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -3700,12 +3709,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
let saveText: String
|
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||||
if case .file = component.slice.item.storyItem.media {
|
|
||||||
saveText = "Save Video"
|
|
||||||
} else {
|
|
||||||
saveText = "Save Image"
|
|
||||||
}
|
|
||||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
@ -3718,7 +3722,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
})))
|
})))
|
||||||
|
|
||||||
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) {
|
if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3737,7 +3741,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
component.presentController(UndoOverlayController(
|
component.presentController(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .linkCopied(text: "Link copied."),
|
content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -3745,7 +3749,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3782,7 +3786,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
tipSignal = packsPromise.get()
|
tipSignal = packsPromise.get()
|
||||||
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
||||||
if packReferences.count > 1 {
|
if packReferences.count > 1 {
|
||||||
return .single(.animatedEmoji(text: "This story contains stickers from [\(packReferences.count) packs]().", arguments: nil, file: nil, action: action))
|
let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
|
||||||
|
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
|
||||||
} else if let reference = packReferences.first {
|
} else if let reference = packReferences.first {
|
||||||
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||||
|> filter { result in
|
|> filter { result in
|
||||||
@ -3796,7 +3801,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if case let .result(info, items, _) = result {
|
if case let .result(info, items, _) = result {
|
||||||
let isEmoji = info.flags.contains(.isEmoji)
|
let isEmoji = info.flags.contains(.isEmoji)
|
||||||
let tip: ContextController.Tip = .animatedEmoji(
|
let tip: ContextController.Tip = .animatedEmoji(
|
||||||
text: isEmoji ? "This story contains\n#[\(info.title)]() emoji." : "This story contains\n#[\(info.title)]() stickers.",
|
text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
|
||||||
arguments: TextNodeWithEntities.Arguments(
|
arguments: TextNodeWithEntities.Arguments(
|
||||||
context: context,
|
context: context,
|
||||||
cache: context.animationCache,
|
cache: context.animationCache,
|
||||||
@ -3854,7 +3859,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers)
|
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers)
|
||||||
|
|
||||||
if !component.slice.peer.isService {
|
if !component.slice.peer.isService {
|
||||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify About Stories" : "Do Not Notify About Stories", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: isMuted ? component.strings.StoryFeed_ContextNotifyOn : component.strings.StoryFeed_ContextNotifyOff, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3876,7 +3881,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: "You will now get a notification whenever **\(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
|
], title: nil, text: component.strings.StoryFeed_TooltipNotifyOn(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -3890,7 +3895,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: "You will no longer receive a notification when **\(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
|
], title: nil, text: component.strings.StoryFeed_TooltipNotifyOff(component.slice.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -3903,7 +3908,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
isHidden = storiesHidden
|
isHidden = storiesHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: isHidden ? "Unhide Stories" : "Hide Stories", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3914,7 +3919,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden)
|
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden)
|
||||||
|
|
||||||
let text = !isHidden ? "Stories from **\(component.slice.peer.compactDisplayTitle)** will now be shown in Archived Chats." : "Stories from **\(component.slice.peer.compactDisplayTitle)** will now be shown in Chats."
|
let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string
|
||||||
let tooltipScreen = TooltipScreen(
|
let tooltipScreen = TooltipScreen(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
account: component.context.account,
|
account: component.context.account,
|
||||||
@ -3923,7 +3928,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0),
|
style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0),
|
||||||
icon: .peer(peer: component.slice.peer, isStory: true),
|
icon: .peer(peer: component.slice.peer, isStory: true),
|
||||||
action: TooltipScreen.Action(
|
action: TooltipScreen.Action(
|
||||||
title: "Undo",
|
title: component.strings.Undo_Undo,
|
||||||
action: {
|
action: {
|
||||||
component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden)
|
component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden)
|
||||||
}
|
}
|
||||||
@ -3946,7 +3951,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !component.slice.item.storyItem.isForwardingDisabled {
|
if !component.slice.item.storyItem.isForwardingDisabled {
|
||||||
let saveText: String = "Save to Gallery"
|
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
@ -3960,7 +3965,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) {
|
if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
a(.default)
|
a(.default)
|
||||||
@ -3979,7 +3984,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
component.presentController(UndoOverlayController(
|
component.presentController(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .linkCopied(text: "Link copied."),
|
content: .linkCopied(text: component.strings.Story_ToastLinkCopied),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -3987,20 +3992,10 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
/*items.append(.action(ContextMenuActionItem(text: "Share", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.sendMessageContext.performShareAction(view: self)
|
|
||||||
})))*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !component.slice.peer.isService {
|
if !component.slice.peer.isService {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Report", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Report, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, a in
|
}, action: { [weak self] c, a in
|
||||||
guard let self, let component = self.component, let controller = component.controller() else {
|
guard let self, let component = self.component, let controller = component.controller() else {
|
||||||
@ -4060,7 +4055,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
tipSignal = packsPromise.get()
|
tipSignal = packsPromise.get()
|
||||||
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
||||||
if packReferences.count > 1 {
|
if packReferences.count > 1 {
|
||||||
return .single(.animatedEmoji(text: "This story contains stickers from [\(packReferences.count) packs]().", arguments: nil, file: nil, action: action))
|
let valueText = component.strings.Story_Context_EmbeddedStickersValue(Int32(packReferences.count))
|
||||||
|
return .single(.animatedEmoji(text: component.strings.Story_Context_EmbeddedStickers(valueText).string, arguments: nil, file: nil, action: action))
|
||||||
} else if let reference = packReferences.first {
|
} else if let reference = packReferences.first {
|
||||||
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||||
|> filter { result in
|
|> filter { result in
|
||||||
@ -4074,7 +4070,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if case let .result(info, items, _) = result {
|
if case let .result(info, items, _) = result {
|
||||||
let isEmoji = info.flags.contains(.isEmoji)
|
let isEmoji = info.flags.contains(.isEmoji)
|
||||||
let tip: ContextController.Tip = .animatedEmoji(
|
let tip: ContextController.Tip = .animatedEmoji(
|
||||||
text: isEmoji ? "This story contains\n#[\(info.title)]() emoji." : "This story contains\n#[\(info.title)]() stickers.",
|
text: isEmoji ? component.strings.Story_Context_EmbeddedEmojiPack(info.title).string : component.strings.Story_Context_EmbeddedStickerPack(info.title).string,
|
||||||
arguments: TextNodeWithEntities.Arguments(
|
arguments: TextNodeWithEntities.Arguments(
|
||||||
context: context,
|
context: context,
|
||||||
cache: context.animationCache,
|
cache: context.animationCache,
|
||||||
@ -4121,7 +4117,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let tooltipScreen = TooltipScreen(
|
let tooltipScreen = TooltipScreen(
|
||||||
account: component.context.account,
|
account: component.context.account,
|
||||||
sharedContext: component.context.sharedContext,
|
sharedContext: component.context.sharedContext,
|
||||||
text: .plain(text: "This video has no sound"), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: nil).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
|
text: .plain(text: component.strings.Story_TooltipVideoHasNoSound), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: nil).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
|
||||||
return .dismiss(consume: true)
|
return .dismiss(consume: true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -344,11 +344,11 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let text = isScheduled ? "Message Scheduled" : "Message Sent"
|
let text = isScheduled ? presentationData.strings.Story_TooltipMessageScheduled : presentationData.strings.Story_TooltipMessageSent
|
||||||
|
|
||||||
let tooltipScreen = UndoOverlayController(
|
let tooltipScreen = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .actionSucceeded(title: "", text: text, cancel: messageId != nil ? "View in Chat" : "", destructive: false),
|
content: .actionSucceeded(title: "", text: text, cancel: messageId != nil ? presentationData.strings.Story_ToastViewInChat : "", destructive: false),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { [weak view, weak self] action in
|
action: { [weak view, weak self] action in
|
||||||
@ -881,7 +881,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
actionSheet.setItemGroups([
|
actionSheet.setItemGroups([
|
||||||
ActionSheetItemGroup(items: [
|
ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: "Copy Link", color: .accent, action: { [weak self, weak view, weak actionSheet] in
|
ActionSheetButtonItem(title: presentationData.strings.Story_Context_CopyLink, color: .accent, action: { [weak self, weak view, weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
|
|
||||||
guard let self, let view else {
|
guard let self, let view else {
|
||||||
@ -911,7 +911,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
} else {
|
} else {
|
||||||
var preferredAction: ShareControllerPreferredAction?
|
var preferredAction: ShareControllerPreferredAction?
|
||||||
if focusedItem.storyItem.isPublic && !component.slice.peer.isService {
|
if focusedItem.storyItem.isPublic && !component.slice.peer.isService {
|
||||||
preferredAction = .custom(action: ShareControllerAction(title: "Copy Link", action: {
|
preferredAction = .custom(action: ShareControllerAction(title: component.strings.Story_Context_CopyLink, action: {
|
||||||
let _ = ((component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id))
|
let _ = ((component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id))
|
||||||
|> deliverOnMainQueue).start(next: { link in
|
|> deliverOnMainQueue).start(next: { link in
|
||||||
if let link {
|
if let link {
|
||||||
@ -920,7 +920,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
component.presentController(UndoOverlayController(
|
component.presentController(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .linkCopied(text: "Link copied."),
|
content: .linkCopied(text: presentationData.strings.Story_ToastLinkCopied),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
@ -1025,7 +1025,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
component.presentController(UndoOverlayController(
|
component.presentController(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .linkCopied(text: "Link copied."),
|
content: .linkCopied(text: presentationData.strings.Story_ToastLinkCopied),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: false,
|
animateInAsReplacement: false,
|
||||||
action: { _ in return false }
|
action: { _ in return false }
|
||||||
|
@ -685,6 +685,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(StoryFooterPanelComponent(
|
component: AnyComponent(StoryFooterPanelComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
|
strings: component.strings,
|
||||||
storyItem: component.storyItem,
|
storyItem: component.storyItem,
|
||||||
externalViews: externalViews,
|
externalViews: externalViews,
|
||||||
expandFraction: dismissFraction,
|
expandFraction: dismissFraction,
|
||||||
|
@ -10,9 +10,11 @@ import TelegramCore
|
|||||||
import MoreHeaderButton
|
import MoreHeaderButton
|
||||||
import SemanticStatusNode
|
import SemanticStatusNode
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
public final class StoryFooterPanelComponent: Component {
|
public final class StoryFooterPanelComponent: Component {
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
|
public let strings: PresentationStrings
|
||||||
public let storyItem: EngineStoryItem?
|
public let storyItem: EngineStoryItem?
|
||||||
public let externalViews: EngineStoryItem.Views?
|
public let externalViews: EngineStoryItem.Views?
|
||||||
public let expandFraction: CGFloat
|
public let expandFraction: CGFloat
|
||||||
@ -22,6 +24,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
strings: PresentationStrings,
|
||||||
storyItem: EngineStoryItem?,
|
storyItem: EngineStoryItem?,
|
||||||
externalViews: EngineStoryItem.Views?,
|
externalViews: EngineStoryItem.Views?,
|
||||||
expandFraction: CGFloat,
|
expandFraction: CGFloat,
|
||||||
@ -30,6 +33,7 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
moreAction: @escaping (UIView, ContextGesture?) -> Void
|
moreAction: @escaping (UIView, ContextGesture?) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.strings = strings
|
||||||
self.storyItem = storyItem
|
self.storyItem = storyItem
|
||||||
self.externalViews = externalViews
|
self.externalViews = externalViews
|
||||||
self.expandViewStats = expandViewStats
|
self.expandViewStats = expandViewStats
|
||||||
@ -42,6 +46,9 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.storyItem != rhs.storyItem {
|
if lhs.storyItem != rhs.storyItem {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -200,10 +207,9 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
|
|
||||||
statusNode.transitionToState(.progress(value: CGFloat(max(0.08, self.uploadProgress)), cancelEnabled: true, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 0.0, lineWidth: 2.0)))
|
statusNode.transitionToState(.progress(value: CGFloat(max(0.08, self.uploadProgress)), cancelEnabled: true, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 0.0, lineWidth: 2.0)))
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let uploadingTextSize = uploadingText.update(
|
let uploadingTextSize = uploadingText.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(Text(text: "Uploading...", font: Font.regular(15.0), color: .white)),
|
component: AnyComponent(Text(text: component.strings.Story_Footer_Uploading, font: Font.regular(15.0), color: .white)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||||
)
|
)
|
||||||
@ -260,11 +266,9 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
|
|
||||||
let viewsText: String
|
let viewsText: String
|
||||||
if viewCount == 0 {
|
if viewCount == 0 {
|
||||||
viewsText = "No Views"
|
viewsText = component.strings.Story_Footer_NoViews
|
||||||
} else if viewCount == 1 {
|
|
||||||
viewsText = "1 view"
|
|
||||||
} else {
|
} else {
|
||||||
viewsText = "\(viewCount) views"
|
viewsText = component.strings.Story_Footer_Views(Int32(viewCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.viewStatsButton.isEnabled = viewCount != 0
|
self.viewStatsButton.isEnabled = viewCount != 0
|
||||||
|
@ -778,13 +778,12 @@ public final class StoryPeerListItemComponent: Component {
|
|||||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction))
|
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction))
|
||||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction))
|
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction))
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let titleString: String
|
let titleString: String
|
||||||
if component.peer.id == component.context.account.peerId {
|
if component.peer.id == component.context.account.peerId {
|
||||||
if let ringAnimation = component.ringAnimation, case .progress = ringAnimation {
|
if let ringAnimation = component.ringAnimation, case .progress = ringAnimation {
|
||||||
titleString = "Uploading..."
|
titleString = component.strings.StoryFeed_MyUploading
|
||||||
} else {
|
} else {
|
||||||
titleString = "My story"
|
titleString = component.strings.StoryFeed_MyStory
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
titleString = component.peer.compactDisplayTitle.trimmingCharacters(in: .whitespacesAndNewlines)
|
titleString = component.peer.compactDisplayTitle.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
@ -89,6 +89,7 @@ private final class ShapeImageView: UIView {
|
|||||||
|
|
||||||
public final class StorySetIndicatorComponent: Component {
|
public final class StorySetIndicatorComponent: Component {
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
|
public let strings: PresentationStrings
|
||||||
public let peer: EnginePeer
|
public let peer: EnginePeer
|
||||||
public let items: [EngineStoryItem]
|
public let items: [EngineStoryItem]
|
||||||
public let hasUnseen: Bool
|
public let hasUnseen: Bool
|
||||||
@ -99,6 +100,7 @@ public final class StorySetIndicatorComponent: Component {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
strings: PresentationStrings,
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
items: [EngineStoryItem],
|
items: [EngineStoryItem],
|
||||||
hasUnseen: Bool,
|
hasUnseen: Bool,
|
||||||
@ -108,6 +110,7 @@ public final class StorySetIndicatorComponent: Component {
|
|||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.items = items
|
self.items = items
|
||||||
self.hasUnseen = hasUnseen
|
self.hasUnseen = hasUnseen
|
||||||
@ -118,6 +121,9 @@ public final class StorySetIndicatorComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: StorySetIndicatorComponent, rhs: StorySetIndicatorComponent) -> Bool {
|
public static func ==(lhs: StorySetIndicatorComponent, rhs: StorySetIndicatorComponent) -> Bool {
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.items != rhs.items {
|
if lhs.items != rhs.items {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -378,14 +384,11 @@ public final class StorySetIndicatorComponent: Component {
|
|||||||
self.imageView.setNeedsDisplay()
|
self.imageView.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let textValue: String
|
let textValue: String
|
||||||
if component.totalCount == 0 {
|
if component.totalCount == 0 {
|
||||||
textValue = ""
|
textValue = ""
|
||||||
} else if component.totalCount == 1 {
|
|
||||||
textValue = "1 story"
|
|
||||||
} else {
|
} else {
|
||||||
textValue = "\(component.totalCount) stories"
|
textValue = component.strings.Story_Footer_Views(Int32(component.totalCount))
|
||||||
}
|
}
|
||||||
let textSize = self.text.update(
|
let textSize = self.text.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "smoothGradient 0.4.png",
|
"filename" : "smoothGradient 0.6.png",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
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
|
return
|
||||||
}
|
}
|
||||||
if let story = message.associatedStories[storyId], story.data.isEmpty {
|
if let story = message.associatedStories[storyId], story.data.isEmpty {
|
||||||
//TODO:localize
|
self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: self.presentationData.strings.Story_TooltipExpired, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: "This story is no longer available", timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: false)
|
let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: true)
|
||||||
let _ = (storyContent.state
|
let _ = (storyContent.state
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
@ -137,15 +137,14 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
titleString = arguments.strings.User_DeletedAccount
|
titleString = arguments.strings.User_DeletedAccount
|
||||||
}
|
}
|
||||||
//TODO:localize
|
|
||||||
isText = false
|
isText = false
|
||||||
if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty {
|
if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty {
|
||||||
isExpiredStory = true
|
isExpiredStory = true
|
||||||
textString = NSAttributedString(string: "Expired story")
|
textString = NSAttributedString(string: arguments.strings.Chat_ReplyExpiredStory)
|
||||||
isMedia = false
|
isMedia = false
|
||||||
} else {
|
} else {
|
||||||
isStory = true
|
isStory = true
|
||||||
textString = NSAttributedString(string: "Story")
|
textString = NSAttributedString(string: arguments.strings.Chat_ReplyStory)
|
||||||
isMedia = true
|
isMedia = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,9 +194,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
//TODO:localize
|
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_StoryMentionAction, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "View Story", font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 186.0)
|
let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 186.0)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||||||
if let story {
|
if let story {
|
||||||
let navigationController = params.navigationController
|
let navigationController = params.navigationController
|
||||||
let context = params.context
|
let context = params.context
|
||||||
let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: story.storyId, readGlobally: story.isMention)
|
let storyContent = SingleStoryContentContextImpl(context: params.context, storyId: story.storyId, readGlobally: true)
|
||||||
let _ = (storyContent.state
|
let _ = (storyContent.state
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
|
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
|
||||||
|
@ -814,7 +814,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { exists in
|
|> deliverOnMainQueue).start(next: { exists in
|
||||||
if exists {
|
if exists {
|
||||||
let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id), readGlobally: false)
|
let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: id), readGlobally: true)
|
||||||
let _ = (storyContent.state
|
let _ = (storyContent.state
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
|
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
|
||||||
@ -833,12 +833,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
navigationController?.pushViewController(storyContainerScreen)
|
navigationController?.pushViewController(storyContainerScreen)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
//TODO:localize
|
|
||||||
var elevatedLayout = true
|
var elevatedLayout = true
|
||||||
if case .chat = urlContext {
|
if case .chat = urlContext {
|
||||||
elevatedLayout = false
|
elevatedLayout = false
|
||||||
}
|
}
|
||||||
present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "story_expired", scale: 0.066, colors: [:], title: nil, text: "This story does not exist", customUndoText: nil, timeout: nil), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in
|
present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "story_expired", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.Story_TooltipExpired, customUndoText: nil, timeout: nil), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in
|
||||||
return true
|
return true
|
||||||
}), nil)
|
}), nil)
|
||||||
}
|
}
|
||||||
|
@ -1050,7 +1050,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
markupNode = current
|
markupNode = current
|
||||||
} else {
|
} else {
|
||||||
markupNode = AvatarVideoNode(context: self.context)
|
markupNode = AvatarVideoNode(context: self.context)
|
||||||
self.insertSubnode(markupNode, aboveSubnode: self.avatarNode)
|
self.avatarNode.contentNode.addSubnode(markupNode)
|
||||||
self.markupNode = markupNode
|
self.markupNode = markupNode
|
||||||
}
|
}
|
||||||
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
|
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
|
||||||
@ -1083,7 +1083,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
shape.path = maskPath.cgPath
|
shape.path = maskPath.cgPath
|
||||||
videoNode.layer.mask = shape
|
videoNode.layer.mask = shape
|
||||||
|
|
||||||
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
self.avatarNode.contentNode.addSubnode(videoNode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let markupNode = self.markupNode {
|
if let markupNode = self.markupNode {
|
||||||
@ -1107,15 +1107,15 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let markupNode = self.markupNode {
|
if let markupNode = self.markupNode {
|
||||||
markupNode.frame = self.avatarNode.frame
|
markupNode.frame = self.avatarNode.bounds
|
||||||
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate)
|
markupNode.updateLayout(size: self.avatarNode.bounds.size, cornerRadius: avatarCornerRadius, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let videoNode = self.videoNode {
|
if let videoNode = self.videoNode {
|
||||||
if self.canAttachVideo {
|
if self.canAttachVideo {
|
||||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
videoNode.updateLayout(size: self.avatarNode.bounds.size, transition: .immediate)
|
||||||
}
|
}
|
||||||
videoNode.frame = self.avatarNode.contentNode.bounds
|
videoNode.frame = self.avatarNode.bounds
|
||||||
|
|
||||||
if isEditing != videoNode.canAttachContent {
|
if isEditing != videoNode.canAttachContent {
|
||||||
videoNode.canAttachContent = isEditing && self.canAttachVideo
|
videoNode.canAttachContent = isEditing && self.canAttachVideo
|
||||||
|
@ -956,8 +956,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
let title: String
|
let title: String
|
||||||
switch key {
|
switch key {
|
||||||
case .stories:
|
case .stories:
|
||||||
//TODO:localize
|
title = presentationData.strings.PeerInfo_PaneStories
|
||||||
title = "Stories"
|
|
||||||
case .media:
|
case .media:
|
||||||
title = presentationData.strings.PeerInfo_PaneMedia
|
title = presentationData.strings.PeerInfo_PaneMedia
|
||||||
case .files:
|
case .files:
|
||||||
|
@ -792,8 +792,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyStories, icon: PresentationResourcesSettings.stories, action: {
|
||||||
items[.stories]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "My Stories", icon: PresentationResourcesSettings.stories, action: {
|
|
||||||
interaction.openSettings(.stories)
|
interaction.openSettings(.stories)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -141,8 +141,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
} else if content.type == "video" {
|
} else if content.type == "video" {
|
||||||
text = stringForMediaKind(.video, strings: self.strings).0.string
|
text = stringForMediaKind(.video, strings: self.strings).0.string
|
||||||
} else if content.type == "telegram_story" {
|
} else if content.type == "telegram_story" {
|
||||||
//TODO:localize
|
text = stringForMediaKind(.story, strings: self.strings).0.string
|
||||||
text = "Story"
|
|
||||||
} else if let _ = content.image {
|
} else if let _ = content.image {
|
||||||
text = stringForMediaKind(.image, strings: self.strings).0.string
|
text = stringForMediaKind(.image, strings: self.strings).0.string
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user