diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index f7e28535f6..bc4690c534 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9711,3 +9711,7 @@ Sorry for the inconvenience."; "Story.Privacy.SaveSettings" = "Save Settings"; "Story.Privacy.PostStory" = "Post Story"; + +"Story.Editor.Draft" = "Draft"; +"Story.Views.ViewsExpired" = "List of viewers becomes unavailable **24 hours** after the story expires."; +"Story.Views.NoViews" = "Nobody has viewed\nyour story yet."; diff --git a/submodules/AvatarNode/Sources/PeerAvatar.swift b/submodules/AvatarNode/Sources/PeerAvatar.swift index b022dcd08d..4a034a8f53 100644 --- a/submodules/AvatarNode/Sources/PeerAvatar.swift +++ b/submodules/AvatarNode/Sources/PeerAvatar.swift @@ -371,7 +371,7 @@ public enum AvatarBackgroundColor { case violet } -public func generateAvatarImage(size: CGSize, icon: UIImage?, iconScale: CGFloat = 1.0, cornerRadius: CGFloat? = nil, circleCorners: Bool = false, color: AvatarBackgroundColor) -> UIImage? { +public func generateAvatarImage(size: CGSize, icon: UIImage?, iconScale: CGFloat = 1.0, cornerRadius: CGFloat? = nil, circleCorners: Bool = false, color: AvatarBackgroundColor, customColors: [UIColor]? = nil) -> UIImage? { return generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.beginPath() @@ -405,10 +405,14 @@ public func generateAvatarImage(size: CGSize, icon: UIImage?, iconScale: CGFloat } let colorsArray: NSArray - if colorIndex == -1 { - colorsArray = AvatarNode.grayscaleColors.map(\.cgColor) as NSArray + if let customColors { + colorsArray = customColors.map(\.cgColor) as NSArray } else { - colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count].map(\.cgColor) as NSArray + if colorIndex == -1 { + colorsArray = AvatarNode.grayscaleColors.map(\.cgColor) as NSArray + } else { + colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count].map(\.cgColor) as NSArray + } } var locations: [CGFloat] = [1.0, 0.0] diff --git a/submodules/Camera/Sources/CameraDevice.swift b/submodules/Camera/Sources/CameraDevice.swift index 6dcdf411f3..96b0553fac 100644 --- a/submodules/Camera/Sources/CameraDevice.swift +++ b/submodules/Camera/Sources/CameraDevice.swift @@ -131,6 +131,10 @@ final class CameraDevice { device.activeVideoMinFrameDuration = targetFPS.duration device.activeVideoMaxFrameDuration = targetFPS.duration } + + if device.isLowLightBoostSupported { + device.automaticallyEnablesLowLightBoostWhenAvailable = true + } } } diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index c641ffcce9..fb52ce3c7e 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -207,13 +207,10 @@ final class CameraOutput: NSObject { } func configureVideoStabilization() { - if let videoDataOutputConnection = self.videoOutput.connection(with: .video), videoDataOutputConnection.isVideoStabilizationSupported { - videoDataOutputConnection.preferredVideoStabilizationMode = .standard -// if #available(iOS 13.0, *) { -// videoDataOutputConnection.preferredVideoStabilizationMode = .cinematicExtended -// } else { -// videoDataOutputConnection.preferredVideoStabilizationMode = .cinematic -// } + if let videoDataOutputConnection = self.videoOutput.connection(with: .video) { + if videoDataOutputConnection.isVideoStabilizationSupported { + videoDataOutputConnection.preferredVideoStabilizationMode = .standard + } } } diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index c32ad93b51..b23a9610c6 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -1650,6 +1650,8 @@ private final class DrawingScreenComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0 - (hasFlip ? 46.0 : 0.0), y: topInset)) .appear(.default(scale: true)) .disappear(.default(scale: true)) + .opacity(!controlsAreVisible ? 0.0 : 1.0) + .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil) ) } @@ -1682,6 +1684,7 @@ private final class DrawingScreenComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0 + (isFilled != nil ? 46.0 : 0.0), y: topInset)) .appear(.default(scale: true)) .disappear(.default(scale: true)) + .opacity(!controlsAreVisible ? 0.0 : 1.0) .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil) ) } diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index a21a409a3c..7e8528c3d3 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -205,13 +205,6 @@ public final class DrawingStickerEntityView: DrawingEntityView { break } -// switch image.imageOrientation { -// case .leftMirrored, .rightMirrored: -// context.scaleBy(x: -1, y: 1) -// default: -// context.scaleBy(x: 1, y: -1) -// } - context.draw(image.cgImage!, in: imageRect) } diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index 6bd096d1ca..c7be445f5e 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -693,7 +693,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate func getRenderImage() -> UIImage? { let rect = self.bounds - UIGraphicsBeginImageContextWithOptions(rect.size, false, 2.0) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 3.0) self.textView.drawHierarchy(in: rect, afterScreenUpdates: true) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -743,7 +743,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate entity.referenceDrawingSize = CGSize(width: itemSize * 4.0, height: itemSize * 4.0) entity.scale = scale entity.position = textPosition.offsetBy( - dx: (emojiTextPosition.x * cos(rotation) + emojiTextPosition.y * sin(rotation)) * scale, + dx: (emojiTextPosition.x * cos(rotation) - emojiTextPosition.y * sin(rotation)) * scale, dy: (emojiTextPosition.y * cos(rotation) + emojiTextPosition.x * sin(rotation)) * scale ) entity.rotation = rotation @@ -1174,7 +1174,8 @@ private class DrawingTextLayoutManager: NSLayoutManager { addPath.addLine(to: CGPoint(x: d.x + self.radius, y: d.y)) path.append(addPath) } - + last = cur + } else { last = cur } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index 29beed3d71..8deb0e25bf 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -27,6 +27,7 @@ enum MediaPickerGridItemContent: Equatable { final class MediaPickerGridItem: GridItem { let content: MediaPickerGridItemContent let interaction: MediaPickerInteraction + let strings: PresentationStrings let theme: PresentationTheme let selectable: Bool let enableAnimations: Bool @@ -34,9 +35,10 @@ final class MediaPickerGridItem: GridItem { let section: GridSection? = nil - init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) { + init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, strings: PresentationStrings, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) { self.content = content self.interaction = interaction + self.strings = strings self.theme = theme self.selectable = selectable self.enableAnimations = enableAnimations @@ -55,7 +57,7 @@ final class MediaPickerGridItem: GridItem { return node case let .draft(draft, index): let node = MediaPickerGridItemNode() - node.setup(interaction: self.interaction, draft: draft, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories) + node.setup(interaction: self.interaction, draft: draft, index: index, strings: self.strings, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories) return node } } @@ -71,7 +73,7 @@ final class MediaPickerGridItem: GridItem { case let .media(media, index): node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories) case let .draft(draft, index): - node.setup(interaction: self.interaction, draft: draft, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories) + node.setup(interaction: self.interaction, draft: draft, index: index, strings: self.strings, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations, stories: self.stories) } } } @@ -287,7 +289,7 @@ final class MediaPickerGridItemNode: GridItemNode { } } - func setup(interaction: MediaPickerInteraction, draft: MediaEditorDraft, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) { + func setup(interaction: MediaPickerInteraction, draft: MediaEditorDraft, index: Int, strings: PresentationStrings, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) { self.interaction = interaction self.theme = theme self.selectable = selectable @@ -310,7 +312,7 @@ final class MediaPickerGridItemNode: GridItemNode { } if self.draftNode.supernode == nil { - self.draftNode.attributedText = NSAttributedString(string: "Draft", font: Font.semibold(12.0), textColor: .white) + self.draftNode.attributedText = NSAttributedString(string: strings.Story_Editor_Draft, font: Font.semibold(12.0), textColor: .white) self.addSubnode(self.draftNode) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index b193d2aa94..09b0ec6a93 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -61,8 +61,8 @@ private struct MediaPickerGridEntry: Comparable, Identifiable { return lhs.stableId < rhs.stableId } - func item(context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme) -> MediaPickerGridItem { - return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme, selectable: self.selectable, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency, stories: self.stories) + func item(context: AccountContext, interaction: MediaPickerInteraction, strings: PresentationStrings, theme: PresentationTheme) -> MediaPickerGridItem { + return MediaPickerGridItem(content: self.content, interaction: interaction, strings: strings, theme: theme, selectable: self.selectable, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency, stories: self.stories) } } @@ -72,12 +72,12 @@ private struct MediaPickerGridTransaction { let updates: [GridNodeUpdateItem] let scrollToItem: GridNodeScrollToItem? - init(previousList: [MediaPickerGridEntry], list: [MediaPickerGridEntry], context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme, scrollToItem: GridNodeScrollToItem?) { + init(previousList: [MediaPickerGridEntry], list: [MediaPickerGridEntry], context: AccountContext, interaction: MediaPickerInteraction, strings: PresentationStrings, theme: PresentationTheme, scrollToItem: GridNodeScrollToItem?) { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list) self.deletions = deleteIndices - self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme), previousIndex: $0.2) } - self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme)) } + self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, strings: strings, theme: theme), previousIndex: $0.2) } + self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, strings: strings, theme: theme)) } self.scrollToItem = scrollToItem } @@ -671,7 +671,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { scrollToItem = GridNodeScrollToItem(index: entries.count - 1, position: .bottom(0.0), transition: .immediate, directionHint: .down, adjustForSection: false) } - let transaction = MediaPickerGridTransaction(previousList: previousEntries, list: entries, context: controller.context, interaction: interaction, theme: self.presentationData.theme, scrollToItem: scrollToItem) + let transaction = MediaPickerGridTransaction(previousList: previousEntries, list: entries, context: controller.context, interaction: interaction, strings: self.presentationData.strings, theme: self.presentationData.theme, scrollToItem: scrollToItem) self.enqueueTransaction(transaction) if !self.didSetReady { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index c2f5bac386..e9c159fffb 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -1956,7 +1956,7 @@ public class CameraScreen: ViewController { } func presentDualCameraTooltip() { - guard let sourceView = self.componentHost.findTaggedView(tag: dualButtonTag) else { + guard let sourceView = self.componentHost.findTaggedView(tag: dualButtonTag), self.cameraState.isDualCameraEnabled else { return } @@ -2131,6 +2131,8 @@ public class CameraScreen: ViewController { self.maybePresentTooltips() } else if case .notDetermined = self.cameraAuthorizationStatus { self.requestDeviceAccess() + } else if case .notDetermined = self.microphoneAuthorizationStatus { + self.requestDeviceAccess() } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 82f37f9eaa..10fe1f0fa5 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -253,6 +253,7 @@ public final class MediaEditor { self.subject = subject if let values { self.values = values + self.updateRenderChain() } else { self.values = MediaEditorValues( originalDimensions: subject.dimensions, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index 812aa64411..b4d38468db 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -12,6 +12,39 @@ import TelegramAnimatedStickerNode import YuvConversion import StickerResources +private func prerenderTextTransformations(entity: DrawingTextEntity, image: UIImage, colorSpace: CGColorSpace) -> MediaEditorComposerStaticEntity { + let imageSize = image.size + + let angle = -entity.rotation + let scale = entity.scale + + let rotatedSize = CGSize( + width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)), + height: abs(imageSize.width * sin(angle)) + abs(imageSize.height * cos(angle)) + ) + let newSize = CGSize(width: rotatedSize.width * scale, height: rotatedSize.height * scale) + + let newImage = generateImage(newSize, contextGenerator: { size, context in + context.setAllowsAntialiasing(true) + context.setShouldAntialias(true) + context.clear(CGRect(origin: .zero, size: size)) + context.translateBy(x: newSize.width * 0.5, y: newSize.height * 0.5) + context.rotate(by: angle) + context.scaleBy(x: scale, y: scale) + let drawRect = CGRect( + x: -imageSize.width * 0.5, + y: -imageSize.height * 0.5, + width: imageSize.width, + height: imageSize.height + ) + if let cgImage = image.cgImage { + context.draw(cgImage, in: drawRect) + } + })! + + return MediaEditorComposerStaticEntity(image: CIImage(image: newImage, options: [.colorSpace: colorSpace])!, position: entity.position, scale: 1.0, rotation: 0.0, baseSize: nil, baseScale: 0.333, mirrored: false) +} + func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, colorSpace: CGColorSpace, tintColor: UIColor? = nil) -> [MediaEditorComposerEntity] { if let entity = entity as? DrawingStickerEntity { let content: MediaEditorComposerStickerEntity.Content @@ -35,7 +68,9 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c return [MediaEditorComposerStaticEntity(image: image, position: CGPoint(x: entity.drawingSize.width * 0.5, y: entity.drawingSize.height * 0.5), scale: 1.0, rotation: 0.0, baseSize: entity.drawingSize, baseScale: nil, mirrored: false)] } else if let entity = entity as? DrawingTextEntity { var entities: [MediaEditorComposerEntity] = [] - entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: nil, baseScale: 0.5, mirrored: false)) + entities.append(prerenderTextTransformations(entity: entity, image: renderImage, colorSpace: colorSpace)) + +// entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: nil, baseScale: 0.333, mirrored: false)) if let renderSubEntities = entity.renderSubEntities { for subEntity in renderSubEntities { entities.append(contentsOf: composerEntitiesForDrawingEntity(account: account, entity: subEntity, colorSpace: colorSpace, tintColor: entity.color.toUIColor())) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 7624f578ef..9f22281ead 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -2740,7 +2740,27 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { [weak self] image, _ in if let self, let image { Queue.mainQueue().async { - self.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, .rectangle)), scale: 2.5) + func roundedImageWithTransparentCorners(image: UIImage, cornerRadius: CGFloat) -> UIImage? { + let rect = CGRect(origin: .zero, size: image.size) + UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) + let context = UIGraphicsGetCurrentContext() + + if let context = context { + let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) + + context.addPath(path.cgPath) + context.clip() + + image.draw(in: rect) + } + + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } + let updatedImage = roundedImageWithTransparentCorners(image: image, cornerRadius: 180.0)! + self.interaction?.insertEntity(DrawingStickerEntity(content: .image(updatedImage, .rectangle)), scale: 2.5) } } } diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift index 48b059b18f..b696a371ab 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift @@ -146,26 +146,26 @@ final class CategoryListItemComponent: Component { } if (self.component?.iconName != component.iconName || self.component?.color != component.color), let iconName = component.iconName { - let mappedColor: AvatarBackgroundColor + let colors: [UIColor] var iconScale: CGFloat = 1.0 switch component.color { case .blue: - mappedColor = .blue + colors = [UIColor(rgb: 0x4faaff), UIColor(rgb: 0x017aff)] case .yellow: - mappedColor = .yellow + colors = [UIColor(rgb: 0xffb643), UIColor(rgb: 0xf69a36)] case .green: - mappedColor = .green + colors = [UIColor(rgb: 0x87d93a), UIColor(rgb: 0x31b73b)] case .purple: - mappedColor = .purple + colors = [UIColor(rgb: 0xc36eff), UIColor(rgb: 0x8c61fa)] case .red: - mappedColor = .red + colors = [UIColor(rgb: 0xc36eff), UIColor(rgb: 0x8c61fa)] case .violet: - mappedColor = .violet + colors = [UIColor(rgb: 0xc36eff), UIColor(rgb: 0x8c61fa)] } iconScale = 1.0 - self.iconView.image = generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: iconName), color: .white), iconScale: iconScale, cornerRadius: 20.0, color: mappedColor) + self.iconView.image = generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: iconName), color: .white), iconScale: iconScale, cornerRadius: 20.0, color: .blue, customColors: colors.reversed()) } self.component = component diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 914f23e282..e369c6463d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1678,7 +1678,9 @@ public final class StoryItemSetContainerComponent: Component { self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData) } + var itemChanged = false if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id { + itemChanged = self.component != nil self.initializedOffset = false if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { @@ -2441,7 +2443,7 @@ public final class StoryItemSetContainerComponent: Component { transition.setAlpha(view: soundButtonView, alpha: soundAlpha) if isVideo { - headerRightOffset -= soundButtonSize.width + 16.0 + headerRightOffset -= soundButtonSize.width + 13.0 } } @@ -2460,7 +2462,7 @@ public final class StoryItemSetContainerComponent: Component { if let storyPrivacyIcon { let privacyIcon: ComponentView - var privacyIconTransition = transition + var privacyIconTransition: Transition = itemChanged ? .immediate : .easeInOut(duration: 0.2) if let current = self.privacyIcon { privacyIcon = current } else { @@ -3026,7 +3028,6 @@ public final class StoryItemSetContainerComponent: Component { } component.controller()?.push(controller) }), elevatedLayout: false, animateInAsReplacement: false, action: { _ in true }) - //strongSelf.currentUndoController = undoController component.controller()?.present(undoController, in: .current) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 09fa50b4c1..e7bee94391 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -15,6 +15,8 @@ import StoryFooterPanelComponent import PeerListItemComponent import AnimatedStickerComponent import AvatarNode +import Markdown +import ButtonComponent final class StoryItemSetViewListComponent: Component { final class AnimationHint { @@ -58,6 +60,7 @@ final class StoryItemSetViewListComponent: Component { let moreAction: (UIView, ContextGesture?) -> Void let openPeer: (EnginePeer) -> Void let openPeerStories: (EnginePeer, AvatarNode) -> Void + let openPremiumIntro: () -> Void init( externalState: ExternalState, @@ -76,7 +79,8 @@ final class StoryItemSetViewListComponent: Component { deleteAction: @escaping () -> Void, moreAction: @escaping (UIView, ContextGesture?) -> Void, openPeer: @escaping (EnginePeer) -> Void, - openPeerStories: @escaping (EnginePeer, AvatarNode) -> Void + openPeerStories: @escaping (EnginePeer, AvatarNode) -> Void, + openPremiumIntro: @escaping () -> Void ) { self.externalState = externalState self.context = context @@ -95,6 +99,7 @@ final class StoryItemSetViewListComponent: Component { self.moreAction = moreAction self.openPeer = openPeer self.openPeerStories = openPeerStories + self.openPremiumIntro = openPremiumIntro } static func ==(lhs: StoryItemSetViewListComponent, rhs: StoryItemSetViewListComponent) -> Bool { @@ -826,32 +831,39 @@ final class StoryItemSetViewListComponent: Component { transition: emptyTransition, component: AnyComponent(AnimatedStickerComponent( account: component.context.account, - animation: AnimatedStickerComponent.Animation(source: .bundle(name: "ChatListNoResults"), loop: true), + animation: AnimatedStickerComponent.Animation(source: .bundle(name: "Burn"), loop: true), size: CGSize(width: 140.0, height: 140.0) )), environment: {}, containerSize: CGSize(width: 140.0, height: 140.0) ) + let fontSize: CGFloat = 16.0 + let body = MarkdownAttributeSet(font: Font.regular(fontSize), textColor: component.theme.list.itemSecondaryTextColor) + let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemSecondaryTextColor) + let link = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemAccentColor) + let attributes = MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in nil }) + let text: String if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) { - text = "List of viewers isn’t available after\n24 hours of story expiration." + text = component.strings.Story_Views_ViewsExpired } else { - text = "Nobody has viewed\nyour story yet." + text = component.strings.Story_Views_NoViews } let textSize = emptyText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: component.theme.list.itemSecondaryTextColor)), + text: .markdown(text: text, attributes: attributes), horizontalAlignment: .center, maximumNumberOfLines: 0 )), environment: {}, - containerSize: CGSize(width: min(300.0, availableSize.width - 16.0 * 2.0), height: 1000.0) + containerSize: CGSize(width: min(330.0, availableSize.width - 16.0 * 2.0), height: 1000.0) ) - + let emptyContentSpacing: CGFloat = 20.0 - var emptyContentY = navigationBarFrame.minY + floor((availableSize.height - navigationBarFrame.minY - (emptyIconSize.height - emptyContentSpacing - textSize.height)) * 0.5) - 60.0 + let emptyContentHeight = emptyIconSize.height + emptyContentSpacing + textSize.height + var emptyContentY = navigationBarFrame.minY + floor((availableSize.height - navigationBarFrame.minY - emptyContentHeight) * 0.5) if let emptyIconView = emptyIcon.view { if emptyIconView.superview == nil { @@ -866,6 +878,7 @@ final class StoryItemSetViewListComponent: Component { self.insertSubview(emptyTextView, belowSubview: self.scrollView) } emptyTransition.setFrame(view: emptyTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: emptyContentY), size: textSize)) + emptyContentY += textSize.height + emptyContentSpacing * 2.0 } } else { if let emptyIcon = self.emptyIcon { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryPrivacyIconComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryPrivacyIconComponent.swift index 025e35588a..e5a898bd93 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryPrivacyIconComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryPrivacyIconComponent.swift @@ -56,7 +56,7 @@ final class StoryPrivacyIconComponent: Component { let colors: [CGColor] var icon: UIImage? - if let previousPrivacy, previousPrivacy != component.privacy { + if let previousPrivacy, previousPrivacy != component.privacy, !transition.animation.isImmediate { let disappearingBackgroundView = UIImageView(image: self.image) disappearingBackgroundView.frame = self.bounds self.insertSubview(disappearingBackgroundView, at: 0) diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 9dfc351541..0adb2fef2f 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -76,6 +76,12 @@ public final class TextFieldComponent: Component { } } + public enum FormatMenuAvailability: Equatable { + case available + case locked + case none + } + public let context: AccountContext public let strings: PresentationStrings public let externalState: ExternalState @@ -83,6 +89,8 @@ public final class TextFieldComponent: Component { public let textColor: UIColor public let insets: UIEdgeInsets public let hideKeyboard: Bool + public let formatMenuAvailability: FormatMenuAvailability + public let lockedFormatAction: () -> Void public let present: (ViewController) -> Void public let paste: (PasteData) -> Void @@ -94,6 +102,8 @@ public final class TextFieldComponent: Component { textColor: UIColor, insets: UIEdgeInsets, hideKeyboard: Bool, + formatMenuAvailability: FormatMenuAvailability, + lockedFormatAction: @escaping () -> Void, present: @escaping (ViewController) -> Void, paste: @escaping (PasteData) -> Void ) { @@ -104,6 +114,8 @@ public final class TextFieldComponent: Component { self.textColor = textColor self.insets = insets self.hideKeyboard = hideKeyboard + self.formatMenuAvailability = formatMenuAvailability + self.lockedFormatAction = lockedFormatAction self.present = present self.paste = paste } @@ -127,6 +139,9 @@ public final class TextFieldComponent: Component { if lhs.hideKeyboard != rhs.hideKeyboard { return false } + if lhs.formatMenuAvailability != rhs.formatMenuAvailability { + return false + } return true } @@ -386,8 +401,23 @@ public final class TextFieldComponent: Component { guard let component = self.component, !textView.attributedText.string.isEmpty && textView.selectedRange.length > 0 else { return UIMenu(children: suggestedActions) } - let strings = component.strings + + if case .none = component.formatMenuAvailability { + return UIMenu(children: suggestedActions) + } + + if case .locked = component.formatMenuAvailability { + var updatedActions = suggestedActions + let formatAction = UIAction(title: strings.TextFormat_Format, image: nil) { [weak self] action in + if let self { + self.component?.lockedFormatAction() + } + } + updatedActions.insert(formatAction, at: 1) + return UIMenu(children: updatedActions) + } + var actions: [UIAction] = [ UIAction(title: strings.TextFormat_Bold, image: nil) { [weak self] action in if let self { diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 8bda5b9955..f82c0e313e 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -249,7 +249,7 @@ public final class AccountContextImpl: AccountContext { self.account = account self.engine = TelegramEngine(account: account) - self.userLimits = EngineConfiguration.UserLimits(UserLimitsConfiguration.defaultValue) + self.userLimits = EngineConfiguration.UserLimits(UserLimitsConfiguration.defaultValue, isPremium: false) self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index e525d09eac..b45611a178 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -82,8 +82,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { var displayUndo = true var undoText = presentationData.strings.Undo_Undo - var undoTextColor = UIColor(rgb: 0x5ac8fa) - undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 1.0, saturation: 0.64, brightness: 1.08) + var undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0) if presentationData.theme.overallDarkAppearance { self.animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor @@ -174,16 +173,20 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) + let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) self.textNode.attributedText = attributedText if let customUndoText { - undoText = customUndoText displayUndo = true } else { displayUndo = false } self.originalRemainingSeconds = 4.5 + + if text.contains("](") { + isUserInteractionEnabled = true + } case let .succeed(text): self.avatarNode = nil self.iconNode = nil @@ -211,7 +214,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) + let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in return ("URL", contents) }), textAlignment: .natural) @@ -257,11 +260,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) + let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) self.textNode.attributedText = attributedText self.textNode.maximumNumberOfLines = 2 displayUndo = false self.originalRemainingSeconds = 3 + + if text.contains("](") { + isUserInteractionEnabled = true + } case let .banned(text): self.avatarNode = nil self.iconNode = nil @@ -820,12 +828,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) + let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) self.textNode.attributedText = attributedText self.textNode.maximumNumberOfLines = 2 displayUndo = false self.originalRemainingSeconds = 3 + + if text.contains("](") { + isUserInteractionEnabled = true + } case let .inviteRequestSent(title, text): self.avatarNode = nil self.iconNode = nil @@ -897,7 +910,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) + let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in return ("URL", contents) }), textAlignment: .natural)