Merge branch 'master' into experimental-2

This commit is contained in:
Ali 2020-07-09 15:25:27 +04:00
commit 43c2d875a6
22 changed files with 3519 additions and 3395 deletions

View File

@ -5679,6 +5679,8 @@ Any member of this group will be able to see messages in the channel.";
"Settings.EditAccount" = "Edit Account"; "Settings.EditAccount" = "Edit Account";
"Settings.EditPhoto" = "Edit Photo"; "Settings.EditPhoto" = "Edit Photo";
"Settings.EditVideo" = "Edit Video";
"Settings.CancelUpload" = "Cancel Upload";
"Settings.FrequentlyAskedQuestions" = "Frequently Asked Questions"; "Settings.FrequentlyAskedQuestions" = "Frequently Asked Questions";
@ -5690,3 +5692,5 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.Dice.u26BD" = "Send a football emoji to do a free kick."; "Conversation.Dice.u26BD" = "Send a football emoji to do a free kick.";
"SettingsSearch_Synonyms_ChatFolders" = ""; "SettingsSearch_Synonyms_ChatFolders" = "";
"EditProfile.NameAndPhotoOrVideoHelp" = "Enter your name and add an optional profile photo or video.";

View File

@ -532,7 +532,7 @@ public protocol SharedAccountContext: class {
func makeProxySettingsController(context: AccountContext) -> ViewController func makeProxySettingsController(context: AccountContext) -> ViewController
func makeLocalizationListController(context: AccountContext) -> ViewController func makeLocalizationListController(context: AccountContext) -> ViewController
func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController
func makeChatRecentActionsController(context: AccountContext, peer: Peer) -> ViewController func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController
func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController
func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToChatController(_ params: NavigateToChatControllerParams)
func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void)

View File

@ -911,7 +911,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var chatListText: (String, String)? var chatListText: (String, String)?
var chatListSearchResult: CachedChatListSearchResult? var chatListSearchResult: CachedChatListSearchResult?
let contentImageSize = CGSize(width: 18.0, height: 18.0) let contentImageSide: CGFloat = max(10.0, min(20.0, floor(item.presentationData.fontSize.baseDisplaySize * 18.0 / 17.0)))
let contentImageSize = CGSize(width: contentImageSide, height: contentImageSide)
let contentImageSpacing: CGFloat = 2.0 let contentImageSpacing: CGFloat = 2.0
let contentImageTrailingSpace: CGFloat = 5.0 let contentImageTrailingSpace: CGFloat = 5.0
var contentImageSpecs: [(message: Message, media: Media, size: CGSize)] = [] var contentImageSpecs: [(message: Message, media: Media, size: CGSize)] = []
@ -1338,7 +1339,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
animateContent = true animateContent = true
} }
let measureString = NSAttributedString(string: "A", font: titleFont, textColor: .black)
let (measureLayout, measureApply) = makeMeasureLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (measureLayout, measureApply) = makeMeasureLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let titleSpacing: CGFloat = -1.0 let titleSpacing: CGFloat = -1.0
@ -1633,7 +1633,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
inputActivitiesApply?() inputActivitiesApply?()
var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: 2.0) var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: floor((measureLayout.size.height - contentImageSize.height) / 2.0))
var validMediaIds: [MediaId] = [] var validMediaIds: [MediaId] = []
for (message, media, mediaSize) in contentImageSpecs { for (message, media, mediaSize) in contentImageSpecs {
guard let mediaId = media.id else { guard let mediaId = media.id else {
@ -1824,16 +1824,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
textFrame.origin.x = contentRect.origin.x textFrame.origin.x = contentRect.origin.x
transition.updateFrameAdditive(node: self.textNode, frame: textFrame) transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
var mediaPreviewOffset = textFrame.origin.offsetBy(dx: 1.0, dy: 2.0) var mediaPreviewOffsetX = textFrame.origin.x + 1.0
let contentImageSpacing: CGFloat = 2.0 let contentImageSpacing: CGFloat = 2.0
for (_, media, mediaSize) in self.currentMediaPreviewSpecs { for (_, media, mediaSize) in self.currentMediaPreviewSpecs {
guard let mediaId = media.id else { guard let mediaId = media.id else {
continue continue
} }
if let previewNode = self.mediaPreviewNodes[mediaId] { if let previewNode = self.mediaPreviewNodes[mediaId] {
transition.updateFrame(node: previewNode, frame: CGRect(origin: mediaPreviewOffset, size: mediaSize)) transition.updateFrame(node: previewNode, frame: CGRect(origin: CGPoint(x: mediaPreviewOffsetX, y: previewNode.frame.minY), size: mediaSize))
} }
mediaPreviewOffset.x += mediaSize.width + contentImageSpacing mediaPreviewOffsetX += mediaSize.width + contentImageSpacing
} }
let dateFrame = self.dateNode.frame let dateFrame = self.dateNode.frame

View File

@ -3,6 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
public enum ContainedViewLayoutTransitionCurve { public enum ContainedViewLayoutTransitionCurve {
case linear
case easeInOut case easeInOut
case spring case spring
case custom(Float, Float, Float, Float) case custom(Float, Float, Float, Float)
@ -15,6 +16,8 @@ public enum ContainedViewLayoutTransitionCurve {
public extension ContainedViewLayoutTransitionCurve { public extension ContainedViewLayoutTransitionCurve {
var timingFunction: String { var timingFunction: String {
switch self { switch self {
case .linear:
return CAMediaTimingFunctionName.linear.rawValue
case .easeInOut: case .easeInOut:
return CAMediaTimingFunctionName.easeInEaseOut.rawValue return CAMediaTimingFunctionName.easeInEaseOut.rawValue
case .spring: case .spring:
@ -26,6 +29,8 @@ public extension ContainedViewLayoutTransitionCurve {
var mediaTimingFunction: CAMediaTimingFunction? { var mediaTimingFunction: CAMediaTimingFunction? {
switch self { switch self {
case .linear:
return nil
case .easeInOut: case .easeInOut:
return nil return nil
case .spring: case .spring:
@ -38,6 +43,8 @@ public extension ContainedViewLayoutTransitionCurve {
#if os(iOS) #if os(iOS)
var viewAnimationOptions: UIView.AnimationOptions { var viewAnimationOptions: UIView.AnimationOptions {
switch self { switch self {
case .linear:
return [.curveLinear]
case .easeInOut: case .easeInOut:
return [.curveEaseInOut] return [.curveEaseInOut]
case .spring: case .spring:

View File

@ -3231,6 +3231,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
offset = -offset offset = -offset
} }
switch curve { switch curve {
case .linear:
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear))
case .spring: case .spring:
transition.0.animateOffsetAdditive(node: headerNode, offset: offset) transition.0.animateOffsetAdditive(node: headerNode, offset: offset)
case let .custom(p1, p2, p3, p4): case let .custom(p1, p2, p3, p4):

View File

@ -187,7 +187,7 @@ public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTra
case let .animated(animationDuration, animationCurve): case let .animated(animationDuration, animationCurve):
duration = animationDuration duration = animationDuration
switch animationCurve { switch animationCurve {
case .easeInOut, .custom: case .linear, .easeInOut, .custom:
break break
case .spring: case .spring:
curve = 7 curve = 7

View File

@ -948,7 +948,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.insertSubnode(strongSelf.maskNode, at: 3) strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
} }
let hasCorners = itemListHasRoundedBlockLayout(params) let hasCorners = itemListHasRoundedBlockLayout(params) || !item.noInsets
var hasTopCorners = false var hasTopCorners = false
var hasBottomCorners = false var hasBottomCorners = false
switch neighbors.top { switch neighbors.top {

View File

@ -305,7 +305,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
strongSelf.insertSubnode(strongSelf.maskNode, at: 3) strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
} }
let hasCorners = itemListHasRoundedBlockLayout(params) let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noInsets
var hasTopCorners = false var hasTopCorners = false
var hasBottomCorners = false var hasBottomCorners = false
switch neighbors.top { switch neighbors.top {

View File

@ -2743,9 +2743,9 @@
[self setPlayButtonHidden:true animated:false]; [self setPlayButtonHidden:true animated:false];
} }
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber editingStartValueDidChange:(NSTimeInterval)startValue - (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)videoScrubber editingStartValueDidChange:(NSTimeInterval)startValue
{ {
if (startValue > _dotPosition) { if (startValue > _dotPosition || videoScrubber.trimEndValue < _dotPosition) {
_resetDotPosition = true; _resetDotPosition = true;
[self resetDotImage]; [self resetDotImage];
} }
@ -2754,7 +2754,7 @@
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber editingEndValueDidChange:(NSTimeInterval)endValue - (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber editingEndValueDidChange:(NSTimeInterval)endValue
{ {
if (endValue < _dotPosition) { if (endValue < _dotPosition || videoScrubber.trimStartValue > _dotPosition) {
_resetDotPosition = true; _resetDotPosition = true;
[self resetDotImage]; [self resetDotImage];
} }

View File

@ -96,6 +96,22 @@ public final class AvatarGalleryControllerPresentationArguments {
} }
} }
public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryEntry] {
var updatedEntries: [AvatarGalleryEntry] = []
let count: Int32 = Int32(entries.count)
var index: Int32 = 0
for entry in entries {
let indexData = GalleryItemIndexData(position: index, totalCount: count)
if case let .topImage(representations, _, immediateThumbnailData) = entry {
updatedEntries.append(.topImage(representations, indexData, immediateThumbnailData))
} else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData) = entry {
updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData))
}
index += 1
}
return updatedEntries
}
public func initialAvatarGalleryEntries(peer: Peer) -> [AvatarGalleryEntry] { public func initialAvatarGalleryEntries(peer: Peer) -> [AvatarGalleryEntry] {
var initialEntries: [AvatarGalleryEntry] = [] var initialEntries: [AvatarGalleryEntry] = []
if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) { if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) {
@ -540,22 +556,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
} }
} }
private func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryEntry] {
var updatedEntries: [AvatarGalleryEntry] = []
let count: Int32 = Int32(entries.count)
var index: Int32 = 0
for entry in entries {
let indexData = GalleryItemIndexData(position: index, totalCount: count)
if case let .topImage(representations, _, immediateThumbnailData) = entry {
updatedEntries.append(.topImage(representations, indexData, immediateThumbnailData))
} else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData) = entry {
updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData))
}
index += 1
}
return updatedEntries
}
private func setMainEntry(_ rawEntry: AvatarGalleryEntry) { private func setMainEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty { if case .topImage = entry, !self.entries.isEmpty {
@ -600,7 +600,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
canDelete = false canDelete = false
} }
entries = self.normalizeEntries(entries) entries = normalizeEntries(entries)
self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceHasRoundCorners: self.sourceHasRoundCorners, delete: canDelete ? { [weak self] in self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceHasRoundCorners: self.sourceHasRoundCorners, delete: canDelete ? { [weak self] in
self?.deleteEntry(entry) self?.deleteEntry(entry)
@ -707,7 +707,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}) })
})) }))
if let position = rawEntry.indexData?.position, position > 0 { if self.peer.id == self.context.account.peerId, let position = rawEntry.indexData?.position, position > 0 {
let title: String let title: String
if let _ = rawEntry.videoRepresentations.last { if let _ = rawEntry.videoRepresentations.last {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo title = self.presentationData.strings.ProfilePhoto_SetMainVideo

View File

@ -555,7 +555,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { peer in
if peer is TelegramGroup { if peer is TelegramGroup {
} else { } else {
pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer)) pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: nil))
} }
}) })
}) })

View File

@ -19,6 +19,7 @@ import ItemListPeerItem
import ItemListPeerActionItem import ItemListPeerActionItem
private let maxUsersDisplayedLimit: Int32 = 10 private let maxUsersDisplayedLimit: Int32 = 10
private let maxUsersDisplayedHighLimit: Int32 = 12
private final class GroupStatsControllerArguments { private final class GroupStatsControllerArguments {
let context: AccountContext let context: AccountContext
@ -94,15 +95,15 @@ private enum StatsEntry: ItemListNodeEntry {
case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case topPostersTitle(PresentationTheme, String, String) case topPostersTitle(PresentationTheme, String, String)
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool) case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool, Bool)
case topPostersExpand(PresentationTheme, String) case topPostersExpand(PresentationTheme, String)
case topAdminsTitle(PresentationTheme, String, String) case topAdminsTitle(PresentationTheme, String, String)
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool) case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool, Bool)
case topAdminsExpand(PresentationTheme, String) case topAdminsExpand(PresentationTheme, String)
case topInvitersTitle(PresentationTheme, String, String) case topInvitersTitle(PresentationTheme, String, String)
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool) case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool, Bool)
case topInvitersExpand(PresentationTheme, String) case topInvitersExpand(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -174,19 +175,19 @@ private enum StatsEntry: ItemListNodeEntry {
return 17 return 17
case .topPostersTitle: case .topPostersTitle:
return 1000 return 1000
case let .topPoster(index, _, _, _, _, _, _): case let .topPoster(index, _, _, _, _, _, _, _):
return 1001 + index return 1001 + index
case .topPostersExpand: case .topPostersExpand:
return 1999 return 1999
case .topAdminsTitle: case .topAdminsTitle:
return 2000 return 2000
case let .topAdmin(index, _, _, _, _, _, _): case let .topAdmin(index, _, _, _, _, _, _, _):
return 2001 + index return 2001 + index
case .topAdminsExpand: case .topAdminsExpand:
return 2999 return 2999
case .topInvitersTitle: case .topInvitersTitle:
return 3000 return 3000
case let .topInviter(index, _, _, _, _, _, _): case let .topInviter(index, _, _, _, _, _, _, _):
return 3001 + index return 3001 + index
case .topInvitersExpand: case .topInvitersExpand:
return 3999 return 3999
@ -309,8 +310,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed): case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed, lhsCanPromote):
if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed { if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote {
return true return true
} else { } else {
return false return false
@ -327,8 +328,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed): case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed, lhsCanPromote):
if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed { if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote {
return true return true
} else { } else {
return false return false
@ -345,8 +346,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed): case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed, lhsCanPromote):
if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed { if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote {
return true return true
} else { } else {
return false return false
@ -393,7 +394,7 @@ private enum StatsEntry: ItemListNodeEntry {
let .topHoursGraph(_, _, _, graph, type), let .topHoursGraph(_, _, _, graph, type),
let .topWeekdaysGraph(_, _, _, graph, type): let .topWeekdaysGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks) return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .topPoster(_, _, strings, dateTimeFormat, peer, topPoster, revealed): case let .topPoster(_, _, strings, dateTimeFormat, peer, topPoster, revealed, canPromote):
var textComponents: [String] = [] var textComponents: [String] = []
if topPoster.messageCount > 0 { if topPoster.messageCount > 0 {
textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount)) textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount))
@ -402,13 +403,17 @@ private enum StatsEntry: ItemListNodeEntry {
} }
} }
var options: [ItemListPeerItemRevealOption] = [] var options: [ItemListPeerItemRevealOption] = []
if !peer.isDeleted {
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: { options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id) arguments.openPeerHistory(peer.id)
})) }))
if canPromote && arguments.context.account.peerId != peer.id {
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: { options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
arguments.promotePeer(peer.id) arguments.promotePeer(peer.id)
})) }))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { }
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id) arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId) arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId)
@ -417,7 +422,7 @@ private enum StatsEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandTopPosters() arguments.expandTopPosters()
}) })
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed): case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed, canPromote):
var textComponents: [String] = [] var textComponents: [String] = []
if topAdmin.deletedCount > 0 { if topAdmin.deletedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount)) textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount))
@ -429,13 +434,17 @@ private enum StatsEntry: ItemListNodeEntry {
textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount)) textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount))
} }
var options: [ItemListPeerItemRevealOption] = [] var options: [ItemListPeerItemRevealOption] = []
if !peer.isDeleted {
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopAdmin_Actions, action: { options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopAdmin_Actions, action: {
arguments.openPeerAdminActions(peer.id) arguments.openPeerAdminActions(peer.id)
})) }))
if canPromote && arguments.context.account.peerId != peer.id {
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopAdmin_Promote, action: { options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopAdmin_Promote, action: {
arguments.promotePeer(peer.id) arguments.promotePeer(peer.id)
})) }))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { }
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id) arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId) arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId)
@ -444,17 +453,21 @@ private enum StatsEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandTopAdmins() arguments.expandTopAdmins()
}) })
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed): case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed, canPromote):
var textComponents: [String] = [] var textComponents: [String] = []
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount)) textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
var options: [ItemListPeerItemRevealOption] = [] var options: [ItemListPeerItemRevealOption] = []
if !peer.isDeleted {
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: { options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id) arguments.openPeerHistory(peer.id)
})) }))
if canPromote && arguments.context.account.peerId != peer.id {
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: { options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
arguments.promotePeer(peer.id) arguments.promotePeer(peer.id)
})) }))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { }
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id) arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId) arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId)
@ -467,7 +480,7 @@ private enum StatsEntry: ItemListNodeEntry {
} }
} }
private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStats?, peers: [PeerId: Peer]?, presentationData: PresentationData) -> [StatsEntry] { private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStatsState, data: GroupStats?, channelPeer: Peer, peers: [PeerId: Peer]?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = [] var entries: [StatsEntry] = []
if let data = data { if let data = data {
@ -519,13 +532,14 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
} }
if let peers = peers { if let peers = peers {
let canPromote = canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: nil)
if !data.topPosters.isEmpty { if !data.topPosters.isEmpty {
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates)) entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates))
var index: Int32 = 0 var index: Int32 = 0
var topPosters = data.topPosters var topPosters = data.topPosters
var effectiveExpanded = state.topPostersExpanded var effectiveExpanded = state.topPostersExpanded
if topPosters.count > maxUsersDisplayedLimit && !effectiveExpanded { if topPosters.count > maxUsersDisplayedHighLimit && !effectiveExpanded {
topPosters = Array(topPosters.prefix(Int(maxUsersDisplayedLimit))) topPosters = Array(topPosters.prefix(Int(maxUsersDisplayedLimit)))
} else { } else {
effectiveExpanded = true effectiveExpanded = true
@ -533,7 +547,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
for topPoster in topPosters { for topPoster in topPosters {
if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 { if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 {
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions)) entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions, canPromote))
index += 1 index += 1
} }
} }
@ -548,7 +562,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
var topAdmins = data.topAdmins var topAdmins = data.topAdmins
var effectiveExpanded = state.topAdminsExpanded var effectiveExpanded = state.topAdminsExpanded
if topAdmins.count > maxUsersDisplayedLimit && !effectiveExpanded { if topAdmins.count > maxUsersDisplayedHighLimit && !effectiveExpanded {
topAdmins = Array(topAdmins.prefix(Int(maxUsersDisplayedLimit))) topAdmins = Array(topAdmins.prefix(Int(maxUsersDisplayedLimit)))
} else { } else {
effectiveExpanded = true effectiveExpanded = true
@ -556,7 +570,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
for topAdmin in data.topAdmins { for topAdmin in data.topAdmins {
if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 { if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 {
entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions)) entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions, canPromote))
index += 1 index += 1
} }
} }
@ -571,7 +585,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
var topInviters = data.topInviters var topInviters = data.topInviters
var effectiveExpanded = state.topInvitersExpanded var effectiveExpanded = state.topInvitersExpanded
if topInviters.count > maxUsersDisplayedLimit && !effectiveExpanded { if topInviters.count > maxUsersDisplayedHighLimit && !effectiveExpanded {
topInviters = Array(topInviters.prefix(Int(maxUsersDisplayedLimit))) topInviters = Array(topInviters.prefix(Int(maxUsersDisplayedLimit)))
} else { } else {
effectiveExpanded = true effectiveExpanded = true
@ -579,7 +593,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
for topInviter in data.topInviters { for topInviter in data.topInviters {
if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 { if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 {
entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions)) entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions, canPromote))
index += 1 index += 1
} }
} }
@ -667,6 +681,35 @@ private struct GroupStatsState: Equatable {
} }
} }
private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initialParticipant: ChannelParticipant?) -> Bool {
if let channel = channelPeer as? TelegramChannel {
if channel.flags.contains(.isCreator) {
return true
} else if let initialParticipant = initialParticipant {
switch initialParticipant {
case .creator:
return false
case let .member(_, _, adminInfo, _, _):
if let adminInfo = adminInfo {
return adminInfo.canBeEditedByAccountPeer || adminInfo.promotedBy == accountPeerId
} else {
return channel.hasPermission(.addAdmins)
}
}
} else {
return channel.hasPermission(.addAdmins)
}
} else if let group = channelPeer as? TelegramGroup {
if case .creator = group.role {
return true
} else {
return false
}
} else {
return false
}
}
public func groupStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController { public func groupStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
let statePromise = ValuePromise(GroupStatsState()) let statePromise = ValuePromise(GroupStatsState())
let stateValue = Atomic(value: GroupStatsState()) let stateValue = Atomic(value: GroupStatsState())
@ -792,9 +835,10 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
let previousData = Atomic<GroupStats?>(value: nil) let previousData = Atomic<GroupStats?>(value: nil)
let signal = combineLatest(statePromise.get(), context.sharedContext.presentationData, dataPromise.get(), peersPromise.get(), longLoadingSignal)
let signal = combineLatest(statePromise.get(), context.sharedContext.presentationData, dataPromise.get(), context.account.postbox.loadedPeerWithId(peerId), peersPromise.get(), longLoadingSignal)
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { state, presentationData, data, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { state, presentationData, data, channelPeer, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previous = previousData.swap(data) let previous = previousData.swap(data)
var emptyStateItem: ItemListControllerEmptyStateItem? var emptyStateItem: ItemListControllerEmptyStateItem?
if data == nil { if data == nil {
@ -806,7 +850,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(state: state, data: data, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: channelPeer, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
@ -851,7 +895,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
let _ = (context.account.postbox.loadedPeerWithId(peerId) let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { peer in
let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer) let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId)
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
}) })
} }

View File

@ -170,8 +170,6 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.item = item strongSelf.item = item
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: imageSize)) let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: imageSize))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize) let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize)
if let image = image { if let image = image {
@ -180,6 +178,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
imageNode = current imageNode = current
} else { } else {
imageNode = TransformImageNode() imageNode = TransformImageNode()
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
imageNode.layer.mask = shape imageNode.layer.mask = shape
strongSelf.imageNode = imageNode strongSelf.imageNode = imageNode
strongSelf.insertSubnode(imageNode, at: 0) strongSelf.insertSubnode(imageNode, at: 0)
@ -221,7 +221,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
videoNode.updateLayout(size: imageSize, transition: .immediate) videoNode.updateLayout(size: imageSize, transition: .immediate)
videoNode.frame = imageFrame videoNode.frame = imageFrame
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
videoNode.layer.mask = shape videoNode.layer.mask = shape
strongSelf.addSubnode(videoNode) strongSelf.addSubnode(videoNode)

View File

@ -18,6 +18,7 @@ final class ChatRecentActionsController: TelegramBaseController {
private let context: AccountContext private let context: AccountContext
private let peer: Peer private let peer: Peer
private let initialAdminPeerId: PeerId?
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
@ -26,9 +27,10 @@ final class ChatRecentActionsController: TelegramBaseController {
private let titleView: ChatRecentActionsTitleView private let titleView: ChatRecentActionsTitleView
init(context: AccountContext, peer: Peer) { init(context: AccountContext, peer: Peer, adminPeerId: PeerId?) {
self.context = context self.context = context
self.peer = peer self.peer = peer
self.initialAdminPeerId = adminPeerId
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -174,6 +176,11 @@ final class ChatRecentActionsController: TelegramBaseController {
return self?.navigationController as? NavigationController return self?.navigationController as? NavigationController
}) })
if let adminPeerId = self.initialAdminPeerId {
self.controllerNode.updateFilter(events: .all, adminPeerIds: [adminPeerId])
self.updateTitle()
}
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }

View File

@ -78,14 +78,17 @@ final class MultiScaleTextNode: ASDisplayNode {
return result return result
} }
func update(stateFractions: [AnyHashable: CGFloat], transition: ContainedViewLayoutTransition) { func update(stateFractions: [AnyHashable: CGFloat], alpha: CGFloat = 1.0, transition: ContainedViewLayoutTransition) {
var fractionSum: CGFloat = 0.0 var fractionSum: CGFloat = 0.0
for (_, fraction) in stateFractions { for (_, fraction) in stateFractions {
fractionSum += fraction fractionSum += fraction
} }
for (key, fraction) in stateFractions { for (key, fraction) in stateFractions {
if let node = self.stateNodes[key], let nodeLayout = node.currentLayout { if let node = self.stateNodes[key], let _ = node.currentLayout {
transition.updateAlpha(node: node, alpha: fraction / fractionSum) if !transition.isAnimated {
node.layer.removeAllAnimations()
}
transition.updateAlpha(node: node, alpha: fraction / fractionSum * alpha)
} }
} }
} }

View File

@ -81,6 +81,7 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
let leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0) let leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0)
let rightInset = sideInset let rightInset = sideInset
let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0 let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0
let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
@ -93,7 +94,7 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
} }
self.textNode.maximumNumberOfLines = 1 self.textNode.maximumNumberOfLines = 1
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue) self.textNode.attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: textColorValue)
let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude)) let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude))

View File

@ -114,6 +114,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
let leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0) let leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0)
let rightInset = sideInset + 18.0 let rightInset = sideInset + 18.0
let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0 let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0
let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
@ -125,12 +126,12 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
labelFont = Font.regular(15.0) labelFont = Font.regular(15.0)
} else { } else {
labelColorValue = presentationData.theme.list.itemSecondaryTextColor labelColorValue = presentationData.theme.list.itemSecondaryTextColor
labelFont = Font.regular(17.0) labelFont = titleFont
} }
self.labelNode.attributedText = NSAttributedString(string: item.label.text, font: labelFont, textColor: labelColorValue) self.labelNode.attributedText = NSAttributedString(string: item.label.text, font: labelFont, textColor: labelColorValue)
self.textNode.maximumNumberOfLines = 1 self.textNode.maximumNumberOfLines = 1
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue) self.textNode.attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: textColorValue)
let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude)) let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude))
let labelSize = self.labelNode.updateLayout(CGSize(width: width - textSize.width - (leftInset + rightInset), height: .greatestFiniteMagnitude)) let labelSize = self.labelNode.updateLayout(CGSize(width: width - textSize.width - (leftInset + rightInset), height: .greatestFiniteMagnitude))

View File

@ -738,7 +738,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
} }
} }
self.galleryEntries = entries self.galleryEntries = normalizeEntries(entries)
self.items = items self.items = items
self.itemsUpdated?(items) self.itemsUpdated?(items)
self.currentIndex = 0 self.currentIndex = 0
@ -767,7 +767,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
} }
} }
self.galleryEntries = entries self.galleryEntries = normalizeEntries(entries)
self.items = items self.items = items
self.itemsUpdated?(items) self.itemsUpdated?(items)
self.currentIndex = max(0, previousIndex - 1) self.currentIndex = max(0, previousIndex - 1)
@ -1019,6 +1019,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if case .immediate = transition, fraction == 1.0 { if case .immediate = transition, fraction == 1.0 {
return return
} }
if fraction > 0.0 {
self.videoNode?.pause()
} else {
self.videoNode?.play()
}
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction) transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
} }
} }
@ -1199,8 +1204,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
private var videoContent: NativeVideoContent? private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double? private var videoStartTimestamp: Double?
var tapped: (() -> Void)? var tapped: ((Bool) -> Void)?
var cancel: (() -> Void)?
var canAttachVideo = true var canAttachVideo = true
@ -1219,7 +1223,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state { if case .ended = recognizer.state {
self.tapped?() self.tapped?(false)
} }
} }
@ -1911,7 +1915,8 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
private let requestUpdateLayout: () -> Void private let requestUpdateLayout: () -> Void
let avatarNode: PeerInfoEditingAvatarNode let avatarNode: PeerInfoEditingAvatarNode
let avatarTextNode: HighlightableButtonNode let avatarTextNode: ImmediateTextNode
let avatarButtonNode: HighlightableButtonNode
var itemNodes: [PeerInfoHeaderTextFieldNodeKey: PeerInfoHeaderTextFieldNode] = [:] var itemNodes: [PeerInfoHeaderTextFieldNodeKey: PeerInfoHeaderTextFieldNode] = [:]
@ -1921,17 +1926,19 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
self.avatarNode = PeerInfoEditingAvatarNode(context: context) self.avatarNode = PeerInfoEditingAvatarNode(context: context)
self.avatarTextNode = HighlightableButtonNode() self.avatarTextNode = ImmediateTextNode()
self.avatarButtonNode = HighlightableButtonNode()
super.init() super.init()
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
self.avatarButtonNode.addSubnode(self.avatarTextNode)
self.avatarTextNode.addTarget(self, action: #selector(textPressed), forControlEvents: .touchUpInside) self.avatarButtonNode.addTarget(self, action: #selector(textPressed), forControlEvents: .touchUpInside)
} }
@objc private func textPressed() { @objc private func textPressed() {
self.avatarNode.tapped?() self.avatarNode.tapped?(true)
} }
func editingTextForKey(_ key: PeerInfoHeaderTextFieldNodeKey) -> String? { func editingTextForKey(_ key: PeerInfoHeaderTextFieldNodeKey) -> String? {
@ -1950,14 +1957,14 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0 var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0
if canEditPeerInfo(context: self.context, peer: peer) { if canEditPeerInfo(context: self.context, peer: peer) {
if self.avatarTextNode.supernode == nil { if self.avatarButtonNode.supernode == nil {
self.addSubnode(self.avatarTextNode) self.addSubnode(self.avatarButtonNode)
} }
self.avatarTextNode.attributedText = NSAttributedString(string: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, font: Font.regular(17.0), textColor: presentationData.theme.list.itemAccentColor)
self.avatarTextNode.setAttributedTitle(NSAttributedString(string: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, font: Font.regular(17.0), textColor: presentationData.theme.list.itemAccentColor), for: []) let avatarTextSize = self.avatarTextNode.updateLayout(CGSize(width: width, height: 32.0))
transition.updateFrame(node: self.avatarTextNode, frame: CGRect(origin: CGPoint(), size: avatarTextSize))
let avatarTextSize = self.avatarTextNode.measure(CGSize(width: width, height: 32.0)) transition.updateFrame(node: self.avatarButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - avatarTextSize.width) / 2.0), y: contentHeight - 1.0), size: avatarTextSize))
transition.updateFrame(node: self.avatarTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - avatarTextSize.width) / 2.0), y: contentHeight - 1.0), size: avatarTextSize))
contentHeight += 32.0 contentHeight += 32.0
} }
@ -2068,6 +2075,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
private let videoCallsEnabled: Bool private let videoCallsEnabled: Bool
private(set) var isAvatarExpanded: Bool private(set) var isAvatarExpanded: Bool
private(set) var twoLineInfo = false
let avatarListNode: PeerInfoAvatarListNode let avatarListNode: PeerInfoAvatarListNode
@ -2093,7 +2101,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var performButtonAction: ((PeerInfoHeaderButtonKey) -> Void)? var performButtonAction: ((PeerInfoHeaderButtonKey) -> Void)?
var requestAvatarExpansion: ((Bool, [AvatarGalleryEntry], AvatarGalleryEntry?, (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?) -> Void)? var requestAvatarExpansion: ((Bool, [AvatarGalleryEntry], AvatarGalleryEntry?, (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?) -> Void)?
var requestOpenAvatarForEditing: (() -> Void)? var requestOpenAvatarForEditing: ((Bool) -> Void)?
var requestUpdateLayout: (() -> Void)? var requestUpdateLayout: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition? var navigationTransition: PeerInfoHeaderNavigationTransition?
@ -2126,11 +2134,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.subtitleNodeRawContainer = ASDisplayNode() self.subtitleNodeRawContainer = ASDisplayNode()
self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded]) self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.subtitleNode.displaysAsynchronously = false self.subtitleNode.displaysAsynchronously = false
self.subtitleNode.allowsGroupOpacity = true
self.usernameNodeContainer = ASDisplayNode() self.usernameNodeContainer = ASDisplayNode()
self.usernameNodeRawContainer = ASDisplayNode() self.usernameNodeRawContainer = ASDisplayNode()
self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded]) self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.usernameNode.displaysAsynchronously = false self.usernameNode.displaysAsynchronously = false
self.usernameNode.allowsGroupOpacity = true
self.regularContentNode = PeerInfoHeaderRegularContentNode() self.regularContentNode = PeerInfoHeaderRegularContentNode()
var requestUpdateLayoutImpl: (() -> Void)? var requestUpdateLayoutImpl: (() -> Void)?
@ -2176,11 +2186,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
self?.initiateAvatarExpansion() self?.initiateAvatarExpansion()
} }
self.editingContentNode.avatarNode.tapped = { [weak self] in self.editingContentNode.avatarNode.tapped = { [weak self] confirm in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.requestOpenAvatarForEditing?() strongSelf.requestOpenAvatarForEditing?(confirm)
} }
} }
@ -2366,21 +2376,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height)) TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
], mainState: TitleNodeStateRegular) ], mainState: TitleNodeStateRegular)
self.titleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], transition: transition)
self.subtitleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], transition: transition)
self.usernameNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], transition: transition)
let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0 let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 10.0), size: CGSize(width: avatarSize, height: avatarSize)) let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 10.0), size: CGSize(width: avatarSize, height: avatarSize))
let avatarCenter = CGPoint(x: (1.0 - transitionFraction) * avatarFrame.midX + transitionFraction * transitionSourceAvatarFrame.midX, y: (1.0 - transitionFraction) * avatarFrame.midY + transitionFraction * transitionSourceAvatarFrame.midY) let avatarCenter = CGPoint(x: (1.0 - transitionFraction) * avatarFrame.midX + transitionFraction * transitionSourceAvatarFrame.midX, y: (1.0 - transitionFraction) * avatarFrame.midY + transitionFraction * transitionSourceAvatarFrame.midY)
@ -2402,6 +2397,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let subtitleFrame: CGRect let subtitleFrame: CGRect
let usernameFrame: CGRect let usernameFrame: CGRect
let usernameSpacing: CGFloat = 4.0 let usernameSpacing: CGFloat = 4.0
var twoLineInfo = false
if self.isAvatarExpanded { if self.isAvatarExpanded {
let minTitleSize = CGSize(width: titleSize.width * 0.7, height: titleSize.height * 0.7) let minTitleSize = CGSize(width: titleSize.width * 0.7, height: titleSize.height * 0.7)
let minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - expandedAvatarControlsHeight + 9.0 + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize) let minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - expandedAvatarControlsHeight + 9.0 + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize)
@ -2412,14 +2408,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 10.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize) titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 10.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize)
let totalSubtitleWidth = subtitleSize.width + usernameSpacing + usernameSize.width let totalSubtitleWidth = subtitleSize.width + usernameSpacing + usernameSize.width
if usernameSize.width == 0.0 || totalSubtitleWidth > width - textSideInset * 2.0 { twoLineInfo = totalSubtitleWidth > width - textSideInset * 2.0
if usernameSize.width == 0.0 || twoLineInfo {
subtitleFrame = CGRect(origin: CGPoint(x: floor((width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) subtitleFrame = CGRect(origin: CGPoint(x: floor((width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
usernameFrame = CGRect(origin: CGPoint(x: floor((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize) usernameFrame = CGRect(origin: CGPoint(x: floor((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize)
} else { } else {
subtitleFrame = CGRect(origin: CGPoint(x: floor((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) subtitleFrame = CGRect(origin: CGPoint(x: floor((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
usernameFrame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + usernameSpacing, y: titleFrame.maxY + 1.0), size: usernameSize) usernameFrame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + usernameSpacing, y: titleFrame.maxY + 1.0), size: usernameSize)
} }
} }
self.twoLineInfo = twoLineInfo
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 || subtitleSize.height.isZero) ? 8.0 : 0.0
@ -2435,6 +2434,22 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset
self.titleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], transition: transition)
let subtitleAlpha: CGFloat = self.isSettings ? 1.0 - titleCollapseFraction : 1.0
self.subtitleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], alpha: subtitleAlpha, transition: transition)
self.usernameNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], alpha: subtitleAlpha, transition: transition)
let avatarScale: CGFloat let avatarScale: CGFloat
let avatarOffset: CGFloat let avatarOffset: CGFloat
if self.navigationTransition != nil { if self.navigationTransition != nil {
@ -2544,7 +2559,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition) self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition)
let panelWithAvatarHeight: CGFloat = (self.isSettings ? 40.0 : 112.0) + avatarSize var panelWithAvatarHeight: CGFloat = (self.isSettings ? 40.0 : 112.0) + avatarSize
if twoLineInfo {
panelWithAvatarHeight += 17.0
}
let buttonsCollapseStart = titleCollapseOffset let buttonsCollapseStart = titleCollapseOffset
let buttonsCollapseEnd = panelWithAvatarHeight - (navigationHeight - statusBarHeight) + 10.0 let buttonsCollapseEnd = panelWithAvatarHeight - (navigationHeight - statusBarHeight) + 10.0
@ -2589,10 +2607,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale) transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale) transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale)
transition.updateSublayerTransformScale(node: self.usernameNodeContainer, scale: subtitleScale) transition.updateSublayerTransformScale(node: self.usernameNodeContainer, scale: subtitleScale)
if self.isSettings {
transition.updateAlpha(node: self.subtitleNode, alpha: 1.0 - titleCollapseFraction, beginWithCurrentState: true)
transition.updateAlpha(node: self.usernameNode, alpha: 1.0 - titleCollapseFraction, beginWithCurrentState: true)
}
} else { } else {
let titleScale: CGFloat let titleScale: CGFloat
let subtitleScale: CGFloat let subtitleScale: CGFloat
@ -2617,18 +2632,20 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateFrameAdditive(node: self.usernameNodeContainer, frame: CGRect(origin: rawUsernameFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset)) transition.updateFrameAdditive(node: self.usernameNodeContainer, frame: CGRect(origin: rawUsernameFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
} else { } else {
transition.updateFrameAdditiveToCenter(node: self.titleNodeContainer, frame: CGRect(origin: rawTitleFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset + apparentTitleLockOffset)) transition.updateFrameAdditiveToCenter(node: self.titleNodeContainer, frame: CGRect(origin: rawTitleFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset + apparentTitleLockOffset))
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: rawUsernameFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset)) var subtitleCenter = rawSubtitleFrame.center
subtitleCenter.x = rawTitleFrame.center.x + (subtitleCenter.x - rawTitleFrame.center.x) * subtitleScale
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: subtitleCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
var usernameCenter = rawUsernameFrame.center
usernameCenter.x = rawTitleFrame.center.x + (usernameCenter.x - rawTitleFrame.center.x) * subtitleScale
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
} }
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(), size: CGSize())) transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize())) transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale) transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale) transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale)
transition.updateSublayerTransformScaleAdditive(node: self.usernameNodeContainer, scale: subtitleScale) transition.updateSublayerTransformScaleAdditive(node: self.usernameNodeContainer, scale: subtitleScale)
if self.isSettings {
transition.updateAlpha(node: self.subtitleNode, alpha: 1.0 - titleCollapseFraction, beginWithCurrentState: true)
transition.updateAlpha(node: self.usernameNode, alpha: 1.0 - titleCollapseFraction, beginWithCurrentState: true)
}
} }
} }
@ -2810,7 +2827,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if self.isAvatarExpanded { if self.isAvatarExpanded {
resolvedRegularHeight = expandedAvatarListSize.height + expandedAvatarControlsHeight resolvedRegularHeight = expandedAvatarListSize.height + expandedAvatarControlsHeight
} else { } else {
resolvedRegularHeight = (self.isSettings ? 40.0 : 112.0) + avatarSize + navigationHeight resolvedRegularHeight = panelWithAvatarHeight + navigationHeight
} }
let backgroundFrame: CGRect let backgroundFrame: CGRect

View File

@ -819,7 +819,7 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
let ItemAddAccountHelp = 6 let ItemAddAccountHelp = 6
let ItemLogout = 7 let ItemLogout = 7
items[.help]!.append(PeerInfoScreenCommentItem(id: ItemNameHelp, text: presentationData.strings.EditProfile_NameAndPhotoHelp)) 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
@ -837,10 +837,12 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
interaction.openSettings(.username) interaction.openSettings(.username)
})) }))
if let settings = data.globalSettings, settings.accountsAndPeers.count + 1 < maximumNumberOfAccounts {
items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: { items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: {
interaction.openSettings(.addAccount) interaction.openSettings(.addAccount)
})) }))
items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: presentationData.strings.Settings_AddAnotherAccount_Help)) items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: 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)
@ -2110,16 +2112,37 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})) }))
} }
self.headerNode.requestOpenAvatarForEditing = { [weak self] in self.headerNode.requestOpenAvatarForEditing = { [weak self] confirm in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if strongSelf.state.updatingAvatar != nil { if strongSelf.state.updatingAvatar != nil {
let proceed = {
strongSelf.updateAvatarDisposable.set(nil) strongSelf.updateAvatarDisposable.set(nil)
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil) strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
} }
}
if confirm {
let controller = ActionSheetController(presentationData: strongSelf.presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Settings_CancelUpload, color: .destructive, action: { [weak self] in
dismissAction()
proceed()
}))
controller.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
strongSelf.controller?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} else {
proceed()
}
} else { } else {
strongSelf.openAvatarForEditing() strongSelf.openAvatarForEditing()
} }
@ -2140,7 +2163,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
switch key { switch key {
case .edit: case .edit:
(strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.2, curve: .easeInOut)) (strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.3, curve: .linear))
strongSelf.state = strongSelf.state.withIsEditing(true) strongSelf.state = strongSelf.state.withIsEditing(true)
var updateOnCompletion = false var updateOnCompletion = false
if strongSelf.headerNode.isAvatarExpanded { if strongSelf.headerNode.isAvatarExpanded {
@ -2166,7 +2189,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}) })
strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true) strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true)
case .done, .cancel: case .done, .cancel:
(strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.2, curve: .easeInOut)) (strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.3, curve: .linear))
strongSelf.view.endEditing(true) strongSelf.view.endEditing(true)
if case .done = key { if case .done = key {
guard let data = strongSelf.data else { guard let data = strongSelf.data else {
@ -3880,13 +3903,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if let item = item, case let .image(image) = item { if let item = item, case let .image(image) = item {
if index > 0 { if index > 0 {
let title: String let setMainTitle: String
if image.2.isEmpty { if image.2.isEmpty {
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto setMainTitle = self.presentationData.strings.ProfilePhoto_SetMainPhoto
} else { } else {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo setMainTitle = self.presentationData.strings.ProfilePhoto_SetMainVideo
} }
items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in items.append(ActionSheetButtonItem(title: setMainTitle, color: .accent, action: { [weak self] in
dismissAction() dismissAction()
self?.setMainAvatar(item) self?.setMainAvatar(item)
})) }))
@ -3897,7 +3920,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.editAvatarItem(item) self?.editAvatarItem(item)
})) }))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.GroupInfo_SetGroupPhotoDelete, color: .destructive, action: { [weak self] in
let deleteTitle: String
if image.2.isEmpty {
deleteTitle = self.presentationData.strings.GroupInfo_SetGroupPhotoDelete
} else {
deleteTitle = self.presentationData.strings.Settings_RemoveVideo
}
items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak self] in
dismissAction() dismissAction()
self?.deleteAvatar(item) self?.deleteAvatar(item)
})) }))
@ -4116,7 +4146,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
assetsController?.dismiss() assetsController?.dismiss()
self?.updateProfilePhoto(result) self?.updateProfilePhoto(result)
})) }))
strongSelf.controller?.present(controller, in: .window(.root)) controller.navigationPresentation = .modal
strongSelf.controller?.push(controller)
} }
mixin.didFinishWithImage = { [weak self] image in mixin.didFinishWithImage = { [weak self] image in
if let image = image { if let image = image {
@ -5344,7 +5375,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let (_, navigationHeight) = self.validLayout else { guard let (_, navigationHeight) = self.validLayout else {
return return
} }
let height: CGFloat = self.isSettings ? 140.0 : 212.0 var height: CGFloat = self.isSettings ? 140.0 : 212.0
if self.headerNode.twoLineInfo {
height += 17.0
}
if !self.state.isEditing { if !self.state.isEditing {
if targetContentOffset.pointee.y < height { if targetContentOffset.pointee.y < height {
if targetContentOffset.pointee.y < height / 2.0 { if targetContentOffset.pointee.y < height / 2.0 {

View File

@ -1096,8 +1096,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return peerSharedMediaControllerImpl(context: context, peerId: peerId) return peerSharedMediaControllerImpl(context: context, peerId: peerId)
} }
public func makeChatRecentActionsController(context: AccountContext, peer: Peer) -> ViewController { public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController {
return ChatRecentActionsController(context: context, peer: peer) return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId)
} }
public func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) { public func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) {