mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
9a50913b57
commit
616a371830
@ -373,7 +373,13 @@ final class DemoPagerComponent: Component {
|
||||
|
||||
if firstTime {
|
||||
self.scrollView.contentOffset = CGPoint(x: CGFloat(component.index) * availableSize.width, y: 0.0)
|
||||
component.updated(self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width), component.items.count)
|
||||
var position: CGFloat
|
||||
if self.scrollView.contentSize.width > self.scrollView.frame.width {
|
||||
position = self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width)
|
||||
} else {
|
||||
position = 0.0
|
||||
}
|
||||
component.updated(position, component.items.count)
|
||||
}
|
||||
let viewportCenter = self.scrollView.contentOffset.x + availableSize.width * 0.5
|
||||
|
||||
@ -1230,12 +1236,12 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public convenience init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, action: @escaping () -> Void) {
|
||||
self.init(context: context, subject: subject, source: source, order: nil, action: action)
|
||||
public convenience init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, forceDark: Bool = false, action: @escaping () -> Void) {
|
||||
self.init(context: context, subject: subject, source: source, order: nil, forceDark: forceDark, action: action)
|
||||
}
|
||||
|
||||
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, order: [PremiumPerk]?, action: @escaping () -> Void) {
|
||||
super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, order: order, action: action), navigationBarAppearance: .none)
|
||||
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, order: [PremiumPerk]?, forceDark: Bool = false, action: @escaping () -> Void) {
|
||||
super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, order: order, action: action), navigationBarAppearance: .none, theme: forceDark ? .dark : .default)
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
|
@ -283,7 +283,7 @@ public enum PremiumSource: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
enum PremiumPerk: CaseIterable {
|
||||
public enum PremiumPerk: CaseIterable {
|
||||
case doubleLimits
|
||||
case moreUpload
|
||||
case fasterDownload
|
||||
@ -300,7 +300,7 @@ enum PremiumPerk: CaseIterable {
|
||||
case translation
|
||||
case stories
|
||||
|
||||
static var allCases: [PremiumPerk] {
|
||||
public static var allCases: [PremiumPerk] {
|
||||
return [
|
||||
.doubleLimits,
|
||||
.moreUpload,
|
||||
@ -2771,7 +2771,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
public weak var containerView: UIView?
|
||||
public var animationColor: UIColor?
|
||||
|
||||
public init(context: AccountContext, modal: Bool = true, source: PremiumSource) {
|
||||
public init(context: AccountContext, modal: Bool = true, source: PremiumSource, forceDark: Bool = false) {
|
||||
self.context = context
|
||||
|
||||
var updateInProgressImpl: ((Bool) -> Void)?
|
||||
@ -2793,7 +2793,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
completion: {
|
||||
completionImpl?()
|
||||
}
|
||||
), navigationBarAppearance: .transparent)
|
||||
), navigationBarAppearance: .transparent, theme: forceDark ? .dark : .default)
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
|
@ -48,8 +48,11 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
|
||||
let nextAction = ActionSlot<Void>()
|
||||
|
||||
init(context: AccountContext, controller: PremiumLimitsListScreen, buttonTitle: String, gloss: Bool) {
|
||||
init(context: AccountContext, controller: PremiumLimitsListScreen, buttonTitle: String, gloss: Bool, forceDark: Bool) {
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if forceDark {
|
||||
self.presentationData = self.presentationData.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
}
|
||||
|
||||
self.controller = controller
|
||||
|
||||
@ -413,6 +416,7 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
component: AnyComponent(
|
||||
StoriesPageComponent(
|
||||
context: context,
|
||||
theme: self.presentationData.theme,
|
||||
bottomInset: self.footerNode.frame.height,
|
||||
updatedBottomAlpha: { [weak self] alpha in
|
||||
if let strongSelf = self {
|
||||
@ -1008,17 +1012,19 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
|
||||
private let buttonText: String
|
||||
private let buttonGloss: Bool
|
||||
private let forceDark: Bool
|
||||
|
||||
var action: () -> Void = {}
|
||||
var disposed: () -> Void = {}
|
||||
public var action: () -> Void = {}
|
||||
public var disposed: () -> Void = {}
|
||||
|
||||
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, buttonText: String, isPremium: Bool) {
|
||||
public init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, buttonText: String, isPremium: Bool, forceDark: Bool = false) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
self.source = source
|
||||
self.order = order
|
||||
self.buttonText = buttonText
|
||||
self.buttonGloss = !isPremium
|
||||
self.forceDark = forceDark
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@ -1041,7 +1047,7 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = Node(context: self.context, controller: self, buttonTitle: self.buttonText, gloss: self.buttonGloss)
|
||||
self.displayNode = Node(context: self.context, controller: self, buttonTitle: self.buttonText, gloss: self.buttonGloss, forceDark: self.forceDark)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
@ -1163,9 +1169,15 @@ private class FooterNode: ASDisplayNode {
|
||||
let bottomPanelPadding: CGFloat = 12.0
|
||||
let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding
|
||||
|
||||
let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0
|
||||
var panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 8.0
|
||||
var buttonOffset: CGFloat = 20.0
|
||||
if let (_, count) = self.currentParams, count > 1 {
|
||||
panelHeight += 20.0
|
||||
buttonOffset += 20.0
|
||||
}
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: 40.0), size: CGSize(width: buttonWidth, height: buttonHeight)))
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: buttonOffset), size: CGSize(width: buttonWidth, height: buttonHeight)))
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: panelFrame)
|
||||
self.backgroundNode.update(size: panelFrame.size, transition: transition)
|
||||
@ -1187,6 +1199,7 @@ private class FooterNode: ASDisplayNode {
|
||||
containerSize: layout.size
|
||||
)
|
||||
self.pageIndicatorView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - indicatorSize.width) / 2.0), y: 10.0), size: indicatorSize)
|
||||
transition.updateAlpha(layer: self.pageIndicatorView.layer, alpha: count <= 1 ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.coverNode, frame: panelFrame)
|
||||
|
@ -15,10 +15,12 @@ import AvatarStoryIndicatorComponent
|
||||
|
||||
private final class AvatarComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let peer: EnginePeer
|
||||
|
||||
init(context: AccountContext, peer: EnginePeer) {
|
||||
init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.peer = peer
|
||||
}
|
||||
|
||||
@ -26,6 +28,9 @@ private final class AvatarComponent: Component {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
@ -60,7 +65,7 @@ private final class AvatarComponent: Component {
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: -22.0), size: size)
|
||||
self.avatarNode.setPeer(
|
||||
context: component.context,
|
||||
theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
theme: component.theme,
|
||||
peer: component.peer,
|
||||
synchronousLoad: true
|
||||
)
|
||||
@ -234,11 +239,13 @@ private final class StoriesListComponent: CombinedComponent {
|
||||
typealias EnvironmentType = (Empty, ScrollChildEnvironment)
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let topInset: CGFloat
|
||||
let bottomInset: CGFloat
|
||||
|
||||
init(context: AccountContext, topInset: CGFloat, bottomInset: CGFloat) {
|
||||
init(context: AccountContext, theme: PresentationTheme, topInset: CGFloat, bottomInset: CGFloat) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.topInset = topInset
|
||||
self.bottomInset = bottomInset
|
||||
}
|
||||
@ -247,6 +254,9 @@ private final class StoriesListComponent: CombinedComponent {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.topInset != rhs.topInset {
|
||||
return false
|
||||
}
|
||||
@ -298,7 +308,7 @@ private final class StoriesListComponent: CombinedComponent {
|
||||
let list = Child(List<Empty>.self)
|
||||
|
||||
return { context in
|
||||
let theme = context.component.context.sharedContext.currentPresentationData.with { $0 }.theme
|
||||
let theme = context.component.theme
|
||||
// let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
|
||||
let colors = [
|
||||
@ -322,6 +332,7 @@ private final class StoriesListComponent: CombinedComponent {
|
||||
id: "avatar",
|
||||
component: AnyComponent(AvatarComponent(
|
||||
context: context.component.context,
|
||||
theme: theme,
|
||||
peer: accountPeer
|
||||
))
|
||||
)
|
||||
@ -447,13 +458,15 @@ final class StoriesPageComponent: CombinedComponent {
|
||||
typealias EnvironmentType = DemoPageEnvironment
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let bottomInset: CGFloat
|
||||
let updatedBottomAlpha: (CGFloat) -> Void
|
||||
let updatedDismissOffset: (CGFloat) -> Void
|
||||
let updatedIsDisplaying: (Bool) -> Void
|
||||
|
||||
init(context: AccountContext, bottomInset: CGFloat, updatedBottomAlpha: @escaping (CGFloat) -> Void, updatedDismissOffset: @escaping (CGFloat) -> Void, updatedIsDisplaying: @escaping (Bool) -> Void) {
|
||||
init(context: AccountContext, theme: PresentationTheme, bottomInset: CGFloat, updatedBottomAlpha: @escaping (CGFloat) -> Void, updatedDismissOffset: @escaping (CGFloat) -> Void, updatedIsDisplaying: @escaping (Bool) -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.bottomInset = bottomInset
|
||||
self.updatedBottomAlpha = updatedBottomAlpha
|
||||
self.updatedDismissOffset = updatedDismissOffset
|
||||
@ -464,6 +477,9 @@ final class StoriesPageComponent: CombinedComponent {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.bottomInset != rhs.bottomInset {
|
||||
return false
|
||||
}
|
||||
@ -544,7 +560,7 @@ final class StoriesPageComponent: CombinedComponent {
|
||||
state.position = environment.position
|
||||
state.isDisplaying = environment.isDisplaying
|
||||
|
||||
let theme = context.component.context.sharedContext.currentPresentationData.with { $0 }.theme
|
||||
let theme = context.component.theme
|
||||
// let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
|
||||
let topInset: CGFloat = 56.0
|
||||
@ -554,6 +570,7 @@ final class StoriesPageComponent: CombinedComponent {
|
||||
content: AnyComponent(
|
||||
StoriesListComponent(
|
||||
context: context.component.context,
|
||||
theme: theme,
|
||||
topInset: topInset,
|
||||
bottomInset: context.component.bottomInset + 110.0
|
||||
)
|
||||
|
@ -4488,8 +4488,17 @@ func replayFinalState(
|
||||
}
|
||||
case let .UpdateStory(peerId, story):
|
||||
var updatedPeerEntries: [StoryItemsTableEntry] = transaction.getStoryItems(peerId: peerId)
|
||||
|
||||
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) {
|
||||
let previousEntryStory = updatedPeerEntries.first(where: { item in
|
||||
return item.id == story.id
|
||||
}).flatMap { item -> Stories.Item? in
|
||||
if let value = item.value.get(Stories.StoredItem.self), case let .item(item) = value {
|
||||
return item
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if let storedItem = Stories.StoredItem(apiStoryItem: story, existingItem: previousEntryStory, peerId: peerId, transaction: transaction) {
|
||||
if let currentIndex = updatedPeerEntries.firstIndex(where: { $0.id == storedItem.id }) {
|
||||
if case .item = storedItem {
|
||||
if let codedEntry = CodableEntry(storedItem) {
|
||||
|
@ -1368,7 +1368,7 @@ extension Stories.Item.Views {
|
||||
}
|
||||
|
||||
extension Stories.StoredItem {
|
||||
init?(apiStoryItem: Api.StoryItem, peerId: PeerId, transaction: Transaction) {
|
||||
init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) {
|
||||
switch apiStoryItem {
|
||||
case let .storyItem(flags, id, date, expireDate, caption, entities, media, mediaAreas, privacy, views):
|
||||
let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
@ -1414,11 +1414,19 @@ extension Stories.StoredItem {
|
||||
let isExpired = (flags & (1 << 6)) != 0
|
||||
let isPublic = (flags & (1 << 7)) != 0
|
||||
let isCloseFriends = (flags & (1 << 8)) != 0
|
||||
let isMin = (flags & (1 << 9)) != 0
|
||||
let isForwardingDisabled = (flags & (1 << 10)) != 0
|
||||
let isEdited = (flags & (1 << 11)) != 0
|
||||
let isContacts = (flags & (1 << 12)) != 0
|
||||
let isSelectedContacts = (flags & (1 << 13)) != 0
|
||||
|
||||
var mergedViews: Stories.Item.Views?
|
||||
if isMin, let existingItem = existingItem {
|
||||
mergedViews = existingItem.views
|
||||
} else {
|
||||
mergedViews = views.flatMap(Stories.Item.Views.init(apiViews:))
|
||||
}
|
||||
|
||||
let item = Stories.Item(
|
||||
id: id,
|
||||
timestamp: date,
|
||||
@ -1427,7 +1435,7 @@ extension Stories.StoredItem {
|
||||
mediaAreas: mediaAreas?.compactMap(mediaAreaFromApiMediaArea) ?? [],
|
||||
text: caption ?? "",
|
||||
entities: entities.flatMap { entities in return messageTextEntitiesFromApiEntities(entities) } ?? [],
|
||||
views: views.flatMap(Stories.Item.Views.init(apiViews:)),
|
||||
views: mergedViews,
|
||||
privacy: parsedPrivacy,
|
||||
isPinned: isPinned,
|
||||
isExpired: isExpired,
|
||||
|
@ -288,6 +288,7 @@ public final class ButtonComponent: Component {
|
||||
public let background: Background
|
||||
public let content: AnyComponentWithIdentity<Empty>
|
||||
public let isEnabled: Bool
|
||||
public let allowActionWhenDisabled: Bool
|
||||
public let displaysProgress: Bool
|
||||
public let action: () -> Void
|
||||
|
||||
@ -295,12 +296,14 @@ public final class ButtonComponent: Component {
|
||||
background: Background,
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
isEnabled: Bool,
|
||||
allowActionWhenDisabled: Bool = false,
|
||||
displaysProgress: Bool,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.background = background
|
||||
self.content = content
|
||||
self.isEnabled = isEnabled
|
||||
self.allowActionWhenDisabled = allowActionWhenDisabled
|
||||
self.displaysProgress = displaysProgress
|
||||
self.action = action
|
||||
}
|
||||
@ -315,6 +318,9 @@ public final class ButtonComponent: Component {
|
||||
if lhs.isEnabled != rhs.isEnabled {
|
||||
return false
|
||||
}
|
||||
if lhs.allowActionWhenDisabled != rhs.allowActionWhenDisabled {
|
||||
return false
|
||||
}
|
||||
if lhs.displaysProgress != rhs.displaysProgress {
|
||||
return false
|
||||
}
|
||||
@ -375,7 +381,7 @@ public final class ButtonComponent: Component {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
self.isEnabled = component.isEnabled && !component.displaysProgress
|
||||
self.isEnabled = (component.isEnabled || component.allowActionWhenDisabled) && !component.displaysProgress
|
||||
|
||||
transition.setBackgroundColor(view: self, color: component.background.color)
|
||||
transition.setCornerRadius(layer: self.layer, cornerRadius: component.background.cornerRadius)
|
||||
|
@ -2229,6 +2229,25 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var preloadViewListIds: [(Int32, EngineStoryItem.Views)] = []
|
||||
if let views = component.slice.item.storyItem.views {
|
||||
preloadViewListIds.append((component.slice.item.storyItem.id, views))
|
||||
}
|
||||
if currentIndex != 0, let views = component.slice.allItems[currentIndex - 1].storyItem.views {
|
||||
preloadViewListIds.append((component.slice.allItems[currentIndex - 1].storyItem.id, views))
|
||||
}
|
||||
if currentIndex != component.slice.allItems.count - 1, let views = component.slice.allItems[currentIndex + 1].storyItem.views {
|
||||
preloadViewListIds.append((component.slice.allItems[currentIndex + 1].storyItem.id, views))
|
||||
}
|
||||
|
||||
for (id, views) in preloadViewListIds {
|
||||
if component.sharedViewListsContext.viewLists[StoryId(peerId: component.slice.peer.id, id: id)] == nil {
|
||||
let viewList = component.context.engine.messages.storyViewList(id: id, views: views)
|
||||
component.sharedViewListsContext.viewLists[StoryId(peerId: component.slice.peer.id, id: id)] = viewList
|
||||
}
|
||||
}
|
||||
|
||||
if self.viewListPanState != nil || self.isCompletingViewListPan {
|
||||
for (id, _) in self.viewLists {
|
||||
if !visibleViewListIds.contains(id) {
|
||||
@ -2447,18 +2466,40 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
let animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
|
||||
self.component?.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: "**\(peer.compactDisplayTitle)** has been removed from your contacts.", timeout: nil),
|
||||
content: .universal(
|
||||
animation: "anim_infotip",
|
||||
scale: 1.0,
|
||||
colors: [
|
||||
"info1.info1.stroke": animationBackgroundColor,
|
||||
"info2.info2.Fill": animationBackgroundColor
|
||||
],
|
||||
title: nil,
|
||||
text: "**\(peer.compactDisplayTitle)** has been removed from your contacts.",
|
||||
customUndoText: "Undo",
|
||||
timeout: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component else {
|
||||
return false
|
||||
}
|
||||
guard case let .user(user) = peer else {
|
||||
return false
|
||||
}
|
||||
if case .undo = action {
|
||||
let _ = component.context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: user.phone ?? "", addToPrivacyExceptions: false).start()
|
||||
}
|
||||
return false
|
||||
}
|
||||
), nil)
|
||||
})))
|
||||
} else {
|
||||
if isBlocked {
|
||||
|
||||
} else {
|
||||
itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuBlock, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.destructiveColor)
|
||||
@ -2471,13 +2512,33 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
let animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
|
||||
self.component?.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: "**\(peer.compactDisplayTitle)** has been blocked.", timeout: nil),
|
||||
content: .universal(
|
||||
animation: "anim_infotip",
|
||||
scale: 1.0,
|
||||
colors: [
|
||||
"info1.info1.stroke": animationBackgroundColor,
|
||||
"info2.info2.Fill": animationBackgroundColor
|
||||
],
|
||||
title: nil,
|
||||
text: "**\(peer.compactDisplayTitle)** has been blocked.",
|
||||
customUndoText: "Undo",
|
||||
timeout: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component else {
|
||||
return false
|
||||
}
|
||||
if case .undo = action {
|
||||
let _ = component.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: false).start()
|
||||
}
|
||||
return false
|
||||
}
|
||||
), nil)
|
||||
})))
|
||||
}
|
||||
@ -3108,6 +3169,12 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
switch action {
|
||||
case .copy:
|
||||
storeAttributedTextInPasteboard(text)
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in true })
|
||||
self.sendMessageContext.tooltipScreen?.dismiss()
|
||||
self.sendMessageContext.tooltipScreen = undoController
|
||||
component.controller()?.present(undoController, in: .current)
|
||||
case .share:
|
||||
self.sendMessageContext.performShareTextAction(view: self, text: text.string)
|
||||
case .lookup:
|
||||
@ -4015,6 +4082,58 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentStoriesUpgradeScreen() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
let context = component.context
|
||||
//TODO:localize
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: context, subject: .stories, source: .other, order: [.stories], buttonText: "Upgrade Stories", isPremium: false, forceDark: true)
|
||||
controller.action = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = PremiumIntroScreen(context: context, source: .stories, forceDark: true)
|
||||
self.sendMessageContext.actionSheet = controller
|
||||
controller.wasDismissed = { [weak self, weak controller]in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.sendMessageContext.actionSheet === controller {
|
||||
self.sendMessageContext.actionSheet = nil
|
||||
}
|
||||
self.updateIsProgressPaused()
|
||||
}
|
||||
|
||||
replaceImpl?(controller)
|
||||
}
|
||||
controller.disposed = { [weak self, weak controller] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.sendMessageContext.actionSheet === controller {
|
||||
self.sendMessageContext.actionSheet = nil
|
||||
}
|
||||
self.updateIsProgressPaused()
|
||||
}
|
||||
replaceImpl = { [weak self, weak controller] c in
|
||||
controller?.dismiss(animated: true, completion: {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.controller()?.push(c)
|
||||
})
|
||||
}
|
||||
self.sendMessageContext.actionSheet = controller
|
||||
self.updateIsProgressPaused()
|
||||
|
||||
component.controller()?.push(controller)
|
||||
}
|
||||
|
||||
private func requestSave() {
|
||||
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {
|
||||
return
|
||||
@ -4360,6 +4479,24 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.requestSave()
|
||||
})))
|
||||
|
||||
if case let .user(accountUser) = component.slice.peer {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Stealth Mode", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if accountUser.isPremium {
|
||||
self.sendMessageContext.requestStealthMode(view: self)
|
||||
} else {
|
||||
self.presentStoriesUpgradeScreen()
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
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) || component.slice.item.storyItem.isPinned) {
|
||||
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)
|
||||
@ -4560,15 +4697,14 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if accountUser.isPremium {
|
||||
self.requestSave()
|
||||
} else {
|
||||
let premiumScreen = PremiumIntroScreen(context: component.context, source: .stories)
|
||||
component.controller()?.push(premiumScreen)
|
||||
self.presentStoriesUpgradeScreen()
|
||||
}
|
||||
})))
|
||||
}
|
||||
@ -4579,14 +4715,13 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if accountUser.isPremium {
|
||||
self.sendMessageContext.requestStealthMode(view: self)
|
||||
} else {
|
||||
let premiumScreen = PremiumIntroScreen(context: component.context, source: .stories)
|
||||
component.controller()?.push(premiumScreen)
|
||||
self.presentStoriesUpgradeScreen()
|
||||
}
|
||||
})))
|
||||
|
||||
|
@ -2989,12 +2989,12 @@ final class StoryItemSetContainerSendMessage {
|
||||
}
|
||||
|
||||
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||
if let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp {
|
||||
if let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp, !"".isEmpty {
|
||||
let remainingActiveSeconds = activeUntilTimestamp - timestamp
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
//TODO:localize
|
||||
let text = "The creators of stories you will view in the next \(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds)) won't see you in the viewers' lists."
|
||||
let text = "The creators of stories you will view in the next **\(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds))** won't see you in the viewers' lists."
|
||||
let tooltipScreen = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .actionSucceeded(title: "You are in Stealth Mode", text: text, cancel: "", destructive: false),
|
||||
@ -3021,7 +3021,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
//TODO:localize
|
||||
let text = "The creators of stories you will view in the next \(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds)) won't see you in the viewers' lists."
|
||||
let text = "The creators of stories you will view in the next **\(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds))** won't see you in the viewers' lists."
|
||||
tooltipScreenValue.content = .actionSucceeded(title: "You are in Stealth Mode", text: text, cancel: "", destructive: false)
|
||||
})
|
||||
|
||||
@ -3062,7 +3062,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
//TODO:localize
|
||||
let text = "The creators of stories you viewed in the last \(timeIntervalString(strings: presentationData.strings, value: pastPeriod)) or will view in the next \(timeIntervalString(strings: presentationData.strings, value: futurePeriod)) won’t see you in the viewers’ lists."
|
||||
let text = "The creators of stories you viewed in the last \(timeIntervalString(strings: presentationData.strings, value: pastPeriod)) or will view in the next **\(timeIntervalString(strings: presentationData.strings, value: futurePeriod))** won’t see you in the viewers’ lists."
|
||||
let tooltipScreen = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .actionSucceeded(title: "Stealth Mode On", text: text, cancel: "", destructive: false),
|
||||
|
@ -311,7 +311,7 @@ public final class StoryFooterPanelComponent: Component {
|
||||
if viewCount != 0 {
|
||||
expandedSegments.append(.number(viewCount, NSAttributedString(string: "\(viewCount)", font: Font.semibold(17.0), textColor: .white)))
|
||||
}
|
||||
expandedSegments.append(.text(1, NSAttributedString(string: " Views", font: Font.semibold(17.0), textColor: .white)))
|
||||
expandedSegments.append(.text(1, NSAttributedString(string: viewPart, font: Font.semibold(17.0), textColor: .white)))
|
||||
|
||||
let viewStatsTextLayout = self.viewStatsText.update(size: CGSize(width: availableSize.width, height: size.height), segments: regularSegments, transition: isFirstTime ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut))
|
||||
let expandedViewStatsTextLayout = self.viewStatsExpandedText.update(size: CGSize(width: availableSize.width, height: size.height), segments: expandedSegments, transition: isFirstTime ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut))
|
||||
|
@ -54,6 +54,8 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var timer: Timer?
|
||||
private var showCooldownToast: Bool = false
|
||||
private var hideCooldownTimer: Timer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -65,6 +67,27 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
||||
|
||||
deinit {
|
||||
self.timer?.invalidate()
|
||||
self.hideCooldownTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func displayCooldown() {
|
||||
if !self.showCooldownToast {
|
||||
self.showCooldownToast = true
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||
}
|
||||
|
||||
self.hideCooldownTimer?.invalidate()
|
||||
self.hideCooldownTimer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: false, block: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.hideCooldownTimer?.invalidate()
|
||||
self.hideCooldownTimer = nil
|
||||
|
||||
self.showCooldownToast = false
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||
})
|
||||
}
|
||||
|
||||
func update(component: StoryStealthModeSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
@ -96,7 +119,7 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
if remainingCooldownSeconds > 0 {
|
||||
if remainingCooldownSeconds > 0, self.showCooldownToast {
|
||||
let toast: ComponentView<Empty>
|
||||
var toastTransition = transition
|
||||
if let current = self.toast {
|
||||
@ -128,6 +151,7 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
||||
if let toastView = toast.view {
|
||||
if toastView.superview == nil {
|
||||
self.addSubview(toastView)
|
||||
toastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
|
||||
if let toastView = toastView as? ToastContentComponent.View, let iconView = toastView.iconView as? LottieComponent.View {
|
||||
iconView.playOnce()
|
||||
@ -138,7 +162,11 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
||||
} else {
|
||||
if let toast = self.toast {
|
||||
self.toast = nil
|
||||
toast.view?.removeFromSuperview()
|
||||
if let toastView = toast.view {
|
||||
toastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak toastView] _ in
|
||||
toastView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,12 +212,24 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
||||
Text(text: buttonText, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
isEnabled: remainingCooldownSeconds <= 0,
|
||||
allowActionWhenDisabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.dismiss()
|
||||
|
||||
var remainingCooldownSeconds: Int32 = 0
|
||||
if let cooldownUntilTimestamp = component.cooldownUntilTimestamp {
|
||||
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
||||
}
|
||||
|
||||
if remainingCooldownSeconds > 0 {
|
||||
self.displayCooldown()
|
||||
} else {
|
||||
component.dismiss()
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
@ -3355,7 +3355,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
if self.navigationTransition != nil {
|
||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||
var trueAvatarSize = transitionSourceAvatarFrame.size
|
||||
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
|
||||
trueAvatarSize.width -= 1.33 * 4.0
|
||||
trueAvatarSize.height -= 1.33 * 4.0
|
||||
}
|
||||
@ -3397,7 +3397,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.avatarListNode.listContainerNode.isHidden = false
|
||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||
var trueAvatarSize = transitionSourceAvatarFrame.size
|
||||
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
|
||||
trueAvatarSize.width -= 1.33 * 4.0
|
||||
trueAvatarSize.height -= 1.33 * 4.0
|
||||
}
|
||||
@ -3446,7 +3446,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
apparentAvatarFrame = CGRect(origin: CGPoint(x: expandedAvatarCenter.x * (1.0 - transitionFraction) + transitionFraction * avatarCenter.x, y: expandedAvatarCenter.y * (1.0 - transitionFraction) + transitionFraction * avatarCenter.y), size: CGSize())
|
||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||
var trueAvatarSize = transitionSourceAvatarFrame.size
|
||||
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
|
||||
trueAvatarSize.width -= 1.33 * 4.0
|
||||
trueAvatarSize.height -= 1.33 * 4.0
|
||||
}
|
||||
@ -3459,7 +3459,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
var trueAvatarSize = avatarFrame.size
|
||||
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
|
||||
trueAvatarSize.width -= 3.0 * 4.0
|
||||
trueAvatarSize.height -= 3.0 * 4.0
|
||||
}
|
||||
@ -3484,7 +3484,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let neutralAvatarListContainerSize = expandedAvatarListSize
|
||||
var avatarListContainerSize = CGSize(width: neutralAvatarListContainerSize.width * (1.0 - transitionFraction) + transitionSourceAvatarFrame.width * transitionFraction, height: neutralAvatarListContainerSize.height * (1.0 - transitionFraction) + transitionSourceAvatarFrame.height * transitionFraction)
|
||||
|
||||
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
|
||||
avatarListContainerSize.width -= 1.33 * 5.0
|
||||
avatarListContainerSize.height -= 1.33 * 5.0
|
||||
}
|
||||
|
@ -116,7 +116,6 @@ public class ShareRootControllerImpl {
|
||||
private var currentShareController: ShareController?
|
||||
private var currentPasscodeController: ViewController?
|
||||
|
||||
private var shouldBeMaster = Promise<Bool>()
|
||||
private let disposable = MetaDisposable()
|
||||
private var observer1: AnyObject?
|
||||
private var observer2: AnyObject?
|
||||
@ -130,7 +129,6 @@ public class ShareRootControllerImpl {
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.shouldBeMaster.set(.single(false))
|
||||
if let observer = self.observer1 {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user