Various improvements

This commit is contained in:
Ilya Laktyushin 2024-02-27 10:38:24 +04:00
parent a603c138e9
commit 35e9887343
48 changed files with 590 additions and 202 deletions

View File

@ -11218,6 +11218,7 @@ Sorry for the inconvenience.";
"GroupBoost.AdditionalFeatures" = "Additional Features"; "GroupBoost.AdditionalFeatures" = "Additional Features";
"GroupBoost.AdditionalFeaturesText" = "By gaining **boosts**, your group reaches higher levels and unlocks more features."; "GroupBoost.AdditionalFeaturesText" = "By gaining **boosts**, your group reaches higher levels and unlocks more features.";
"ChannelBoost.AdditionalFeaturesText" = "By gaining **boosts**, your channel reaches higher levels and unlocks more features.";
"Stats.Boosts.Group.NoBoostersYet" = "No users currently boost your group"; "Stats.Boosts.Group.NoBoostersYet" = "No users currently boost your group";
"Stats.Boosts.Group.BoostersInfo" = "Your group is currently boosted by these members."; "Stats.Boosts.Group.BoostersInfo" = "Your group is currently boosted by these members.";

View File

@ -935,7 +935,7 @@ public protocol SharedAccountContext: AnyObject {
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
func makeArchiveSettingsController(context: AccountContext) -> ViewController func makeArchiveSettingsController(context: AccountContext) -> ViewController
func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
func makeBusinessSetupScreen(context: AccountContext) -> ViewController func makeBusinessSetupScreen(context: AccountContext) -> ViewController
func makeChatbotSetupScreen(context: AccountContext) -> ViewController func makeChatbotSetupScreen(context: AccountContext) -> ViewController
func makeBusinessLocationSetupScreen(context: AccountContext, initialValue: TelegramBusinessLocation?, completion: @escaping (TelegramBusinessLocation?) -> Void) -> ViewController func makeBusinessLocationSetupScreen(context: AccountContext, initialValue: TelegramBusinessLocation?, completion: @escaping (TelegramBusinessLocation?) -> Void) -> ViewController

View File

@ -70,6 +70,14 @@ public enum PremiumDemoSubject {
case messageTags case messageTags
case lastSeen case lastSeen
case messagePrivacy case messagePrivacy
case folderTags
case businessLocation
case businessHours
case businessGreetingMessage
case businessQuickReplies
case businessAwayMessage
case businessChatBots
} }
public enum PremiumLimitSubject { public enum PremiumLimitSubject {

View File

@ -5617,7 +5617,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private func openFilterSettings() { private func openFilterSettings() {
self.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(false) self.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(false)
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController { if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: true, dismissed: { [weak self] in let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: true, scrollToTags: false, dismissed: { [weak self] in
self?.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(true) self?.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(true)
}) })
navigationController.pushViewController(controller) navigationController.pushViewController(controller)

View File

@ -41,6 +41,19 @@ private enum ChatListFilterPresetListSection: Int32 {
case tags case tags
} }
public enum ChatListFilterPresetListEntryTag: ItemListItemTag {
case displayTags
public func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? ChatListFilterPresetListEntryTag, self == other {
return true
} else {
return false
}
}
}
private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String { private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String {
if peers.isEmpty { if peers.isEmpty {
return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder
@ -192,7 +205,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
//TODO:localize //TODO:localize
return ItemListSwitchItem(presentationData: presentationData, title: "Show Folder Tags", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Show Folder Tags", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateDisplayTags(value) arguments.updateDisplayTags(value)
}) }, tag: ChatListFilterPresetListEntryTag.displayTags)
case .displayTagsFooter: case .displayTagsFooter:
//TODO:localize //TODO:localize
return ItemListTextItem(presentationData: presentationData, text: .plain("Display folder names for each chat in the chat list."), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain("Display folder names for each chat in the chat list."), sectionId: self.section)
@ -285,7 +298,7 @@ public enum ChatListFilterPresetListControllerMode {
case modal case modal
} }
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, dismissed: (() -> Void)? = nil) -> ViewController { public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, scrollToTags: Bool = false, dismissed: (() -> Void)? = nil) -> ViewController {
let initialState = ChatListFilterPresetListControllerState() let initialState = ChatListFilterPresetListControllerState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@ -627,7 +640,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatListFolderSettings_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatListFolderSettings_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits), style: .blocks, animateChanges: true) let entries = chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, initialScrollToItem: scrollToTags ? ListViewScrollToItem(index: entries.count - 1, position: .center(.bottom), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down) : nil, animateChanges: true)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -889,11 +889,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
} }
}) })
if case let .share(_, selfPeer, _) = self.mode { switch self.mode {
case let .share(_, selfPeer, _):
if let selfPeer { if let selfPeer {
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer) self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
} }
self.headerNode.mapNode.hasPickerAnnotation = true self.headerNode.mapNode.hasPickerAnnotation = true
case .pick:
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, location: TelegramMediaMap(coordinate: CLLocationCoordinate2DMake(0, 0)), queryId: nil, resultId: nil, forcedSelection: true)
self.headerNode.mapNode.hasPickerAnnotation = true
} }
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,61 @@
import Foundation
import UIKit
import SceneKit
import Display
import AppBundle
private let sceneVersion: Int = 1
final class BadgeBusinessView: UIView, PhoneDemoDecorationView {
private let sceneView: SCNView
private var leftParticles: SCNNode?
private var rightParticles: SCNNode?
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let scene = loadCompressedScene(name: "business", version: sceneVersion) {
self.sceneView.scene = scene
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
self.leftParticles = self.sceneView.scene?.rootNode.childNode(withName: "leftParticles", recursively: false)
self.rightParticles = self.sceneView.scene?.rootNode.childNode(withName: "rightParticles", recursively: false)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setVisible(_ visible: Bool) {
if visible, let leftParticles = self.leftParticles, let rightParticles = self.rightParticles, leftParticles.parent == nil {
self.sceneView.scene?.rootNode.addChildNode(leftParticles)
self.sceneView.scene?.rootNode.addChildNode(rightParticles)
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.5 : 0.0, completion: { [weak self] finished in
if let strongSelf = self, finished && !visible && strongSelf.leftParticles?.parent != nil {
strongSelf.leftParticles?.removeFromParentNode()
strongSelf.rightParticles?.removeFromParentNode()
}
})
}
func resetAnimation() {
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}

View File

@ -13,8 +13,8 @@ final class BadgeStarsView: UIView, PhoneDemoDecorationView {
override init(frame: CGRect) { override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") { if let scene = loadCompressedScene(name: "badge", version: 1) {
self.sceneView.scene = try? SCNScene(url: url, options: nil) self.sceneView.scene = scene
} }
self.sceneView.isUserInteractionEnabled = false self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60 self.sceneView.preferredFramesPerSecond = 60
@ -67,8 +67,8 @@ final class EmojiStarsView: UIView, PhoneDemoDecorationView {
override init(frame: CGRect) { override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "emoji", withExtension: "scn") { if let scene = loadCompressedScene(name: "emoji", version: 1) {
self.sceneView.scene = try? SCNScene(url: url, options: nil) self.sceneView.scene = scene
} }
self.sceneView.isUserInteractionEnabled = false self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60 self.sceneView.preferredFramesPerSecond = 60
@ -121,8 +121,8 @@ final class TagStarsView: UIView, PhoneDemoDecorationView {
override init(frame: CGRect) { override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "tag", withExtension: "scn") { if let scene = loadCompressedScene(name: "tag", version: 1) {
self.sceneView.scene = try? SCNScene(url: url, options: nil) self.sceneView.scene = scene
} }
self.sceneView.isUserInteractionEnabled = false self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60 self.sceneView.preferredFramesPerSecond = 60

View File

@ -12,7 +12,7 @@ import TelegramCore
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData import TelegramPresentationData
private let sceneVersion: Int = 3 private let sceneVersion: Int = 1
public final class BoostHeaderBackgroundComponent: Component { public final class BoostHeaderBackgroundComponent: Component {
let isVisible: Bool let isVisible: Bool
@ -58,7 +58,7 @@ public final class BoostHeaderBackgroundComponent: Component {
private func setup() { private func setup() {
guard let url = getAppBundle().url(forResource: "boost", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else { guard let scene = loadCompressedScene(name: "boost", version: sceneVersion) else {
return return
} }

View File

@ -13,8 +13,6 @@ import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import EmojiStatusComponent import EmojiStatusComponent
private let sceneVersion: Int = 3
class EmojiHeaderComponent: Component { class EmojiHeaderComponent: Component {
let context: AccountContext let context: AccountContext
let animationCache: AnimationCache let animationCache: AnimationCache

View File

@ -5,6 +5,8 @@ import Display
import AppBundle import AppBundle
import LegacyComponents import LegacyComponents
private let sceneVersion: Int = 1
final class FasterStarsView: UIView, PhoneDemoDecorationView { final class FasterStarsView: UIView, PhoneDemoDecorationView {
private let sceneView: SCNView private let sceneView: SCNView
@ -13,8 +15,8 @@ final class FasterStarsView: UIView, PhoneDemoDecorationView {
override init(frame: CGRect) { override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "lightspeed", withExtension: "scn") { if let scene = loadCompressedScene(name: "lightspeed", version: sceneVersion) {
self.sceneView.scene = try? SCNScene(url: url, options: nil) self.sceneView.scene = scene
} }
self.sceneView.isUserInteractionEnabled = false self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60 self.sceneView.preferredFramesPerSecond = 60

View File

@ -14,7 +14,7 @@ import MergedAvatarsNode
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData import TelegramPresentationData
private let sceneVersion: Int = 3 private let sceneVersion: Int = 1
final class GiftAvatarComponent: Component { final class GiftAvatarComponent: Component {
let context: AccountContext let context: AccountContext
@ -106,7 +106,7 @@ final class GiftAvatarComponent: Component {
} }
private func setup() { private func setup() {
guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else { guard let scene = loadCompressedScene(name: "gift", version: sceneVersion) else {
return return
} }

View File

@ -371,6 +371,7 @@ final class PhoneDemoComponent: Component {
case emoji case emoji
case hello case hello
case tag case tag
case business
} }
enum Model { enum Model {
@ -547,6 +548,13 @@ final class PhoneDemoComponent: Component {
self.decorationView = starsView self.decorationView = starsView
self.decorationContainerView.addSubview(starsView) self.decorationContainerView.addSubview(starsView)
} }
case .business:
if let _ = self.decorationView as? BadgeBusinessView {
} else {
let starsView = BadgeBusinessView(frame: self.decorationContainerView.bounds)
self.decorationView = starsView
self.decorationContainerView.addSubview(starsView)
}
} }
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position) self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)

View File

@ -698,7 +698,7 @@ private final class SheetContent: CombinedComponent {
isCurrent = mode == .current isCurrent = mode == .current
} }
case .features: case .features:
textString = strings.GroupBoost_AdditionalFeaturesText textString = isGroup ? strings.GroupBoost_AdditionalFeaturesText : strings.ChannelBoost_AdditionalFeaturesText
} }
let defaultTitle = strings.ChannelBoost_Level("\(level)").string let defaultTitle = strings.ChannelBoost_Level("\(level)").string

View File

@ -8,7 +8,7 @@ import GZip
import AppBundle import AppBundle
import LegacyComponents import LegacyComponents
private let sceneVersion: Int = 1 private let sceneVersion: Int = 2
private func deg2rad(_ number: Float) -> Float { private func deg2rad(_ number: Float) -> Float {
return number * .pi / 180 return number * .pi / 180
@ -223,24 +223,7 @@ class PremiumCoinComponent: Component {
} }
private func setup() { private func setup() {
let resourceUrl: URL guard let scene = loadCompressedScene(name: "coin", version: sceneVersion) else {
if let url = getAppBundle().url(forResource: "coin", withExtension: "scn") {
resourceUrl = url
} else {
let fileName = "coin_\(sceneVersion).scn"
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
guard let url = getAppBundle().url(forResource: "coin", withExtension: ""),
let compressedData = try? Data(contentsOf: url),
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
return
}
try? decompressedData.write(to: tmpUrl)
}
resourceUrl = tmpUrl
}
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
return return
} }
@ -316,8 +299,8 @@ class PremiumCoinComponent: Component {
return return
} }
let fromScale: Float = 0.85 let fromScale: Float = 0.9
let toScale: Float = 0.9 let toScale: Float = 1.0
let animation = CABasicAnimation(keyPath: "scale") let animation = CABasicAnimation(keyPath: "scale")
animation.duration = 2.0 animation.duration = 2.0

View File

@ -1177,7 +1177,7 @@ private final class DemoSheetContent: CombinedComponent {
text = strings.Premium_LastSeenInfo text = strings.Premium_LastSeenInfo
case .messagePrivacy: case .messagePrivacy:
text = strings.Premium_MessagePrivacyInfo text = strings.Premium_MessagePrivacyInfo
case .doubleLimits, .stories, .business: default:
text = "" text = ""
} }
@ -1392,6 +1392,14 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
case lastSeen case lastSeen
case messagePrivacy case messagePrivacy
case business case business
case folderTags
case businessLocation
case businessHours
case businessGreetingMessage
case businessQuickReplies
case businessAwayMessage
case businessChatBots
} }
public enum Source: Equatable { public enum Source: Equatable {

View File

@ -533,6 +533,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
demoSubject = .messagePrivacy demoSubject = .messagePrivacy
case .business: case .business:
demoSubject = .business demoSubject = .business
default:
demoSubject = .doubleLimits
} }
let buttonText: String let buttonText: String

View File

@ -439,6 +439,15 @@ public enum PremiumPerk: CaseIterable {
case lastSeen case lastSeen
case messagePrivacy case messagePrivacy
case business case business
case folderTags
case businessLocation
case businessHours
case businessGreetingMessage
case businessQuickReplies
case businessAwayMessage
case businessChatBots
public static var allCases: [PremiumPerk] { public static var allCases: [PremiumPerk] {
return [ return [
@ -520,6 +529,8 @@ public enum PremiumPerk: CaseIterable {
return "message_privacy" return "message_privacy"
case .business: case .business:
return "business" return "business"
default:
return ""
} }
} }
@ -567,6 +578,8 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_MessagePrivacy return strings.Premium_MessagePrivacy
case .business: case .business:
return strings.Premium_Business return strings.Premium_Business
default:
return ""
} }
} }
@ -614,6 +627,8 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_MessagePrivacyInfo return strings.Premium_MessagePrivacyInfo
case .business: case .business:
return strings.Premium_BusinessInfo return strings.Premium_BusinessInfo
default:
return ""
} }
} }
@ -661,6 +676,8 @@ public enum PremiumPerk: CaseIterable {
return "Premium/Perk/MessagePrivacy" return "Premium/Perk/MessagePrivacy"
case .business: case .business:
return "Premium/Perk/Business" return "Premium/Perk/Business"
default:
return ""
} }
} }
} }
@ -1996,6 +2013,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
case .business: case .business:
demoSubject = .business demoSubject = .business
default:
demoSubject = .doubleLimits
} }
let isPremium = state?.isPremium == true let isPremium = state?.isPremium == true
@ -2100,7 +2119,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
foregroundColor: .white, foregroundColor: .white,
iconName: perk.iconName iconName: perk.iconName
))), ))),
action: { _ in action: { [weak state] _ in
let isPremium = state?.isPremium == true
if isPremium {
switch perk { switch perk {
case .location: case .location:
let _ = (accountContext.engine.data.get( let _ = (accountContext.engine.data.get(
@ -2138,6 +2159,39 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
case .chatbots: case .chatbots:
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext)) push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext))
} }
} else {
var demoSubject: PremiumDemoScreen.Subject
switch perk {
case .location:
demoSubject = .businessLocation
case .hours:
demoSubject = .businessHours
case .quickReplies:
demoSubject = .businessQuickReplies
case .greetings:
demoSubject = .businessGreetingMessage
case .awayMessages:
demoSubject = .businessAwayMessage
case .chatbots:
demoSubject = .businessChatBots
}
var dismissImpl: (() -> Void)?
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: [.businessLocation, .businessHours, .businessQuickReplies, .businessGreetingMessage, .businessAwayMessage, .businessChatBots], buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "").string : strings.Premium_SubscribeFor(state?.price ?? "").string), isPremium: isPremium, forceDark: forceDark)
controller.action = { [weak state] in
dismissImpl?()
if state?.isPremium == false {
buy()
}
}
controller.disposed = {
updateIsFocused(false)
}
present(controller)
dismissImpl = { [weak controller] in
controller?.dismiss(animated: true, completion: nil)
}
updateIsFocused(true)
}
} }
)))) ))))
i += 1 i += 1
@ -2238,7 +2292,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
iconName: "Premium/BusinessPerk/Tag" iconName: "Premium/BusinessPerk/Tag"
))), ))),
action: { _ in action: { _ in
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, dismissed: nil)) push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil))
} }
)))) ))))

View File

@ -834,6 +834,129 @@ public class PremiumLimitsListScreen: ViewController {
) )
) )
availableItems[.businessLocation] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessLocation,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["business_location"],
decoration: .business
)),
title: strings.Business_Location,
text: strings.Business_LocationInfo,
textColor: textColor
)
)
)
)
availableItems[.businessHours] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessHours,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["business_hours"],
decoration: .business
)),
title: strings.Business_OpeningHours,
text: strings.Business_OpeningHoursInfo,
textColor: textColor
)
)
)
)
availableItems[.businessQuickReplies] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessQuickReplies,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["greeting_message"],
decoration: .business
)),
title: strings.Business_QuickReplies,
text: strings.Business_QuickRepliesInfo,
textColor: textColor
)
)
)
)
availableItems[.businessGreetingMessage] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessGreetingMessage,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["greeting_message"],
decoration: .business
)),
title: strings.Business_GreetingMessages,
text: strings.Business_GreetingMessagesInfo,
textColor: textColor
)
)
)
)
availableItems[.businessAwayMessage] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessAwayMessage,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["away_message"],
decoration: .business
)),
title: strings.Business_AwayMessages,
text: strings.Business_AwayMessagesInfo,
textColor: textColor
)
)
)
)
availableItems[.businessChatBots] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessChatBots,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["business_bots"],
decoration: .business
)),
title: strings.Business_Chatbots,
text: strings.Business_ChatbotsInfo,
textColor: textColor
)
)
)
)
if let order = controller.order { if let order = controller.order {
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] } var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
let initialIndex: Int let initialIndex: Int

View File

@ -8,7 +8,7 @@ import GZip
import AppBundle import AppBundle
import LegacyComponents import LegacyComponents
private let sceneVersion: Int = 6 private let sceneVersion: Int = 7
private func deg2rad(_ number: Float) -> Float { private func deg2rad(_ number: Float) -> Float {
return number * .pi / 180 return number * .pi / 180
@ -45,7 +45,31 @@ private func generateDiffuseTexture() -> UIImage {
})! })!
} }
class PremiumStarComponent: Component { func loadCompressedScene(name: String, version: Int) -> SCNScene? {
let resourceUrl: URL
if let url = getAppBundle().url(forResource: name, withExtension: "scn") {
resourceUrl = url
} else {
let fileName = "\(name)_\(version).scn"
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
guard let url = getAppBundle().url(forResource: name, withExtension: ""),
let compressedData = try? Data(contentsOf: url),
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
return nil
}
try? decompressedData.write(to: tmpUrl)
}
resourceUrl = tmpUrl
}
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
return nil
}
return scene
}
final class PremiumStarComponent: Component {
let isIntro: Bool let isIntro: Bool
let isVisible: Bool let isVisible: Bool
let hasIdleAnimations: Bool let hasIdleAnimations: Bool
@ -251,24 +275,7 @@ class PremiumStarComponent: Component {
} }
private func setup() { private func setup() {
let resourceUrl: URL guard let scene = loadCompressedScene(name: "star", version: sceneVersion) else {
if let url = getAppBundle().url(forResource: "star", withExtension: "scn") {
resourceUrl = url
} else {
let fileName = "star_\(sceneVersion).scn"
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
guard let url = getAppBundle().url(forResource: "star", withExtension: ""),
let compressedData = try? Data(contentsOf: url),
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
return
}
try? decompressedData.write(to: tmpUrl)
}
resourceUrl = tmpUrl
}
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
return return
} }

View File

@ -5,6 +5,8 @@ import Display
import AppBundle import AppBundle
import SwiftSignalKit import SwiftSignalKit
private let sceneVersion: Int = 1
final class SwirlStarsView: UIView, PhoneDemoDecorationView { final class SwirlStarsView: UIView, PhoneDemoDecorationView {
private let sceneView: SCNView private let sceneView: SCNView
@ -13,8 +15,8 @@ final class SwirlStarsView: UIView, PhoneDemoDecorationView {
override init(frame: CGRect) { override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size)) self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "swirl", withExtension: "scn") { if let scene = loadCompressedScene(name: "swirl", version: sceneVersion) {
self.sceneView.scene = try? SCNScene(url: url, options: nil) self.sceneView.scene = scene
} }
self.sceneView.isUserInteractionEnabled = false self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60 self.sceneView.preferredFramesPerSecond = 60

View File

@ -16,6 +16,8 @@ public final class EmptyStateIndicatorComponent: Component {
public let text: String public let text: String
public let actionTitle: String? public let actionTitle: String?
public let action: () -> Void public let action: () -> Void
public let additionalActionTitle: String?
public let additionalAction: () -> Void
public init( public init(
context: AccountContext, context: AccountContext,
@ -24,7 +26,9 @@ public final class EmptyStateIndicatorComponent: Component {
title: String, title: String,
text: String, text: String,
actionTitle: String?, actionTitle: String?,
action: @escaping () -> Void action: @escaping () -> Void,
additionalActionTitle: String?,
additionalAction: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
@ -33,6 +37,8 @@ public final class EmptyStateIndicatorComponent: Component {
self.text = text self.text = text
self.actionTitle = actionTitle self.actionTitle = actionTitle
self.action = action self.action = action
self.additionalActionTitle = additionalActionTitle
self.additionalAction = additionalAction
} }
public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool { public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool {
@ -54,6 +60,9 @@ public final class EmptyStateIndicatorComponent: Component {
if lhs.actionTitle != rhs.actionTitle { if lhs.actionTitle != rhs.actionTitle {
return false return false
} }
if lhs.additionalActionTitle != rhs.additionalActionTitle {
return false
}
return true return true
} }
@ -65,6 +74,7 @@ public final class EmptyStateIndicatorComponent: Component {
private let title = ComponentView<Empty>() private let title = ComponentView<Empty>()
private let text = ComponentView<Empty>() private let text = ComponentView<Empty>()
private var button: ComponentView<Empty>? private var button: ComponentView<Empty>?
private var additionalButton: ComponentView<Empty>?
override public init(frame: CGRect) { override public init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
@ -139,7 +149,7 @@ public final class EmptyStateIndicatorComponent: Component {
} }
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: 240.0, height: 50.0) containerSize: CGSize(width: 260.0, height: 50.0)
) )
} else { } else {
if let button = self.button { if let button = self.button {
@ -148,14 +158,52 @@ public final class EmptyStateIndicatorComponent: Component {
} }
} }
var additionalButtonSize: CGSize?
if let additionalActionTitle = component.additionalActionTitle {
let additionalButton: ComponentView<Empty>
if let current = self.additionalButton {
additionalButton = current
} else {
additionalButton = ComponentView()
self.additionalButton = additionalButton
}
additionalButtonSize = additionalButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(
text: additionalActionTitle, font:
Font.regular(17.0),
color: component.theme.list.itemAccentColor)
),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.additionalAction()
}
)),
environment: {},
containerSize: CGSize(width: 262.0, height: 50.0)
)
} else {
if let additionalButton = self.additionalButton {
self.additionalButton = nil
additionalButton.view?.removeFromSuperview()
}
}
let animationSpacing: CGFloat = 11.0 let animationSpacing: CGFloat = 11.0
let titleSpacing: CGFloat = 17.0 let titleSpacing: CGFloat = 17.0
let buttonSpacing: CGFloat = 17.0 let buttonSpacing: CGFloat = 21.0
var totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height var totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height
if let buttonSize { if let buttonSize {
totalHeight += buttonSpacing + buttonSize.height totalHeight += buttonSpacing + buttonSize.height
} }
if let additionalButtonSize {
totalHeight += buttonSpacing + additionalButtonSize.height
}
var contentY = floor((availableSize.height - totalHeight) * 0.5) var contentY = floor((availableSize.height - totalHeight) * 0.5)
@ -185,7 +233,14 @@ public final class EmptyStateIndicatorComponent: Component {
self.addSubview(buttonView) self.addSubview(buttonView)
} }
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: contentY), size: buttonSize)) transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: contentY), size: buttonSize))
contentY += buttonSize.height contentY += buttonSize.height + buttonSpacing
}
if let additionalButtonSize, let additionalButtonView = self.additionalButton?.view {
if additionalButtonView.superview == nil {
self.addSubview(additionalButtonView)
}
transition.setFrame(view: additionalButtonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - additionalButtonSize.width) * 0.5), y: contentY), size: additionalButtonSize))
contentY += additionalButtonSize.height
} }
return availableSize return availableSize

View File

@ -178,7 +178,11 @@ public final class ListActionItemComponent: Component {
let contentRightInset: CGFloat let contentRightInset: CGFloat
switch component.accessory { switch component.accessory {
case .none: case .none:
if let _ = component.icon {
contentRightInset = 42.0
} else {
contentRightInset = 16.0 contentRightInset = 16.0
}
case .arrow: case .arrow:
contentRightInset = 30.0 contentRightInset = 30.0
case .toggle: case .toggle:
@ -189,7 +193,7 @@ public final class ListActionItemComponent: Component {
contentHeight += verticalInset contentHeight += verticalInset
if component.leftIcon != nil { if component.leftIcon != nil {
contentLeftInset += 46.0 contentLeftInset += 52.0
} }
let titleSize = self.title.update( let titleSize = self.title.update(
@ -239,7 +243,7 @@ public final class ListActionItemComponent: Component {
var iconOffset: CGFloat = 0.0 var iconOffset: CGFloat = 0.0
if case .none = component.accessory { if case .none = component.accessory {
iconOffset = 6.0 iconOffset = 26.0
} }
let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - contentRightInset - iconSize.width + iconOffset, y: floor((contentHeight - iconSize.height) * 0.5)), size: iconSize) let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - contentRightInset - iconSize.width + iconOffset, y: floor((contentHeight - iconSize.height) * 0.5)), size: iconSize)

View File

@ -8795,7 +8795,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
}) })
case .chatFolders: case .chatFolders:
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, dismissed: nil) let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, scrollToTags: false, dismissed: nil)
push(controller) push(controller)
case .notificationsAndSounds: case .notificationsAndSounds:
if let settings = self.data?.globalSettings { if let settings = self.data?.globalSettings {

View File

@ -295,7 +295,23 @@ final class PeerInfoStoryGridScreenComponent: Component {
let _ = paneNode.scrollToTop() let _ = paneNode.scrollToTop()
} }
func openCreateStory() {
guard let component = self.component else {
return
}
if let rootController = component.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(customTarget: nil, transitionIn: nil, transitionedIn: {}, transitionOut: { _, _ in return nil })
coordinator?.animateIn()
}
}
private var isUpdating = false
func update(component: PeerInfoStoryGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize { func update(component: PeerInfoStoryGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component self.component = component
self.state = state self.state = state
@ -313,7 +329,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
var bottomInset: CGFloat = environment.safeInsets.bottom var bottomInset: CGFloat = environment.safeInsets.bottom
if self.selectedCount != 0 { if self.selectedCount != 0 || (component.scope == .saved && self.paneNode?.isEmpty == false) {
let selectionPanel: ComponentView<Empty> let selectionPanel: ComponentView<Empty>
var selectionPanelTransition = transition var selectionPanelTransition = transition
if let current = self.selectionPanel { if let current = self.selectionPanel {
@ -327,7 +343,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
let buttonText: String let buttonText: String
switch component.scope { switch component.scope {
case .saved: case .saved:
buttonText = environment.strings.ChatList_Context_Archive buttonText = self.selectedCount > 0 ? environment.strings.ChatList_Context_Archive : environment.strings.StoryList_SavedAddAction
case .archive: case .archive:
buttonText = environment.strings.StoryList_SaveToProfile buttonText = environment.strings.StoryList_SaveToProfile
} }
@ -344,7 +360,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
guard let self, let component = self.component, let environment = self.environment else { guard let self, let component = self.component, let environment = self.environment else {
return return
} }
guard let paneNode = self.paneNode, !paneNode.selectedIds.isEmpty else { guard let paneNode = self.paneNode else {
return return
} }
@ -361,6 +377,9 @@ final class PeerInfoStoryGridScreenComponent: Component {
switch component.scope { switch component.scope {
case .saved: case .saved:
let selectedCount = paneNode.selectedItems.count let selectedCount = paneNode.selectedItems.count
if selectedCount == 0 {
self.openCreateStory()
} else {
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start() let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
paneNode.setIsSelectionModeActive(false) paneNode.setIsSelectionModeActive(false)
@ -376,6 +395,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
animateInAsReplacement: false, animateInAsReplacement: false,
action: { _ in return false } action: { _ in return false }
), in: .current) ), in: .current)
}
case .archive: case .archive:
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start() let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start()
@ -449,10 +469,28 @@ final class PeerInfoStoryGridScreenComponent: Component {
}, },
listContext: nil listContext: nil
) )
paneNode.isEmptyUpdated = { [weak self] _ in
guard let self else {
return
}
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
}
self.paneNode = paneNode self.paneNode = paneNode
if let selectionPanelView = self.selectionPanel?.view {
self.insertSubview(paneNode.view, belowSubview: selectionPanelView)
} else {
self.addSubview(paneNode.view) self.addSubview(paneNode.view)
}
paneNode.emptyAction = { [weak self] in paneNode.emptyAction = { [weak self] in
guard let self else {
return
}
self.openCreateStory()
}
paneNode.additionalEmptyAction = { [weak self] in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }

View File

@ -948,6 +948,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
} }
public var isEmptyUpdated: (Bool) -> Void = { _ in }
public private(set) var isSelectionModeActive: Bool public private(set) var isSelectionModeActive: Bool
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
@ -985,6 +987,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
public var openCurrentDate: (() -> Void)? public var openCurrentDate: (() -> Void)?
public var paneDidScroll: (() -> Void)? public var paneDidScroll: (() -> Void)?
public var emptyAction: (() -> Void)? public var emptyAction: (() -> Void)?
public var additionalEmptyAction: (() -> Void)?
public var ensureRectVisible: ((UIView, CGRect) -> Void)? public var ensureRectVisible: ((UIView, CGRect) -> Void)?
@ -1729,6 +1732,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) { private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) {
self.items = items self.items = items
self.isEmptyUpdated(self.isEmpty)
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
var gridSnapshot: UIView? var gridSnapshot: UIView?
@ -2027,14 +2031,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
context: self.context, context: self.context,
theme: presentationData.theme, theme: presentationData.theme,
animationName: "StoryListEmpty", animationName: "StoryListEmpty",
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title, title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text, text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction, actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedAddAction,
action: { [weak self] in action: { [weak self] in
guard let self else { guard let self else {
return return
} }
self.emptyAction?() self.emptyAction?()
},
additionalActionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
additionalAction: { [weak self] in
guard let self else {
return
}
self.additionalEmptyAction?()
} }
)), )),
environment: {}, environment: {},

View File

@ -134,6 +134,9 @@ final class StoryAuthorInfoComponent: Component {
if timeString.count < 6 { if timeString.count < 6 {
combinedString.append(NSAttributedString(string: "\(timeString)", font: Font.regular(11.0), textColor: subtitleColor)) combinedString.append(NSAttributedString(string: "\(timeString)", font: Font.regular(11.0), textColor: subtitleColor))
} }
if component.isEdited {
combinedString.append(NSAttributedString(string: "\(component.strings.Story_HeaderEdited)", font: Font.regular(11.0), textColor: subtitleColor))
}
subtitle = combinedString subtitle = combinedString
subtitleTruncationType = .middle subtitleTruncationType = .middle
} else { } else {

View File

@ -1882,8 +1882,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return archiveSettingsController(context: context) return archiveSettingsController(context: context)
} }
public func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController { public func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController {
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, dismissed: dismissed) return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, scrollToTags: scrollToTags, dismissed: dismissed)
} }
public func makeBusinessSetupScreen(context: AccountContext) -> ViewController { public func makeBusinessSetupScreen(context: AccountContext) -> ViewController {
@ -2040,6 +2040,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .lastSeen mappedSubject = .lastSeen
case .messagePrivacy: case .messagePrivacy:
mappedSubject = .messagePrivacy mappedSubject = .messagePrivacy
default:
mappedSubject = .doubleLimits
} }
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action) return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
} }