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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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