Profile preview

This commit is contained in:
Isaac 2024-04-02 16:28:01 +04:00
parent db60f2c82d
commit baca7c8661
19 changed files with 870 additions and 117 deletions

View File

@ -11884,3 +11884,6 @@ Sorry for the inconvenience.";
"BusinessLink.ErrorExpired" = "Link Expired"; "BusinessLink.ErrorExpired" = "Link Expired";
"WebApp.AlertBiometryAccessText" = "Do you want to allow %@ to use Face ID?"; "WebApp.AlertBiometryAccessText" = "Do you want to allow %@ to use Face ID?";
"StoryList.SubtitleArchived_1" = "1 archived post";
"StoryList.SubtitleArchived_any" = "%d archived posts";

View File

@ -19,6 +19,7 @@
@property (nonatomic, assign) bool displayEdges; @property (nonatomic, assign) bool displayEdges;
@property (nonatomic, assign) bool useLinesForPositions; @property (nonatomic, assign) bool useLinesForPositions;
@property (nonatomic, assign) bool markPositions;
@property (nonatomic, readonly) bool knobStartedDragging; @property (nonatomic, readonly) bool knobStartedDragging;

View File

@ -42,6 +42,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
_value = _startValue; _value = _startValue;
_dotSize = 10.5f; _dotSize = 10.5f;
_minimumUndottedValue = -1; _minimumUndottedValue = -1;
_markPositions = true;
_lineSize = TGPhotoEditorSliderViewLineSize; _lineSize = TGPhotoEditorSliderViewLineSize;
_knobPadding = TGPhotoEditorSliderViewInternalMargin; _knobPadding = TGPhotoEditorSliderViewInternalMargin;
@ -214,6 +215,12 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
{ {
for (NSInteger i = 0; i < self.positionsCount; i++) for (NSInteger i = 0; i < self.positionsCount; i++)
{ {
if (!self.markPositions) {
if (i != 0 && i != self.positionsCount - 1) {
continue;
}
}
if (self.useLinesForPositions) { if (self.useLinesForPositions) {
CGSize lineSize = CGSizeMake(4.0, 12.0); CGSize lineSize = CGSizeMake(4.0, 12.0);
CGRect lineRect = CGRectMake(margin - lineSize.width / 2.0f + totalLength / (self.positionsCount - 1) * i, (sideLength - lineSize.height) / 2, lineSize.width, lineSize.height); CGRect lineRect = CGRectMake(margin - lineSize.width / 2.0f + totalLength / (self.positionsCount - 1) * i, (sideLength - lineSize.height) / 2, lineSize.width, lineSize.height);

View File

@ -73,6 +73,7 @@ public struct PresentationResourcesSettings {
public static let stories = renderIcon(name: "Settings/Menu/Stories") public static let stories = renderIcon(name: "Settings/Menu/Stories")
public static let premiumGift = renderIcon(name: "Settings/Menu/Gift") public static let premiumGift = renderIcon(name: "Settings/Menu/Gift")
public static let business = renderIcon(name: "Settings/Menu/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)]) public static let business = renderIcon(name: "Settings/Menu/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)])
public static let myProfile = renderIcon(name: "Settings/Menu/Profile")
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)

View File

@ -15,6 +15,7 @@ public final class EmptyStateIndicatorComponent: Component {
public let title: String public let title: String
public let text: String public let text: String
public let actionTitle: String? public let actionTitle: String?
public let fitToHeight: Bool
public let action: () -> Void public let action: () -> Void
public let additionalActionTitle: String? public let additionalActionTitle: String?
public let additionalAction: () -> Void public let additionalAction: () -> Void
@ -22,6 +23,7 @@ public final class EmptyStateIndicatorComponent: Component {
public init( public init(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
fitToHeight: Bool,
animationName: String, animationName: String,
title: String, title: String,
text: String, text: String,
@ -32,6 +34,7 @@ public final class EmptyStateIndicatorComponent: Component {
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.fitToHeight = fitToHeight
self.animationName = animationName self.animationName = animationName
self.title = title self.title = title
self.text = text self.text = text
@ -48,6 +51,9 @@ public final class EmptyStateIndicatorComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.fitToHeight != rhs.fitToHeight {
return false
}
if lhs.animationName != rhs.animationName { if lhs.animationName != rhs.animationName {
return false return false
} }
@ -205,7 +211,12 @@ public final class EmptyStateIndicatorComponent: Component {
totalHeight += buttonSpacing + additionalButtonSize.height totalHeight += buttonSpacing + additionalButtonSize.height
} }
var contentY = floor((availableSize.height - totalHeight) * 0.5) var contentY: CGFloat
if component.fitToHeight {
contentY = 0.0
} else {
contentY = floor((availableSize.height - totalHeight) * 0.5)
}
if let animationView = self.animation.view { if let animationView = self.animation.view {
if animationView.superview == nil { if animationView.superview == nil {
@ -243,7 +254,11 @@ public final class EmptyStateIndicatorComponent: Component {
contentY += additionalButtonSize.height contentY += additionalButtonSize.height
} }
return availableSize if component.fitToHeight {
return CGSize(width: availableSize.width, height: totalHeight)
} else {
return availableSize
}
} }
} }

View File

@ -11,18 +11,24 @@ import SliderComponent
public final class ListItemSliderSelectorComponent: Component { public final class ListItemSliderSelectorComponent: Component {
public let theme: PresentationTheme public let theme: PresentationTheme
public let values: [String] public let values: [String]
public let markPositions: Bool
public let selectedIndex: Int public let selectedIndex: Int
public let title: String?
public let selectedIndexUpdated: (Int) -> Void public let selectedIndexUpdated: (Int) -> Void
public init( public init(
theme: PresentationTheme, theme: PresentationTheme,
values: [String], values: [String],
markPositions: Bool,
selectedIndex: Int, selectedIndex: Int,
title: String?,
selectedIndexUpdated: @escaping (Int) -> Void selectedIndexUpdated: @escaping (Int) -> Void
) { ) {
self.theme = theme self.theme = theme
self.values = values self.values = values
self.markPositions = markPositions
self.selectedIndex = selectedIndex self.selectedIndex = selectedIndex
self.title = title
self.selectedIndexUpdated = selectedIndexUpdated self.selectedIndexUpdated = selectedIndexUpdated
} }
@ -33,14 +39,21 @@ public final class ListItemSliderSelectorComponent: Component {
if lhs.values != rhs.values { if lhs.values != rhs.values {
return false return false
} }
if lhs.markPositions != rhs.markPositions {
return false
}
if lhs.selectedIndex != rhs.selectedIndex { if lhs.selectedIndex != rhs.selectedIndex {
return false return false
} }
if lhs.title != rhs.title {
return false
}
return true return true
} }
public final class View: UIView, ListSectionComponent.ChildView { public final class View: UIView, ListSectionComponent.ChildView {
private var titles: [ComponentView<Empty>] = [] private var titles: [Int: ComponentView<Empty>] = [:]
private var mainTitle: ComponentView<Empty>?
private var slider = ComponentView<Empty>() private var slider = ComponentView<Empty>()
private var component: ListItemSliderSelectorComponent? private var component: ListItemSliderSelectorComponent?
@ -67,15 +80,24 @@ public final class ListItemSliderSelectorComponent: Component {
let titleAreaWidth: CGFloat = availableSize.width - titleSideInset * 2.0 let titleAreaWidth: CGFloat = availableSize.width - titleSideInset * 2.0
var validIds: [Int] = []
for i in 0 ..< component.values.count { for i in 0 ..< component.values.count {
if component.title != nil {
if i != 0 && i != component.values.count - 1 {
continue
}
}
validIds.append(i)
var titleTransition = transition var titleTransition = transition
let title: ComponentView<Empty> let title: ComponentView<Empty>
if self.titles.count > i { if let current = self.titles[i] {
title = self.titles[i] title = current
} else { } else {
titleTransition = titleTransition.withAnimation(.none) titleTransition = titleTransition.withAnimation(.none)
title = ComponentView() title = ComponentView()
self.titles.append(title) self.titles[i] = title
} }
let titleSize = title.update( let titleSize = title.update(
transition: .immediate, transition: .immediate,
@ -103,11 +125,48 @@ public final class ListItemSliderSelectorComponent: Component {
titleTransition.setPosition(view: titleView, position: titleFrame.center) titleTransition.setPosition(view: titleView, position: titleFrame.center)
} }
} }
if self.titles.count > component.values.count { var removeIds: [Int] = []
for i in component.values.count ..< self.titles.count { for (id, title) in self.titles {
self.titles[i].view?.removeFromSuperview() if !validIds.contains(id) {
removeIds.append(id)
title.view?.removeFromSuperview()
}
}
for id in removeIds {
self.titles.removeValue(forKey: id)
}
if let title = component.title {
let mainTitle: ComponentView<Empty>
var mainTitleTransition = transition
if let current = self.mainTitle {
mainTitle = current
} else {
mainTitleTransition = mainTitleTransition.withAnimation(.none)
mainTitle = ComponentView()
self.mainTitle = mainTitle
}
let mainTitleSize = mainTitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: title, font: Font.regular(16.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let mainTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - mainTitleSize.width) * 0.5), y: 10.0), size: mainTitleSize)
if let mainTitleView = mainTitle.view {
if mainTitleView.superview == nil {
self.addSubview(mainTitleView)
}
mainTitleView.bounds = CGRect(origin: CGPoint(), size: mainTitleFrame.size)
mainTitleTransition.setPosition(view: mainTitleView, position: mainTitleFrame.center)
}
} else {
if let mainTitle = self.mainTitle {
self.mainTitle = nil
mainTitle.view?.removeFromSuperview()
} }
self.titles.removeLast(self.titles.count - component.values.count)
} }
let sliderSize = self.slider.update( let sliderSize = self.slider.update(
@ -115,6 +174,7 @@ public final class ListItemSliderSelectorComponent: Component {
component: AnyComponent(SliderComponent( component: AnyComponent(SliderComponent(
valueCount: component.values.count, valueCount: component.values.count,
value: component.selectedIndex, value: component.selectedIndex,
markPositions: component.markPositions,
trackBackgroundColor: component.theme.list.controlSecondaryColor, trackBackgroundColor: component.theme.list.controlSecondaryColor,
trackForegroundColor: component.theme.list.itemAccentColor, trackForegroundColor: component.theme.list.itemAccentColor,
valueUpdated: { [weak self] value in valueUpdated: { [weak self] value in

View File

@ -33,6 +33,8 @@ swift_library(
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/Components/HierarchyTrackingLayer", "//submodules/Components/HierarchyTrackingLayer",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -21,6 +21,8 @@ import AnimatedTextComponent
import TextFormat import TextFormat
import AudioToolbox import AudioToolbox
import PremiumLockButtonSubtitleComponent import PremiumLockButtonSubtitleComponent
import ListSectionComponent
import ListItemSliderSelectorComponent
final class PeerAllowedReactionsScreenComponent: Component { final class PeerAllowedReactionsScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -57,6 +59,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
private var reactionsTitleText: ComponentView<Empty>? private var reactionsTitleText: ComponentView<Empty>?
private var reactionsInfoText: ComponentView<Empty>? private var reactionsInfoText: ComponentView<Empty>?
private var reactionInput: ComponentView<Empty>? private var reactionInput: ComponentView<Empty>?
private var reactionCountSection: ComponentView<Empty>?
private let actionButton = ComponentView<Empty>() private let actionButton = ComponentView<Empty>()
private var reactionSelectionControl: ComponentView<Empty>? private var reactionSelectionControl: ComponentView<Empty>?
@ -82,6 +85,8 @@ final class PeerAllowedReactionsScreenComponent: Component {
private var displayInput: Bool = false private var displayInput: Bool = false
private var recenterOnCaret: Bool = false private var recenterOnCaret: Bool = false
private var allowedReactionCount: Int = 11
private var isApplyingSettings: Bool = false private var isApplyingSettings: Bool = false
private var applyDisposable: Disposable? private var applyDisposable: Disposable?
@ -775,6 +780,86 @@ final class PeerAllowedReactionsScreenComponent: Component {
} }
contentHeight += reactionsInfoTextSize.height contentHeight += reactionsInfoTextSize.height
contentHeight += 6.0 contentHeight += 6.0
contentHeight += 32.0
let reactionCountSection: ComponentView<Empty>
if let current = self.reactionCountSection {
reactionCountSection = current
} else {
reactionCountSection = ComponentView()
self.reactionCountSection = reactionCountSection
}
let reactionCountValueList = (1 ... 11).map { i -> String in
return "\(i)"
}
//TODO:localize
let sliderTitle: String
if self.allowedReactionCount == 1 {
sliderTitle = "1 reaction"
} else {
sliderTitle = "\(self.allowedReactionCount) reactions"
}
let reactionCountSectionSize = reactionCountSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "MAXIMUM REACTIONS PER POST",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Limit the number of different reactions that can be added to a post, including already published posts.",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme,
values: reactionCountValueList.map { item in
return item
},
markPositions: false,
selectedIndex: max(0, min(reactionCountValueList.count - 1, self.allowedReactionCount - 1)),
title: sliderTitle,
selectedIndexUpdated: { [weak self] index in
guard let self else {
return
}
let index = max(1, min(reactionCountValueList.count, index + 1))
self.allowedReactionCount = index
self.state?.updated(transition: .immediate)
}
)))
],
displaySeparators: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let reactionCountSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: reactionCountSectionSize)
if let reactionCountSectionView = reactionCountSection.view {
if reactionCountSectionView.superview == nil {
self.scrollView.addSubview(reactionCountSectionView)
}
if animateIn {
reactionCountSectionView.frame = reactionCountSectionFrame
if !transition.animation.isImmediate {
reactionCountSectionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} else {
transition.setFrame(view: reactionCountSectionView, frame: reactionCountSectionFrame)
}
}
contentHeight += reactionCountSectionSize.height
} else { } else {
if let reactionsTitleText = self.reactionsTitleText { if let reactionsTitleText = self.reactionsTitleText {
self.reactionsTitleText = nil self.reactionsTitleText = nil
@ -814,6 +899,19 @@ final class PeerAllowedReactionsScreenComponent: Component {
} }
} }
} }
if let reactionCountSection = self.reactionCountSection {
self.reactionCountSection = nil
if let reactionCountSectionView = reactionCountSection.view {
if !transition.animation.isImmediate {
reactionCountSectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountSectionView] _ in
reactionCountSectionView?.removeFromSuperview()
})
} else {
reactionCountSectionView.removeFromSuperview()
}
}
}
} }
var buttonContents: [AnyComponentWithIdentity<Empty>] = [] var buttonContents: [AnyComponentWithIdentity<Empty>] = []

View File

@ -8,6 +8,7 @@ import TelegramPresentationData
public enum PeerInfoPaneKey: Int32 { public enum PeerInfoPaneKey: Int32 {
case members case members
case stories case stories
case storyArchive
case media case media
case savedMessagesChats case savedMessagesChats
case savedMessages case savedMessages

View File

@ -305,6 +305,7 @@ final class PeerInfoScreenData {
let linkedDiscussionPeer: Peer? let linkedDiscussionPeer: Peer?
let members: PeerInfoMembersData? let members: PeerInfoMembersData?
let storyListContext: PeerStoryListContext? let storyListContext: PeerStoryListContext?
let storyArchiveListContext: PeerStoryListContext?
let encryptionKeyFingerprint: SecretChatKeyFingerprint? let encryptionKeyFingerprint: SecretChatKeyFingerprint?
let globalSettings: TelegramGlobalSettings? let globalSettings: TelegramGlobalSettings?
let invitations: PeerExportedInvitationsState? let invitations: PeerExportedInvitationsState?
@ -344,6 +345,7 @@ final class PeerInfoScreenData {
linkedDiscussionPeer: Peer?, linkedDiscussionPeer: Peer?,
members: PeerInfoMembersData?, members: PeerInfoMembersData?,
storyListContext: PeerStoryListContext?, storyListContext: PeerStoryListContext?,
storyArchiveListContext: PeerStoryListContext?,
encryptionKeyFingerprint: SecretChatKeyFingerprint?, encryptionKeyFingerprint: SecretChatKeyFingerprint?,
globalSettings: TelegramGlobalSettings?, globalSettings: TelegramGlobalSettings?,
invitations: PeerExportedInvitationsState?, invitations: PeerExportedInvitationsState?,
@ -371,6 +373,7 @@ final class PeerInfoScreenData {
self.linkedDiscussionPeer = linkedDiscussionPeer self.linkedDiscussionPeer = linkedDiscussionPeer
self.members = members self.members = members
self.storyListContext = storyListContext self.storyListContext = storyListContext
self.storyArchiveListContext = storyArchiveListContext
self.encryptionKeyFingerprint = encryptionKeyFingerprint self.encryptionKeyFingerprint = encryptionKeyFingerprint
self.globalSettings = globalSettings self.globalSettings = globalSettings
self.invitations = invitations self.invitations = invitations
@ -403,7 +406,7 @@ private enum PeerInfoScreenInputData: Equatable {
public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<Bool, NoError> { public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<Bool, NoError> {
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil) let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let mediaPanes = peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), chatLocationContextHolder: chatLocationContextHolder) let mediaPanes = peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder)
|> map { panes -> Bool in |> map { panes -> Bool in
if let panes { if let panes {
return !panes.isEmpty return !panes.isEmpty
@ -426,15 +429,19 @@ public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: Peer
} }
} }
private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<[PeerInfoPaneKey]?, NoError> { private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, isMyProfile: Bool, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<[PeerInfoPaneKey]?, NoError> {
let tags: [(MessageTags, PeerInfoPaneKey)] = [ var tags: [(MessageTags, PeerInfoPaneKey)] = []
(.photoOrVideo, .media),
(.file, .files), if !isMyProfile {
(.music, .music), tags = [
(.voiceOrInstantVideo, .voice), (.photoOrVideo, .media),
(.webPage, .links), (.file, .files),
(.gif, .gifs) (.music, .music),
] (.voiceOrInstantVideo, .voice),
(.webPage, .links),
(.gif, .gifs)
]
}
enum PaneState { enum PaneState {
case loading case loading
case empty case empty
@ -545,7 +552,7 @@ public func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, c
case .user, .channel, .group: case .user, .channel, .group:
var signals: [Signal<Never, NoError>] = [] var signals: [Signal<Never, NoError>] = []
signals.append(context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder) |> ignoreValues) |> ignoreValues) signals.append(context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder) |> ignoreValues) |> ignoreValues)
signals.append(context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues) signals.append(context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues)
if case .user = inputData { if case .user = inputData {
@ -843,6 +850,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
linkedDiscussionPeer: nil, linkedDiscussionPeer: nil,
members: nil, members: nil,
storyListContext: hasStories == true ? storyListContext : nil, storyListContext: hasStories == true ? storyListContext : nil,
storyArchiveListContext: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: globalSettings, globalSettings: globalSettings,
invitations: nil, invitations: nil,
@ -859,7 +867,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
} }
} }
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<PeerInfoScreenData, NoError> { func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<PeerInfoScreenData, NoError> {
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings) return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings)
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in |> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
let wasUpgradedGroup = Atomic<Bool?>(value: nil) let wasUpgradedGroup = Atomic<Bool?>(value: nil)
@ -881,6 +889,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: nil, linkedDiscussionPeer: nil,
members: nil, members: nil,
storyListContext: nil, storyListContext: nil,
storyArchiveListContext: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: nil, globalSettings: nil,
invitations: nil, invitations: nil,
@ -896,7 +905,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
)) ))
case let .user(userPeerId, secretChatId, kind): case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext? let groupsInCommon: GroupsInCommonContext?
if [.user, .bot].contains(kind) { if isMyProfile {
groupsInCommon = nil
} else if [.user, .bot].contains(kind) {
groupsInCommon = GroupsInCommonContext(account: context.account, peerId: userPeerId, hintGroupInCommon: hintGroupInCommon) groupsInCommon = GroupsInCommonContext(account: context.account, peerId: userPeerId, hintGroupInCommon: hintGroupInCommon)
} else { } else {
groupsInCommon = nil groupsInCommon = nil
@ -1008,6 +1019,23 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} }
|> distinctUntilChanged |> distinctUntilChanged
let hasStoryArchive: Signal<Bool?, NoError>
var storyArchiveListContext: PeerStoryListContext?
if isMyProfile {
let storyArchiveListContextValue = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true)
storyArchiveListContext = storyArchiveListContextValue
hasStoryArchive = storyArchiveListContextValue.state
|> map { state -> Bool? in
if !state.hasCache {
return nil
}
return !state.items.isEmpty
}
|> distinctUntilChanged
} else {
hasStoryArchive = .single(false)
}
let accountIsPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) let accountIsPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in |> map { peer -> Bool in
return peer?.isPremium ?? false return peer?.isPremium ?? false
@ -1092,11 +1120,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
return combineLatest( return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true), context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: isMyProfile, chatLocationContextHolder: chatLocationContextHolder),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
secretChatKeyFingerprint, secretChatKeyFingerprint,
status, status,
hasStories, hasStories,
hasStoryArchive,
accountIsPremium, accountIsPremium,
savedMessagesPeer, savedMessagesPeer,
hasSavedMessagesChats, hasSavedMessagesChats,
@ -1104,10 +1133,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
hasSavedMessageTags, hasSavedMessageTags,
peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false) peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false)
) )
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, personalChannel -> PeerInfoScreenData in |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, personalChannel -> PeerInfoScreenData in
var availablePanes = availablePanes var availablePanes = availablePanes
if let hasStories { if isMyProfile {
availablePanes?.insert(.stories, at: 0)
if let hasStoryArchive, hasStoryArchive {
availablePanes?.insert(.storyArchive, at: 1)
}
} else if let hasStories {
if hasStories, peerView.peers[peerView.peerId] is TelegramUser, peerView.peerId != context.account.peerId { if hasStories, peerView.peers[peerView.peerId] is TelegramUser, peerView.peerId != context.account.peerId {
availablePanes?.insert(.stories, at: 0) availablePanes?.insert(.stories, at: 0)
} }
@ -1155,6 +1189,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: nil, linkedDiscussionPeer: nil,
members: nil, members: nil,
storyListContext: storyListContext, storyListContext: storyListContext,
storyArchiveListContext: storyArchiveListContext,
encryptionKeyFingerprint: encryptionKeyFingerprint, encryptionKeyFingerprint: encryptionKeyFingerprint,
globalSettings: nil, globalSettings: nil,
invitations: nil, invitations: nil,
@ -1244,7 +1279,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
return combineLatest( return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true), context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
status, status,
invitationsContextPromise.get(), invitationsContextPromise.get(),
@ -1325,6 +1360,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: discussionPeer, linkedDiscussionPeer: discussionPeer,
members: nil, members: nil,
storyListContext: storyListContext, storyListContext: storyListContext,
storyArchiveListContext: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: nil, globalSettings: nil,
invitations: invitations, invitations: invitations,
@ -1517,7 +1553,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
return combineLatest(queue: .mainQueue(), return combineLatest(queue: .mainQueue(),
context.account.viewTracker.peerView(groupId, updateData: true), context.account.viewTracker.peerView(groupId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
status, status,
membersData, membersData,
@ -1618,6 +1654,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
linkedDiscussionPeer: discussionPeer, linkedDiscussionPeer: discussionPeer,
members: membersData, members: membersData,
storyListContext: storyListContext, storyListContext: storyListContext,
storyArchiveListContext: nil,
encryptionKeyFingerprint: nil, encryptionKeyFingerprint: nil,
globalSettings: nil, globalSettings: nil,
invitations: invitations, invitations: invitations,

View File

@ -88,6 +88,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
private let isOpenedFromChat: Bool private let isOpenedFromChat: Bool
private let isSettings: Bool private let isSettings: Bool
private let isMyProfile: Bool
private let videoCallsEnabled: Bool private let videoCallsEnabled: Bool
private let forumTopicThreadId: Int64? private let forumTopicThreadId: Int64?
private let chatLocation: ChatLocation private let chatLocation: ChatLocation
@ -179,12 +180,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
private var validLayout: (width: CGFloat, deviceMetrics: DeviceMetrics)? private var validLayout: (width: CGFloat, deviceMetrics: DeviceMetrics)?
init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) { init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, isMyProfile: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) {
self.context = context self.context = context
self.controller = controller self.controller = controller
self.isAvatarExpanded = avatarInitiallyExpanded self.isAvatarExpanded = avatarInitiallyExpanded
self.isOpenedFromChat = isOpenedFromChat self.isOpenedFromChat = isOpenedFromChat
self.isSettings = isSettings self.isSettings = isSettings
self.isMyProfile = isMyProfile
self.videoCallsEnabled = true self.videoCallsEnabled = true
self.forumTopicThreadId = forumTopicThreadId self.forumTopicThreadId = forumTopicThreadId
self.chatLocation = chatLocation self.chatLocation = chatLocation
@ -522,7 +524,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let credibilityIcon: CredibilityIcon let credibilityIcon: CredibilityIcon
var verifiedIcon: CredibilityIcon = .none var verifiedIcon: CredibilityIcon = .none
if let peer = peer { if let peer = peer {
if peer.id == self.context.account.peerId && !self.isSettings { if peer.id == self.context.account.peerId && !self.isSettings && !self.isMyProfile {
credibilityIcon = .none credibilityIcon = .none
} else if peer.isFake { } else if peer.isFake {
credibilityIcon = .fake credibilityIcon = .fake
@ -535,7 +537,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
credibilityIcon = .emojiStatus(emojiStatus) credibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified { } else if peer.isVerified {
credibilityIcon = .verified credibilityIcon = .verified
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != self.context.account.peerId || self.isSettings) { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != self.context.account.peerId || self.isSettings || self.isMyProfile) {
credibilityIcon = .premium credibilityIcon = .premium
} else { } else {
credibilityIcon = .none credibilityIcon = .none
@ -554,7 +556,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0 self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0
let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, threadData: threadData, chatLocation: self.chatLocation, cachedData: cachedData, isContact: isContact, isSettings: isSettings, presentationData: presentationData, transition: transition) let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, threadData: threadData, chatLocation: self.chatLocation, cachedData: cachedData, isContact: isContact, isSettings: isSettings || isMyProfile, presentationData: presentationData, transition: transition)
transition.updateFrame(node: self.editingContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -contentOffset), size: CGSize(width: width, height: editingContentHeight))) transition.updateFrame(node: self.editingContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -contentOffset), size: CGSize(width: width, height: editingContentHeight)))
let avatarOverlayFarme = self.editingContentNode.convert(self.editingContentNode.avatarNode.frame, to: self) let avatarOverlayFarme = self.editingContentNode.convert(self.editingContentNode.avatarNode.frame, to: self)
@ -694,7 +696,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} else { } else {
let backgroundTransitionStepDistance: CGFloat = 50.0 let backgroundTransitionStepDistance: CGFloat = 50.0
var backgroundTransitionDistance: CGFloat = navigationHeight + panelWithAvatarHeight - backgroundTransitionStepDistance var backgroundTransitionDistance: CGFloat = navigationHeight + panelWithAvatarHeight - backgroundTransitionStepDistance
if self.isSettings { if self.isSettings || self.isMyProfile {
backgroundTransitionDistance -= 100.0 backgroundTransitionDistance -= 100.0
} }
if isMediaOnly { if isMediaOnly {
@ -978,15 +980,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.navigationBackgroundBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor self.navigationBackgroundBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
self.navigationSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.navigationSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
let navigationSeparatorAlpha: CGFloat let navigationSeparatorAlpha: CGFloat = 0.0
if isMediaOnly {
navigationSeparatorAlpha = 0.0
} else if state.isEditing && self.isSettings {
//navigationSeparatorAlpha = min(1.0, contentOffset / (navigationHeight * 0.5))
navigationSeparatorAlpha = 0.0
} else {
navigationSeparatorAlpha = 0.0
}
transition.updateAlpha(node: self.navigationBackgroundBackgroundNode, alpha: 1.0 - navigationSeparatorAlpha) transition.updateAlpha(node: self.navigationBackgroundBackgroundNode, alpha: 1.0 - navigationSeparatorAlpha)
transition.updateAlpha(node: self.navigationSeparatorNode, alpha: navigationSeparatorAlpha) transition.updateAlpha(node: self.navigationSeparatorNode, alpha: navigationSeparatorAlpha)
@ -994,7 +988,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let expandedAvatarControlsHeight: CGFloat = 61.0 let expandedAvatarControlsHeight: CGFloat = 61.0
var expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight) var expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight)
if self.isSettings { if self.isSettings || self.isMyProfile {
expandedAvatarListHeight = expandedAvatarListHeight + 60.0 expandedAvatarListHeight = expandedAvatarListHeight + 60.0
} else { } else {
expandedAvatarListHeight = expandedAvatarListHeight + 98.0 expandedAvatarListHeight = expandedAvatarListHeight + 98.0
@ -1002,8 +996,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight) let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight)
let actionButtonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderActionButtons(peer: peer, isSecretChat: isSecretChat, isContact: isContact) let actionButtonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderActionButtons(peer: peer, isSecretChat: isSecretChat, isContact: isContact)
let buttonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info) let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info)
var isPremium = false var isPremium = false
var isVerified = false var isVerified = false
@ -1029,7 +1023,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if let peer = peer { if let peer = peer {
var title: String var title: String
if peer.id == self.context.account.peerId && !self.isSettings { if peer.id == self.context.account.peerId && !self.isSettings && !self.isMyProfile {
if case .replyThread = self.chatLocation { if case .replyThread = self.chatLocation {
title = presentationData.strings.Conversation_MyNotes title = presentationData.strings.Conversation_MyNotes
} else { } else {
@ -1069,6 +1063,26 @@ final class PeerInfoHeaderNode: ASDisplayNode {
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor) smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)) usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
} else if self.isMyProfile {
let subtitleColor: UIColor
subtitleColor = .white
subtitleStringText = presentationData.strings.Presence_online
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
let (maybePanelStatusData, _, _) = panelStatusData
if let panelStatusData = maybePanelStatusData {
let subtitleColor: UIColor
if panelStatusData.isActivity {
subtitleColor = UIColor.white
} else {
subtitleColor = UIColor.white
}
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
}
} else if let _ = threadData { } else if let _ = threadData {
let subtitleColor: UIColor let subtitleColor: UIColor
subtitleColor = UIColor.white subtitleColor = UIColor.white
@ -1341,14 +1355,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let expandedTitleScale: CGFloat = 0.8 let expandedTitleScale: CGFloat = 0.8
var bottomShadowHeight: CGFloat = 88.0 var bottomShadowHeight: CGFloat = 88.0
if !self.isSettings { if !self.isSettings && !self.isMyProfile {
bottomShadowHeight += 100.0 bottomShadowHeight += 100.0
} }
let bottomShadowFrame = CGRect(origin: CGPoint(x: 0.0, y: expandedAvatarHeight - bottomShadowHeight), size: CGSize(width: width, height: bottomShadowHeight)) let bottomShadowFrame = CGRect(origin: CGPoint(x: 0.0, y: expandedAvatarHeight - bottomShadowHeight), size: CGSize(width: width, height: bottomShadowHeight))
transition.updateFrame(node: self.avatarListNode.listContainerNode.bottomShadowNode, frame: bottomShadowFrame, beginWithCurrentState: true) transition.updateFrame(node: self.avatarListNode.listContainerNode.bottomShadowNode, frame: bottomShadowFrame, beginWithCurrentState: true)
self.avatarListNode.listContainerNode.bottomShadowNode.update(size: bottomShadowFrame.size, transition: transition) self.avatarListNode.listContainerNode.bottomShadowNode.update(size: bottomShadowFrame.size, transition: transition)
let singleTitleLockOffset: CGFloat = (peer?.id == self.context.account.peerId || subtitleSize.height.isZero) ? 8.0 : 0.0 let singleTitleLockOffset: CGFloat = ((peer?.id == self.context.account.peerId && !self.isMyProfile) || subtitleSize.height.isZero) ? 8.0 : 0.0
let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset
let titleMaxLockOffset: CGFloat = 7.0 let titleMaxLockOffset: CGFloat = 7.0
@ -1358,14 +1372,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if self.isAvatarExpanded { if self.isAvatarExpanded {
let minTitleSize = CGSize(width: titleSize.width * expandedTitleScale, height: titleSize.height * expandedTitleScale) let minTitleSize = CGSize(width: titleSize.width * expandedTitleScale, height: titleSize.height * expandedTitleScale)
var minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - 58.0 - UIScreenPixel + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize) var minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - 58.0 - UIScreenPixel + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize)
if !self.isSettings { if !self.isSettings && !self.isMyProfile {
minTitleFrame.origin.y -= 83.0 minTitleFrame.origin.y -= 83.0
} }
titleFrame = CGRect(origin: CGPoint(x: minTitleFrame.midX - titleSize.width / 2.0, y: minTitleFrame.midY - titleSize.height / 2.0), size: titleSize) titleFrame = CGRect(origin: CGPoint(x: minTitleFrame.midX - titleSize.width / 2.0, y: minTitleFrame.midY - titleSize.height / 2.0), size: titleSize)
var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset
if case .regular = metrics.widthClass, !isSettings { if case .regular = metrics.widthClass, !isSettings, !isMyProfile {
titleCollapseOffset -= 7.0 titleCollapseOffset -= 7.0
} }
titleOffset = -min(titleCollapseOffset, contentOffset) titleOffset = -min(titleCollapseOffset, contentOffset)
@ -1377,7 +1391,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 9.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize) titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 9.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize)
var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset
if case .regular = metrics.widthClass, !isSettings { if case .regular = metrics.widthClass, !isSettings, !isMyProfile {
titleCollapseOffset -= 7.0 titleCollapseOffset -= 7.0
} }
titleOffset = -min(titleCollapseOffset, contentOffset) titleOffset = -min(titleCollapseOffset, contentOffset)
@ -1408,7 +1422,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let effectiveAreaExpansionFraction: CGFloat let effectiveAreaExpansionFraction: CGFloat
if state.isEditing { if state.isEditing {
effectiveAreaExpansionFraction = 0.0 effectiveAreaExpansionFraction = 0.0
} else if isSettings { } else if isSettings || isMyProfile {
var paneAreaExpansionDelta = (self.frame.maxY - navigationHeight) - contentOffset var paneAreaExpansionDelta = (self.frame.maxY - navigationHeight) - contentOffset
paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance)) paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance))
effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance
@ -1716,12 +1730,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} else { } else {
rawHeight = navigationHeight + panelWithAvatarHeight rawHeight = navigationHeight + panelWithAvatarHeight
var expandablePart: CGFloat = panelWithAvatarHeight - contentOffset var expandablePart: CGFloat = panelWithAvatarHeight - contentOffset
if self.isSettings { if self.isSettings || self.isMyProfile {
expandablePart += 20.0 expandablePart += 20.0
} else { } else {
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId { if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId {
expandablePart = 0.0 expandablePart = 0.0
} else if peer?.id == self.context.account.peerId { } else if peer?.id == self.context.account.peerId && !self.isMyProfile {
expandablePart = 0.0 expandablePart = 0.0
} else { } else {
expandablePart += 99.0 expandablePart += 99.0
@ -2140,7 +2154,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
if !state.isEditing { if !state.isEditing {
if !isSettings { if !isSettings && !isMyProfile {
if self.isAvatarExpanded { if self.isAvatarExpanded {
resolvedHeight -= 21.0 resolvedHeight -= 21.0
} else { } else {

View File

@ -365,6 +365,7 @@ private final class PeerInfoPendingPane {
hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void, hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void,
parentController: ViewController?, parentController: ViewController?,
openMediaCalendar: @escaping () -> Void, openMediaCalendar: @escaping () -> Void,
openAddStory: @escaping () -> Void,
paneDidScroll: @escaping () -> Void, paneDidScroll: @escaping () -> Void,
ensureRectVisible: @escaping (UIView, CGRect) -> Void, ensureRectVisible: @escaping (UIView, CGRect) -> Void,
externalDataUpdated: @escaping (ContainedViewLayoutTransition) -> Void externalDataUpdated: @escaping (ContainedViewLayoutTransition) -> Void
@ -372,8 +373,8 @@ private final class PeerInfoPendingPane {
let captureProtected = data.peer?.isCopyProtectionEnabled ?? false let captureProtected = data.peer?.isCopyProtectionEnabled ?? false
let paneNode: PeerInfoPaneNode let paneNode: PeerInfoPaneNode
switch key { switch key {
case .stories: case .stories, .storyArchive:
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: false, navigationController: chatControllerInteraction.navigationController, listContext: data.storyListContext) let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext)
paneNode = visualPaneNode paneNode = visualPaneNode
visualPaneNode.openCurrentDate = { visualPaneNode.openCurrentDate = {
openMediaCalendar() openMediaCalendar()
@ -384,6 +385,9 @@ private final class PeerInfoPendingPane {
visualPaneNode.ensureRectVisible = { sourceView, rect in visualPaneNode.ensureRectVisible = { sourceView, rect in
ensureRectVisible(sourceView, rect) ensureRectVisible(sourceView, rect)
} }
visualPaneNode.emptyAction = {
openAddStory()
}
case .media: case .media:
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected) let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected)
paneNode = visualPaneNode paneNode = visualPaneNode
@ -500,6 +504,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
var openMediaCalendar: (() -> Void)? var openMediaCalendar: (() -> Void)?
var openAddStory: (() -> Void)?
var paneDidScroll: (() -> Void)? var paneDidScroll: (() -> Void)?
var ensurePaneRectVisible: ((UIView, CGRect) -> Void)? var ensurePaneRectVisible: ((UIView, CGRect) -> Void)?
@ -848,6 +853,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
openMediaCalendar: { [weak self] in openMediaCalendar: { [weak self] in
self?.openMediaCalendar?() self?.openMediaCalendar?()
}, },
openAddStory: { [weak self] in
self?.openAddStory?()
},
paneDidScroll: { [weak self] in paneDidScroll: { [weak self] in
self?.paneDidScroll?() self?.paneDidScroll?()
}, },
@ -1010,6 +1018,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
switch key { switch key {
case .stories: case .stories:
title = presentationData.strings.PeerInfo_PaneStories title = presentationData.strings.PeerInfo_PaneStories
case .storyArchive:
//TODO:localize
title = "Archived Posts"
case .media: case .media:
title = presentationData.strings.PeerInfo_PaneMedia title = presentationData.strings.PeerInfo_PaneMedia
case .files: case .files:

View File

@ -518,6 +518,7 @@ private enum PeerInfoSettingsSection {
case emojiStatus case emojiStatus
case powerSaving case powerSaving
case businessSetup case businessSetup
case profile
} }
private enum PeerInfoReportType { private enum PeerInfoReportType {
@ -725,6 +726,7 @@ private enum SettingsSection: Int, CaseIterable {
case edit case edit
case phone case phone
case accounts case accounts
case myProfile
case proxy case proxy
case apps case apps
case shortcuts case shortcuts
@ -850,6 +852,11 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
})) }))
} }
//TODO:localize
items[.myProfile]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "My Profile", icon: PresentationResourcesSettings.myProfile, action: {
interaction.openSettings(.profile)
}))
if !settings.proxySettings.servers.isEmpty { if !settings.proxySettings.servers.isEmpty {
let proxyType: String let proxyType: String
if settings.proxySettings.enabled, let activeServer = settings.proxySettings.activeServer { if settings.proxySettings.enabled, let activeServer = settings.proxySettings.activeServer {
@ -892,10 +899,6 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
} }
} }
items[.apps]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyStories, icon: PresentationResourcesSettings.stories, action: {
interaction.openSettings(.stories)
}))
items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: { items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: {
interaction.openSettings(.savedMessages) interaction.openSettings(.savedMessages)
})) }))
@ -994,7 +997,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
return result return result
} }
private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] { private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] {
guard let data = data else { guard let data = data else {
return [] return []
} }
@ -1028,7 +1031,9 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
let ItemBirthdayHelp = 12 let ItemBirthdayHelp = 12
let ItemPeerPersonalChannel = 13 let ItemPeerPersonalChannel = 13
items[.help]!.append(PeerInfoScreenCommentItem(id: ItemNameHelp, text: presentationData.strings.EditProfile_NameAndPhotoOrVideoHelp)) if !isMyProfile {
items[.help]!.append(PeerInfoScreenCommentItem(id: ItemNameHelp, text: presentationData.strings.EditProfile_NameAndPhotoOrVideoHelp))
}
if let cachedData = data.cachedData as? CachedUserData { if let cachedData = data.cachedData as? CachedUserData {
items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in
@ -1056,27 +1061,30 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
birthDateString = presentationData.strings.Settings_Birthday_Add birthDateString = presentationData.strings.Settings_Birthday_Add
} }
let isEditingBirthDate = state.isEditingBirthDate if !isMyProfile {
items[.birthday]!.append(PeerInfoScreenDisclosureItem(id: ItemBirthday, label: .coloredText(birthDateString, isEditingBirthDate ? .accent : .generic), text: presentationData.strings.Settings_Birthday, icon: nil, hasArrow: false, action: { let isEditingBirthDate = state.isEditingBirthDate
interaction.updateIsEditingBirthdate(!isEditingBirthDate) items[.birthday]!.append(PeerInfoScreenDisclosureItem(id: ItemBirthday, label: .coloredText(birthDateString, isEditingBirthDate ? .accent : .generic), text: presentationData.strings.Settings_Birthday, icon: nil, hasArrow: false, action: {
})) interaction.updateIsEditingBirthdate(!isEditingBirthDate)
if isEditingBirthDate, let birthday {
items[.birthday]!.append(PeerInfoScreenBirthdatePickerItem(id: ItemBirthdayPicker, value: birthday, valueUpdated: { value in
interaction.updateBirthdate(value)
})) }))
items[.birthday]!.append(PeerInfoScreenActionItem(id: ItemBirthdayRemove, text: presentationData.strings.Settings_Birthday_Remove, alignment: .natural, action: { if isEditingBirthDate, let birthday {
interaction.updateBirthdate(.some(nil)) items[.birthday]!.append(PeerInfoScreenBirthdatePickerItem(id: ItemBirthdayPicker, value: birthday, valueUpdated: { value in
interaction.updateIsEditingBirthdate(false) interaction.updateBirthdate(value)
})) }))
} items[.birthday]!.append(PeerInfoScreenActionItem(id: ItemBirthdayRemove, text: presentationData.strings.Settings_Birthday_Remove, alignment: .natural, action: {
interaction.updateBirthdate(.some(nil))
interaction.updateIsEditingBirthdate(false)
}))
}
var birthdayIsForContactsOnly = false
if let birthdayPrivacy = data.globalSettings?.privacySettings?.birthday, case .enableContacts = birthdayPrivacy { var birthdayIsForContactsOnly = false
birthdayIsForContactsOnly = true if let birthdayPrivacy = data.globalSettings?.privacySettings?.birthday, case .enableContacts = birthdayPrivacy {
birthdayIsForContactsOnly = true
}
items[.birthday]!.append(PeerInfoScreenCommentItem(id: ItemBirthdayHelp, text: birthdayIsForContactsOnly ? presentationData.strings.Settings_Birthday_ContactsHelp : presentationData.strings.Settings_Birthday_Help, linkAction: { _ in
interaction.openBirthdatePrivacy()
}))
} }
items[.birthday]!.append(PeerInfoScreenCommentItem(id: ItemBirthdayHelp, text: birthdayIsForContactsOnly ? presentationData.strings.Settings_Birthday_ContactsHelp : presentationData.strings.Settings_Birthday_Help, linkAction: { _ in
interaction.openBirthdatePrivacy()
}))
if let user = data.peer as? TelegramUser { if let user = data.peer as? TelegramUser {
items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, action: { items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, action: {
@ -1091,7 +1099,7 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
interaction.openSettings(.username) interaction.openSettings(.username)
})) }))
if let peer = data.peer as? TelegramUser { if !isMyProfile, let peer = data.peer as? TelegramUser {
var colors: [PeerNameColors.Colors] = [] var colors: [PeerNameColors.Colors] = []
if let nameColor = peer.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) { if let nameColor = peer.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) {
colors.append(nameColor) colors.append(nameColor)
@ -1123,9 +1131,11 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
} }
} }
items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: { if !isMyProfile {
interaction.openSettings(.addAccount) items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: {
})) interaction.openSettings(.addAccount)
}))
}
var hasPremiumAccounts = false var hasPremiumAccounts = false
if data.peer?.isPremium == true && !context.account.testingEnvironment { if data.peer?.isPremium == true && !context.account.testingEnvironment {
@ -1142,11 +1152,13 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
} }
} }
items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: hasPremiumAccounts ? presentationData.strings.Settings_AddAnotherAccount_PremiumHelp : presentationData.strings.Settings_AddAnotherAccount_Help)) if !isMyProfile {
items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: hasPremiumAccounts ? presentationData.strings.Settings_AddAnotherAccount_PremiumHelp : presentationData.strings.Settings_AddAnotherAccount_Help))
items[.logout]!.append(PeerInfoScreenActionItem(id: ItemLogout, text: presentationData.strings.Settings_Logout, color: .destructive, alignment: .center, action: { items[.logout]!.append(PeerInfoScreenActionItem(id: ItemLogout, text: presentationData.strings.Settings_Logout, color: .destructive, alignment: .center, action: {
interaction.openSettings(.logout) interaction.openSettings(.logout)
})) }))
}
var result: [(AnyHashable, [PeerInfoScreenItem])] = [] var result: [(AnyHashable, [PeerInfoScreenItem])] = []
for section in Section.allCases { for section in Section.allCases {
@ -2371,6 +2383,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?> private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
let isSettings: Bool let isSettings: Bool
let isMyProfile: Bool
private let isMediaOnly: Bool private let isMediaOnly: Bool
let initialExpandPanes: Bool let initialExpandPanes: Bool
@ -2488,7 +2501,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
private var didSetReady = false private var didSetReady = false
init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, initialPaneKey: PeerInfoPaneKey?) { init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, initialPaneKey: PeerInfoPaneKey?) {
self.controller = controller self.controller = controller
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
@ -2499,9 +2512,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.reactionSourceMessageId = reactionSourceMessageId self.reactionSourceMessageId = reactionSourceMessageId
self.callMessages = callMessages self.callMessages = callMessages
self.isSettings = isSettings self.isSettings = isSettings
self.isMyProfile = isMyProfile
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.chatLocationContextHolder = chatLocationContextHolder self.chatLocationContextHolder = chatLocationContextHolder
self.isMediaOnly = context.account.peerId == peerId && !isSettings self.isMediaOnly = context.account.peerId == peerId && !isSettings && !isMyProfile
self.initialExpandPanes = initialPaneKey != nil self.initialExpandPanes = initialPaneKey != nil
self.scrollNode = ASScrollNode() self.scrollNode = ASScrollNode()
@ -2512,7 +2526,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if case let .replyThread(message) = chatLocation { if case let .replyThread(message) = chatLocation {
forumTopicThreadId = message.threadId forumTopicThreadId = message.threadId
} }
self.headerNode = PeerInfoHeaderNode(context: context, controller: controller, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isMediaOnly: self.isMediaOnly, isSettings: isSettings, forumTopicThreadId: forumTopicThreadId, chatLocation: self.chatLocation) self.headerNode = PeerInfoHeaderNode(context: context, controller: controller, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isMediaOnly: self.isMediaOnly, isSettings: isSettings, isMyProfile: isMyProfile, forumTopicThreadId: forumTopicThreadId, chatLocation: self.chatLocation)
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly, initialPaneKey: initialPaneKey) self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly, initialPaneKey: initialPaneKey)
super.init() super.init()
@ -3437,6 +3451,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
strongSelf.openMediaCalendar() strongSelf.openMediaCalendar()
} }
self.paneContainerNode.openAddStory = { [weak self] in
guard let self else {
return
}
self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil)
}
self.paneContainerNode.paneDidScroll = { [weak self] in self.paneContainerNode.paneDidScroll = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -4180,7 +4201,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root)) strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root))
} }
} else { } else {
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder) screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder)
var previousTimestamp: Double? var previousTimestamp: Double?
self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in
@ -9186,6 +9207,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil)
case .proxy: case .proxy:
self.controller?.push(proxySettingsController(context: self.context)) self.controller?.push(proxySettingsController(context: self.context))
case .profile:
self.controller?.push(PeerInfoScreenImpl(
context: self.context,
updatedPresentationData: self.controller?.updatedPresentationData,
peerId: self.context.account.peerId,
avatarInitiallyExpanded: false,
isOpenedFromChat: false,
nearbyPeerDistance: nil,
reactionSourceMessageId: nil,
callMessages: [],
isMyProfile: true
))
case .stories: case .stories:
push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .saved)) push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .saved))
case .savedMessages: case .savedMessages:
@ -10503,7 +10536,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
var validEditingSections: [AnyHashable] = [] var validEditingSections: [AnyHashable] = []
let editItems = self.isSettings ? settingsEditingItems(data: self.data, state: self.state, context: self.context, presentationData: self.presentationData, interaction: self.interaction) : editingItems(data: self.data, state: self.state, chatLocation: self.chatLocation, context: self.context, presentationData: self.presentationData, interaction: self.interaction) let editItems = (self.isSettings || self.isMyProfile) ? settingsEditingItems(data: self.data, state: self.state, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isMyProfile: self.isMyProfile) : editingItems(data: self.data, state: self.state, chatLocation: self.chatLocation, context: self.context, presentationData: self.presentationData, interaction: self.interaction)
for (sectionId, sectionItems) in editItems { for (sectionId, sectionItems) in editItems {
var insets = UIEdgeInsets() var insets = UIEdgeInsets()
@ -10827,7 +10860,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { } else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
} }
if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) { if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) {
rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0)
} else if self.isMyProfile {
rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0) rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0)
} }
@ -11182,6 +11217,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
private let reactionSourceMessageId: MessageId? private let reactionSourceMessageId: MessageId?
private let callMessages: [Message] private let callMessages: [Message]
private let isSettings: Bool private let isSettings: Bool
private let isMyProfile: Bool
private let hintGroupInCommon: PeerId? private let hintGroupInCommon: PeerId?
private weak var requestsContext: PeerInvitationImportersContext? private weak var requestsContext: PeerInvitationImportersContext?
private let switchToRecommendedChannels: Bool private let switchToRecommendedChannels: Bool
@ -11241,7 +11277,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false) {
self.context = context self.context = context
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.peerId = peerId self.peerId = peerId
@ -11251,6 +11287,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
self.reactionSourceMessageId = reactionSourceMessageId self.reactionSourceMessageId = reactionSourceMessageId
self.callMessages = callMessages self.callMessages = callMessages
self.isSettings = isSettings self.isSettings = isSettings
self.isMyProfile = isMyProfile
self.hintGroupInCommon = hintGroupInCommon self.hintGroupInCommon = hintGroupInCommon
self.requestsContext = requestsContext self.requestsContext = requestsContext
self.switchToRecommendedChannels = switchToRecommendedChannels self.switchToRecommendedChannels = switchToRecommendedChannels
@ -11587,7 +11624,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil) self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil)
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 }) self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get()) self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get()) self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())

View File

@ -461,6 +461,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
captureProtected: false, captureProtected: false,
isSaved: true, isSaved: true,
isArchive: component.scope == .archive, isArchive: component.scope == .archive,
isProfileEmbedded: false,
navigationController: { [weak self] in navigationController: { [weak self] in
guard let self else { guard let self else {
return nil return nil

View File

@ -189,6 +189,62 @@ private let viewCountImage: UIImage = {
return image! return image!
}() }()
private let privacyTypeImageScaleFactor: CGFloat = {
return 0.9
}()
private let privacyTypeEveryoneImage: UIImage = {
let baseImage = UIImage(bundleImageName: "Stories/PrivacyEveryone")!
let imageSize = CGSize(width: floor(baseImage.size.width * privacyTypeImageScaleFactor), height: floor(baseImage.size.width * privacyTypeImageScaleFactor))
let image = generateImage(imageSize, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
baseImage.draw(in: CGRect(origin: CGPoint(), size: size))
UIGraphicsPopContext()
})
return image!
}()
private let privacyTypeContactsImage: UIImage = {
let baseImage = UIImage(bundleImageName: "Stories/PrivacyContacts")!
let imageSize = CGSize(width: floor(baseImage.size.width * privacyTypeImageScaleFactor), height: floor(baseImage.size.width * privacyTypeImageScaleFactor))
let image = generateImage(imageSize, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
baseImage.draw(in: CGRect(origin: CGPoint(), size: size))
UIGraphicsPopContext()
})
return image!
}()
private let privacyTypeCloseFriendsImage: UIImage = {
let baseImage = UIImage(bundleImageName: "Stories/PrivacyCloseFriends")!
let imageSize = CGSize(width: floor(baseImage.size.width * privacyTypeImageScaleFactor), height: floor(baseImage.size.width * privacyTypeImageScaleFactor))
let image = generateImage(imageSize, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
baseImage.draw(in: CGRect(origin: CGPoint(), size: size))
UIGraphicsPopContext()
})
return image!
}()
private let privacyTypeSelectedImage: UIImage = {
let baseImage = UIImage(bundleImageName: "Stories/PrivacySelectedContacts")!
let imageSize = CGSize(width: floor(baseImage.size.width * privacyTypeImageScaleFactor), height: floor(baseImage.size.width * privacyTypeImageScaleFactor))
let image = generateImage(imageSize, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
baseImage.draw(in: CGRect(origin: CGPoint(), size: size))
UIGraphicsPopContext()
})
return image!
}()
private final class DurationLayer: CALayer { private final class DurationLayer: CALayer {
override init() { override init() {
super.init() super.init()
@ -264,6 +320,41 @@ private final class DurationLayer: CALayer {
self.contents = image?.cgImage self.contents = image?.cgImage
} }
} }
func update(privacyType: Stories.Item.Privacy.Base, isMin: Bool) {
if isMin {
self.contents = nil
} else {
let iconImage: UIImage
switch privacyType {
case .everyone:
iconImage = privacyTypeEveryoneImage
case .contacts:
iconImage = privacyTypeContactsImage
case .closeFriends:
iconImage = privacyTypeCloseFriendsImage
case .nobody:
iconImage = privacyTypeSelectedImage
}
let sideInset: CGFloat = 0.0
let verticalInset: CGFloat = 0.0
let image = generateImage(CGSize(width: iconImage.size.width + sideInset * 2.0, height: iconImage.size.height + verticalInset * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.normal)
context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 2.5, color: UIColor(rgb: 0x000000, alpha: 0.22).cgColor)
UIGraphicsPushContext(context)
iconImage.draw(in: CGRect(origin: CGPoint(x: (size.width - iconImage.size.width) * 0.5, y: (size.height - iconImage.size.height) * 0.5), size: iconImage.size))
UIGraphicsPopContext()
})
self.contents = image?.cgImage
}
}
} }
private protocol ItemLayer: SparseItemGridLayer { private protocol ItemLayer: SparseItemGridLayer {
@ -276,7 +367,7 @@ private protocol ItemLayer: SparseItemGridLayer {
var hasContents: Bool { get set } var hasContents: Bool { get set }
func setSpoilerContents(_ contents: Any?) func setSpoilerContents(_ contents: Any?)
func updateDuration(viewCount: Int32?, duration: Int32?, isMin: Bool, minFactor: CGFloat) func updateDuration(viewCount: Int32?, duration: Int32?, privacy: Stories.Item.Privacy.Base?, isMin: Bool, minFactor: CGFloat)
func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool)
func updateHasSpoiler(hasSpoiler: Bool) func updateHasSpoiler(hasSpoiler: Bool)
@ -288,8 +379,10 @@ private final class GenericItemLayer: CALayer, ItemLayer {
var item: VisualMediaItem? var item: VisualMediaItem?
var viewCountLayer: DurationLayer? var viewCountLayer: DurationLayer?
var durationLayer: DurationLayer? var durationLayer: DurationLayer?
var privacyTypeLayer: DurationLayer?
var leftShadowLayer: SimpleLayer? var leftShadowLayer: SimpleLayer?
var rightShadowLayer: SimpleLayer? var rightShadowLayer: SimpleLayer?
var topRightShadowLayer: SimpleLayer?
var minFactor: CGFloat = 1.0 var minFactor: CGFloat = 1.0
var selectionLayer: GridMessageSelectionLayer? var selectionLayer: GridMessageSelectionLayer?
var dustLayer: MediaDustLayer? var dustLayer: MediaDustLayer?
@ -335,7 +428,7 @@ private final class GenericItemLayer: CALayer, ItemLayer {
self.item = item self.item = item
} }
func updateDuration(viewCount: Int32?, duration: Int32?, isMin: Bool, minFactor: CGFloat) { func updateDuration(viewCount: Int32?, duration: Int32?, privacy: Stories.Item.Privacy.Base?, isMin: Bool, minFactor: CGFloat) {
self.minFactor = minFactor self.minFactor = minFactor
if let viewCount { if let viewCount {
@ -371,6 +464,23 @@ private final class GenericItemLayer: CALayer, ItemLayer {
durationLayer.removeFromSuperlayer() durationLayer.removeFromSuperlayer()
} }
if let privacy {
if let privacyTypeLayer = self.privacyTypeLayer {
privacyTypeLayer.update(privacyType: privacy, isMin: isMin)
} else {
let privacyTypeLayer = DurationLayer()
privacyTypeLayer.contentsGravity = .bottomRight
privacyTypeLayer.update(privacyType: privacy, isMin: isMin)
self.addSublayer(privacyTypeLayer)
privacyTypeLayer.frame = CGRect(origin: CGPoint(x: self.bounds.width - 2.0, y: 3.0), size: CGSize())
privacyTypeLayer.transform = CATransform3DMakeScale(minFactor, minFactor, 1.0)
self.privacyTypeLayer = privacyTypeLayer
}
} else if let privacyTypeLayer = self.privacyTypeLayer {
self.privacyTypeLayer = nil
privacyTypeLayer.removeFromSuperlayer()
}
let size = self.bounds.size let size = self.bounds.size
if self.viewCountLayer != nil { if self.viewCountLayer != nil {
@ -404,6 +514,22 @@ private final class GenericItemLayer: CALayer, ItemLayer {
rightShadowLayer.removeFromSuperlayer() rightShadowLayer.removeFromSuperlayer()
} }
} }
if self.privacyTypeLayer != nil {
if self.topRightShadowLayer == nil {
let topRightShadowLayer = SimpleLayer()
self.topRightShadowLayer = topRightShadowLayer
self.insertSublayer(topRightShadowLayer, at: 0)
topRightShadowLayer.contents = rightShadowImage.cgImage
let shadowSize = CGSize(width: min(size.width, rightShadowImage.size.width), height: min(size.height, rightShadowImage.size.height))
topRightShadowLayer.frame = CGRect(origin: CGPoint(x: size.width - shadowSize.width, y: size.height - shadowSize.height), size: shadowSize)
}
} else {
if let topRightShadowLayer = self.topRightShadowLayer {
self.topRightShadowLayer = nil
topRightShadowLayer.removeFromSuperlayer()
}
}
} }
func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) { func updateSelection(theme: CheckNodeTheme, isSelected: Bool?, animated: Bool) {
@ -468,6 +594,9 @@ private final class GenericItemLayer: CALayer, ItemLayer {
if let durationLayer = self.durationLayer { if let durationLayer = self.durationLayer {
durationLayer.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 4.0), size: CGSize()) durationLayer.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 4.0), size: CGSize())
} }
if let privacyTypeLayer = self.privacyTypeLayer {
privacyTypeLayer.frame = CGRect(origin: CGPoint(x: size.width - 2.0, y: 3.0), size: CGSize())
}
if let leftShadowLayer = self.leftShadowLayer { if let leftShadowLayer = self.leftShadowLayer {
let shadowSize = CGSize(width: min(size.width, leftShadowImage.size.width), height: min(size.height, leftShadowImage.size.height)) let shadowSize = CGSize(width: min(size.width, leftShadowImage.size.width), height: min(size.height, leftShadowImage.size.height))
@ -479,6 +608,11 @@ private final class GenericItemLayer: CALayer, ItemLayer {
rightShadowLayer.frame = CGRect(origin: CGPoint(x: size.width - shadowSize.width, y: size.height - shadowSize.height), size: shadowSize) rightShadowLayer.frame = CGRect(origin: CGPoint(x: size.width - shadowSize.width, y: size.height - shadowSize.height), size: shadowSize)
} }
if let topRightShadowLayer = self.topRightShadowLayer {
let shadowSize = CGSize(width: min(size.width, rightShadowImage.size.width), height: min(size.height, rightShadowImage.size.height))
topRightShadowLayer.frame = CGRect(origin: CGPoint(x: size.width - shadowSize.width, y: 0.0), size: shadowSize)
}
if let binding = binding as? SparseItemGridBindingImpl, let item = item as? VisualMediaItem, let previousItem = self.item, previousItem.story.media.id != item.story.media.id { if let binding = binding as? SparseItemGridBindingImpl, let item = item as? VisualMediaItem, let previousItem = self.item, previousItem.story.media.id != item.story.media.id {
binding.bindLayers(items: [item], layers: [displayItem], size: size, insets: insets, synchronous: .none) binding.bindLayers(items: [item], layers: [displayItem], size: size, insets: insets, synchronous: .none)
} }
@ -489,11 +623,14 @@ private final class ItemTransitionView: UIView {
private weak var itemLayer: CALayer? private weak var itemLayer: CALayer?
private var copyDurationLayer: SimpleLayer? private var copyDurationLayer: SimpleLayer?
private var copyViewCountLayer: SimpleLayer? private var copyViewCountLayer: SimpleLayer?
private var copyPrivacyTypeLayer: SimpleLayer?
private var copyLeftShadowLayer: SimpleLayer? private var copyLeftShadowLayer: SimpleLayer?
private var copyRightShadowLayer: SimpleLayer? private var copyRightShadowLayer: SimpleLayer?
private var copyTopRightShadowLayer: SimpleLayer?
private var viewCountLayerBottomLeftPosition: CGPoint? private var viewCountLayerBottomLeftPosition: CGPoint?
private var durationLayerBottomLeftPosition: CGPoint? private var durationLayerBottomLeftPosition: CGPoint?
private var privacyTypeLayerTopRightPosition: CGPoint?
init(itemLayer: CALayer?) { init(itemLayer: CALayer?) {
self.itemLayer = itemLayer self.itemLayer = itemLayer
@ -505,13 +642,17 @@ private final class ItemTransitionView: UIView {
var viewCountLayer: CALayer? var viewCountLayer: CALayer?
var durationLayer: CALayer? var durationLayer: CALayer?
var privacyTypeLayer: CALayer?
var leftShadowLayer: CALayer? var leftShadowLayer: CALayer?
var rightShadowLayer: CALayer? var rightShadowLayer: CALayer?
var topRightShadowLayer: CALayer?
if let itemLayer = itemLayer as? GenericItemLayer { if let itemLayer = itemLayer as? GenericItemLayer {
viewCountLayer = itemLayer.viewCountLayer viewCountLayer = itemLayer.viewCountLayer
durationLayer = itemLayer.durationLayer durationLayer = itemLayer.durationLayer
privacyTypeLayer = itemLayer.privacyTypeLayer
leftShadowLayer = itemLayer.leftShadowLayer leftShadowLayer = itemLayer.leftShadowLayer
rightShadowLayer = itemLayer.rightShadowLayer rightShadowLayer = itemLayer.rightShadowLayer
topRightShadowLayer = itemLayer.topRightShadowLayer
self.layer.contents = itemLayer.contents self.layer.contents = itemLayer.contents
} }
@ -537,6 +678,17 @@ private final class ItemTransitionView: UIView {
self.copyRightShadowLayer = copyLayer self.copyRightShadowLayer = copyLayer
} }
if let topRightShadowLayer {
let copyLayer = SimpleLayer()
copyLayer.contents = topRightShadowLayer.contents
copyLayer.contentsRect = topRightShadowLayer.contentsRect
copyLayer.contentsGravity = topRightShadowLayer.contentsGravity
copyLayer.contentsScale = topRightShadowLayer.contentsScale
copyLayer.frame = topRightShadowLayer.frame
self.layer.addSublayer(copyLayer)
self.copyTopRightShadowLayer = copyLayer
}
if let viewCountLayer { if let viewCountLayer {
let copyViewCountLayer = SimpleLayer() let copyViewCountLayer = SimpleLayer()
copyViewCountLayer.contents = viewCountLayer.contents copyViewCountLayer.contents = viewCountLayer.contents
@ -550,6 +702,19 @@ private final class ItemTransitionView: UIView {
self.viewCountLayerBottomLeftPosition = CGPoint(x: viewCountLayer.frame.minX, y: itemLayer.bounds.height - viewCountLayer.frame.maxY) self.viewCountLayerBottomLeftPosition = CGPoint(x: viewCountLayer.frame.minX, y: itemLayer.bounds.height - viewCountLayer.frame.maxY)
} }
if let privacyTypeLayer {
let copyPrivacyTypeLayer = SimpleLayer()
copyPrivacyTypeLayer.contents = privacyTypeLayer.contents
copyPrivacyTypeLayer.contentsRect = privacyTypeLayer.contentsRect
copyPrivacyTypeLayer.contentsGravity = privacyTypeLayer.contentsGravity
copyPrivacyTypeLayer.contentsScale = privacyTypeLayer.contentsScale
copyPrivacyTypeLayer.frame = privacyTypeLayer.frame
self.layer.addSublayer(copyPrivacyTypeLayer)
self.copyPrivacyTypeLayer = copyPrivacyTypeLayer
self.privacyTypeLayerTopRightPosition = CGPoint(x: itemLayer.bounds.width - privacyTypeLayer.frame.maxX, y: privacyTypeLayer.frame.minY)
}
if let durationLayer { if let durationLayer {
let copyDurationLayer = SimpleLayer() let copyDurationLayer = SimpleLayer()
copyDurationLayer.contents = durationLayer.contents copyDurationLayer.contents = durationLayer.contents
@ -576,8 +741,12 @@ private final class ItemTransitionView: UIView {
transition.setFrame(layer: copyDurationLayer, frame: CGRect(origin: CGPoint(x: size.width - durationLayerBottomLeftPosition.x - copyDurationLayer.bounds.width, y: size.height - durationLayerBottomLeftPosition.y - copyDurationLayer.bounds.height), size: copyDurationLayer.bounds.size)) transition.setFrame(layer: copyDurationLayer, frame: CGRect(origin: CGPoint(x: size.width - durationLayerBottomLeftPosition.x - copyDurationLayer.bounds.width, y: size.height - durationLayerBottomLeftPosition.y - copyDurationLayer.bounds.height), size: copyDurationLayer.bounds.size))
} }
if let copyViewCountLayer = self.copyViewCountLayer, let viewcountLayerBottomLeftPosition = self.viewCountLayerBottomLeftPosition { if let copyViewCountLayer = self.copyViewCountLayer, let viewCountLayerBottomLeftPosition = self.viewCountLayerBottomLeftPosition {
transition.setFrame(layer: copyViewCountLayer, frame: CGRect(origin: CGPoint(x: viewcountLayerBottomLeftPosition.x, y: size.height - viewcountLayerBottomLeftPosition.y - copyViewCountLayer.bounds.height), size: copyViewCountLayer.bounds.size)) transition.setFrame(layer: copyViewCountLayer, frame: CGRect(origin: CGPoint(x: viewCountLayerBottomLeftPosition.x, y: size.height - viewCountLayerBottomLeftPosition.y - copyViewCountLayer.bounds.height), size: copyViewCountLayer.bounds.size))
}
if let privacyTypeLayer = self.copyPrivacyTypeLayer, let privacyTypeLayerTopRightPosition = self.privacyTypeLayerTopRightPosition {
transition.setFrame(layer: privacyTypeLayer, frame: CGRect(origin: CGPoint(x: size.width - privacyTypeLayerTopRightPosition.x, y: privacyTypeLayerTopRightPosition.y), size: privacyTypeLayer.bounds.size))
} }
if let copyLeftShadowLayer = self.copyLeftShadowLayer { if let copyLeftShadowLayer = self.copyLeftShadowLayer {
@ -587,6 +756,10 @@ private final class ItemTransitionView: UIView {
if let copyRightShadowLayer = self.copyRightShadowLayer { if let copyRightShadowLayer = self.copyRightShadowLayer {
transition.setFrame(layer: copyRightShadowLayer, frame: CGRect(origin: CGPoint(x: size.width - copyRightShadowLayer.bounds.width, y: size.height - copyRightShadowLayer.bounds.height), size: copyRightShadowLayer.bounds.size)) transition.setFrame(layer: copyRightShadowLayer, frame: CGRect(origin: CGPoint(x: size.width - copyRightShadowLayer.bounds.width, y: size.height - copyRightShadowLayer.bounds.height), size: copyRightShadowLayer.bounds.size))
} }
if let copyTopRightShadowLayer = self.copyTopRightShadowLayer {
transition.setFrame(layer: copyTopRightShadowLayer, frame: CGRect(origin: CGPoint(x: size.width - copyTopRightShadowLayer.bounds.width, y: 0.0), size: copyTopRightShadowLayer.bounds.size))
}
} }
} }
@ -595,6 +768,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
let chatLocation: ChatLocation let chatLocation: ChatLocation
let directMediaImageCache: DirectMediaImageCache let directMediaImageCache: DirectMediaImageCache
let captureProtected: Bool let captureProtected: Bool
let displayPrivacy: Bool
var strings: PresentationStrings var strings: PresentationStrings
var chatPresentationData: ChatPresentationData var chatPresentationData: ChatPresentationData
var checkNodeTheme: CheckNodeTheme var checkNodeTheme: CheckNodeTheme
@ -613,11 +787,12 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
private var shimmerImages: [CGFloat: UIImage] = [:] private var shimmerImages: [CGFloat: UIImage] = [:]
init(context: AccountContext, chatLocation: ChatLocation, directMediaImageCache: DirectMediaImageCache, captureProtected: Bool) { init(context: AccountContext, chatLocation: ChatLocation, directMediaImageCache: DirectMediaImageCache, captureProtected: Bool, displayPrivacy: Bool) {
self.context = context self.context = context
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.directMediaImageCache = directMediaImageCache self.directMediaImageCache = directMediaImageCache
self.captureProtected = false self.captureProtected = false
self.displayPrivacy = displayPrivacy
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.strings = presentationData.strings self.strings = presentationData.strings
@ -794,6 +969,11 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
viewCount = Int32(value) viewCount = Int32(value)
} }
var privacyType: EngineStoryPrivacy.Base?
if self.displayPrivacy, let value = story.privacy {
privacyType = value.base
}
var duration: Int32? var duration: Int32?
var isMin: Bool = false var isMin: Bool = false
if let file = selectedMedia as? TelegramMediaFile, !file.isAnimated { if let file = selectedMedia as? TelegramMediaFile, !file.isAnimated {
@ -802,7 +982,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
} }
isMin = layer.bounds.width < 80.0 isMin = layer.bounds.width < 80.0
} }
layer.updateDuration(viewCount: viewCount, duration: duration, isMin: isMin, minFactor: min(1.0, layer.bounds.height / 74.0)) layer.updateDuration(viewCount: viewCount, duration: duration, privacy: privacyType, isMin: isMin, minFactor: min(1.0, layer.bounds.height / 74.0))
} }
var isSelected: Bool? var isSelected: Bool?
@ -895,6 +1075,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private let chatLocation: ChatLocation private let chatLocation: ChatLocation
private let isSaved: Bool private let isSaved: Bool
private let isArchive: Bool private let isArchive: Bool
private let isProfileEmbedded: Bool
public private(set) var contentType: ContentType public private(set) var contentType: ContentType
private var contentTypePromise: ValuePromise<ContentType> private var contentTypePromise: ValuePromise<ContentType>
@ -1002,7 +1183,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private var emptyStateView: ComponentView<Empty>? private var emptyStateView: ComponentView<Empty>?
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) { public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, isProfileEmbedded: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.chatLocation = chatLocation self.chatLocation = chatLocation
@ -1011,6 +1192,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
self.navigationController = navigationController self.navigationController = navigationController
self.isSaved = isSaved self.isSaved = isSaved
self.isArchive = isArchive self.isArchive = isArchive
self.isProfileEmbedded = isProfileEmbedded
self.isSelectionModeActive = isArchive self.isSelectionModeActive = isArchive
@ -1024,7 +1206,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
context: context, context: context,
chatLocation: .peer(id: peerId), chatLocation: .peer(id: peerId),
directMediaImageCache: self.directMediaImageCache, directMediaImageCache: self.directMediaImageCache,
captureProtected: captureProtected captureProtected: captureProtected,
displayPrivacy: isProfileEmbedded && !self.isArchive
) )
self.listSource = listContext ?? PeerStoryListContext(account: context.account, peerId: peerId, isArchived: self.isArchive) self.listSource = listContext ?? PeerStoryListContext(account: context.account, peerId: peerId, isArchived: self.isArchive)
@ -1421,7 +1604,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
strongSelf.itemGrid.cancelGestures() strongSelf.itemGrid.cancelGestures()
} }
self.statusPromise.set(.single(PeerInfoStatusData(text: "", isActivity: false, key: .stories))) self.statusPromise.set(.single(PeerInfoStatusData(text: "", isActivity: false, key: self.isArchive ? .storyArchive : .stories)))
/*self.storedStateDisposable = (visualMediaStoredState(engine: context.engine, peerId: peerId, messageTag: self.stateTag) /*self.storedStateDisposable = (visualMediaStoredState(engine: context.engine, peerId: peerId, messageTag: self.stateTag)
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
@ -1671,11 +1854,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} else { } else {
if self.isSaved { if self.isSaved {
title = self.presentationData.strings.StoryList_SubtitleSaved(Int32(state.totalCount)) title = self.presentationData.strings.StoryList_SubtitleSaved(Int32(state.totalCount))
} else if self.isArchive {
title = self.presentationData.strings.StoryList_SubtitleArchived(Int32(state.totalCount))
} else { } else {
title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount)) title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount))
} }
} }
self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: .stories))) self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: self.isArchive ? .storyArchive : .stories)))
let timezoneOffset = Int32(TimeZone.current.secondsFromGMT()) let timezoneOffset = Int32(TimeZone.current.secondsFromGMT())
@ -1952,12 +2137,19 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
itemLayer.updateSelection(theme: self.itemGridBinding.checkNodeTheme, isSelected: self.itemInteraction.selectedIds?.contains(item.story.id), animated: animated) itemLayer.updateSelection(theme: self.itemGridBinding.checkNodeTheme, isSelected: self.itemInteraction.selectedIds?.contains(item.story.id), animated: animated)
} }
let isSelecting = self._itemInteraction?.selectedIds != nil var isSelecting = false
if let selectedIds = self._itemInteraction?.selectedIds, !selectedIds.isEmpty {
isSelecting = true
}
self.itemGrid.pinchEnabled = !isSelecting self.itemGrid.pinchEnabled = !isSelecting
var enableDismissGesture = true var enableDismissGesture = true
if let items = self.items, items.items.isEmpty { if self.isProfileEmbedded {
} else if isSelecting { enableDismissGesture = true
} else if let items = self.items, items.items.isEmpty {
}
if isSelecting {
enableDismissGesture = false enableDismissGesture = false
} }
self.view.disablesInteractiveTransitionGestureRecognizer = !enableDismissGesture self.view.disablesInteractiveTransitionGestureRecognizer = !enableDismissGesture
@ -2030,6 +2222,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
component: AnyComponent(EmptyStateIndicatorComponent( component: AnyComponent(EmptyStateIndicatorComponent(
context: self.context, context: self.context,
theme: presentationData.theme, theme: presentationData.theme,
fitToHeight: self.isProfileEmbedded,
animationName: "StoryListEmpty", animationName: "StoryListEmpty",
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_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_SavedEmptyPosts_Text, text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
@ -2040,7 +2233,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
self.emptyAction?() self.emptyAction?()
}, },
additionalActionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction, additionalActionTitle: (self.isArchive || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
additionalAction: { [weak self] in additionalAction: { [weak self] in
guard let self else { guard let self else {
return return
@ -2051,6 +2244,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
environment: {}, environment: {},
containerSize: CGSize(width: size.width, height: size.height - topInset - bottomInset) containerSize: CGSize(width: size.width, height: size.height - topInset - bottomInset)
) )
let emptyStateFrame: CGRect
if self.isProfileEmbedded {
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(topInset, floor((visibleHeight - topInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
} else {
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: topInset), size: emptyStateSize)
}
if let emptyStateComponentView = emptyStateView.view { if let emptyStateComponentView = emptyStateView.view {
if emptyStateComponentView.superview == nil { if emptyStateComponentView.superview == nil {
self.view.addSubview(emptyStateComponentView) self.view.addSubview(emptyStateComponentView)
@ -2058,12 +2259,20 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
} }
emptyStateTransition.setFrame(view: emptyStateComponentView, frame: CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: topInset), size: emptyStateSize)) emptyStateTransition.setFrame(view: emptyStateComponentView, frame: emptyStateFrame)
} }
if self.didUpdateItemsOnce {
Transition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) let backgroundColor: UIColor
if self.isProfileEmbedded {
backgroundColor = presentationData.theme.list.plainBackgroundColor
} else { } else {
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor backgroundColor = presentationData.theme.list.blocksBackgroundColor
}
if self.didUpdateItemsOnce {
Transition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor)
} else {
self.view.backgroundColor = backgroundColor
} }
} else { } else {
if let emptyStateView = self.emptyStateView { if let emptyStateView = self.emptyStateView {
@ -2076,7 +2285,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}) })
} }
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) if self.isProfileEmbedded {
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor)
} else {
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
}
} else { } else {
self.view.backgroundColor = .clear self.view.backgroundColor = .clear
} }

View File

@ -1463,7 +1463,9 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
values: valueList.map { item in values: valueList.map { item in
return environment.strings.MessageTimer_Days(Int32(item)) return environment.strings.MessageTimer_Days(Int32(item))
}, },
markPositions: true,
selectedIndex: selectedInactivityIndex, selectedIndex: selectedInactivityIndex,
title: nil,
selectedIndexUpdated: { [weak self] index in selectedIndexUpdated: { [weak self] index in
guard let self else { guard let self else {
return return

View File

@ -9,6 +9,7 @@ import ComponentFlow
public final class SliderComponent: Component { public final class SliderComponent: Component {
public let valueCount: Int public let valueCount: Int
public let value: Int public let value: Int
public let markPositions: Bool
public let trackBackgroundColor: UIColor public let trackBackgroundColor: UIColor
public let trackForegroundColor: UIColor public let trackForegroundColor: UIColor
public let valueUpdated: (Int) -> Void public let valueUpdated: (Int) -> Void
@ -17,6 +18,7 @@ public final class SliderComponent: Component {
public init( public init(
valueCount: Int, valueCount: Int,
value: Int, value: Int,
markPositions: Bool,
trackBackgroundColor: UIColor, trackBackgroundColor: UIColor,
trackForegroundColor: UIColor, trackForegroundColor: UIColor,
valueUpdated: @escaping (Int) -> Void, valueUpdated: @escaping (Int) -> Void,
@ -24,6 +26,7 @@ public final class SliderComponent: Component {
) { ) {
self.valueCount = valueCount self.valueCount = valueCount
self.value = value self.value = value
self.markPositions = markPositions
self.trackBackgroundColor = trackBackgroundColor self.trackBackgroundColor = trackBackgroundColor
self.trackForegroundColor = trackForegroundColor self.trackForegroundColor = trackForegroundColor
self.valueUpdated = valueUpdated self.valueUpdated = valueUpdated
@ -37,6 +40,9 @@ public final class SliderComponent: Component {
if lhs.value != rhs.value { if lhs.value != rhs.value {
return false return false
} }
if lhs.markPositions != rhs.markPositions {
return false
}
if lhs.trackBackgroundColor != rhs.trackBackgroundColor { if lhs.trackBackgroundColor != rhs.trackBackgroundColor {
return false return false
} }
@ -97,6 +103,7 @@ public final class SliderComponent: Component {
sliderView.maximumValue = CGFloat(component.valueCount - 1) sliderView.maximumValue = CGFloat(component.valueCount - 1)
sliderView.positionsCount = component.valueCount sliderView.positionsCount = component.valueCount
sliderView.useLinesForPositions = true sliderView.useLinesForPositions = true
sliderView.markPositions = component.markPositions
sliderView.backgroundColor = nil sliderView.backgroundColor = nil
sliderView.isOpaque = false sliderView.isOpaque = false

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "profile.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,231 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
1.000000 0.176471 0.333333 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 5.000000 5.000000 cm
1.000000 1.000000 1.000000 scn
17.165209 3.024336 m
18.919451 4.825943 20.000000 7.286784 20.000000 10.000000 c
20.000000 15.522848 15.522847 20.000000 10.000000 20.000000 c
4.477152 20.000000 0.000000 15.522848 0.000000 10.000000 c
0.000000 7.286821 1.080519 4.826013 2.834717 3.024412 c
2.877964 3.183798 2.941688 3.345104 3.027704 3.506973 c
3.983309 5.305289 5.950467 7.156250 9.999956 7.156250 c
14.049442 7.156250 16.016598 5.305290 16.972202 3.506973 c
17.058231 3.345078 17.121962 3.183746 17.165209 3.024336 c
h
11.770506 0.156250 m
8.229494 0.156250 l
8.804153 0.053581 9.395820 0.000000 10.000000 0.000000 c
10.604180 0.000000 11.195847 0.053581 11.770506 0.156250 c
h
13.499953 12.843750 m
13.499953 10.910753 11.932950 9.343750 9.999953 9.343750 c
8.066957 9.343750 6.499953 10.910753 6.499953 12.843750 c
6.499953 14.776747 8.066957 16.343750 9.999953 16.343750 c
11.932950 16.343750 13.499953 14.776747 13.499953 12.843750 c
h
f*
n
Q
q
25.000000 15.000000 m
25.000000 9.477154 20.522846 5.000000 15.000000 5.000000 c
9.477152 5.000000 5.000000 9.477154 5.000000 15.000000 c
5.000000 20.522848 9.477152 25.000000 15.000000 25.000000 c
20.522846 25.000000 25.000000 20.522848 25.000000 15.000000 c
h
W*
n
q
1.000000 0.000000 -0.000000 1.000000 5.000000 5.000000 cm
1.000000 1.000000 1.000000 scn
18.670000 10.000000 m
18.670000 5.211691 14.788309 1.330000 10.000000 1.330000 c
10.000000 -1.330000 l
16.257387 -1.330000 21.330000 3.742613 21.330000 10.000000 c
18.670000 10.000000 l
h
10.000000 1.330000 m
5.211691 1.330000 1.330000 5.211691 1.330000 10.000000 c
-1.330000 10.000000 l
-1.330000 3.742613 3.742614 -1.330000 10.000000 -1.330000 c
10.000000 1.330000 l
h
1.330000 10.000000 m
1.330000 14.788309 5.211691 18.670000 10.000000 18.670000 c
10.000000 21.330000 l
3.742614 21.330000 -1.330000 16.257385 -1.330000 10.000000 c
1.330000 10.000000 l
h
10.000000 18.670000 m
14.788309 18.670000 18.670000 14.788309 18.670000 10.000000 c
21.330000 10.000000 l
21.330000 16.257385 16.257387 21.330000 10.000000 21.330000 c
10.000000 18.670000 l
h
f
n
Q
Q
endstream
endobj
2 0 obj
3071
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
endstream
endobj
4 0 obj
944
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000003329 00000 n
0000003352 00000 n
0000004544 00000 n
0000004566 00000 n
0000004864 00000 n
0000004966 00000 n
0000004987 00000 n
0000005160 00000 n
0000005234 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
5294
%%EOF