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.EditPhoto" = "Edit Photo";
"Settings.EditVideo" = "Edit Video";
"Settings.CancelUpload" = "Cancel Upload";
"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.";
"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 makeLocalizationListController(context: AccountContext) -> 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 navigateToChatController(_ params: NavigateToChatControllerParams)
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 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 contentImageTrailingSpace: CGFloat = 5.0
var contentImageSpecs: [(message: Message, media: Media, size: CGSize)] = []
@ -1338,7 +1339,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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 titleSpacing: CGFloat = -1.0
@ -1633,7 +1633,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
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] = []
for (message, media, mediaSize) in contentImageSpecs {
guard let mediaId = media.id else {
@ -1824,16 +1824,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
textFrame.origin.x = contentRect.origin.x
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
for (_, media, mediaSize) in self.currentMediaPreviewSpecs {
guard let mediaId = media.id else {
continue
}
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2743,9 +2743,9 @@
[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;
[self resetDotImage];
}
@ -2754,7 +2754,7 @@
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber editingEndValueDidChange:(NSTimeInterval)endValue
{
if (endValue < _dotPosition) {
if (endValue < _dotPosition || videoScrubber.trimStartValue > _dotPosition) {
_resetDotPosition = true;
[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] {
var initialEntries: [AvatarGalleryEntry] = []
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) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {
@ -600,7 +600,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
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?.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
if let _ = rawEntry.videoRepresentations.last {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo

View File

@ -555,7 +555,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI
|> deliverOnMainQueue).start(next: { peer in
if peer is TelegramGroup {
} 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
private let maxUsersDisplayedLimit: Int32 = 10
private let maxUsersDisplayedHighLimit: Int32 = 12
private final class GroupStatsControllerArguments {
let context: AccountContext
@ -94,15 +95,15 @@ private enum StatsEntry: ItemListNodeEntry {
case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
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 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 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)
var section: ItemListSectionId {
@ -174,19 +175,19 @@ private enum StatsEntry: ItemListNodeEntry {
return 17
case .topPostersTitle:
return 1000
case let .topPoster(index, _, _, _, _, _, _):
case let .topPoster(index, _, _, _, _, _, _, _):
return 1001 + index
case .topPostersExpand:
return 1999
case .topAdminsTitle:
return 2000
case let .topAdmin(index, _, _, _, _, _, _):
case let .topAdmin(index, _, _, _, _, _, _, _):
return 2001 + index
case .topAdminsExpand:
return 2999
case .topInvitersTitle:
return 3000
case let .topInviter(index, _, _, _, _, _, _):
case let .topInviter(index, _, _, _, _, _, _, _):
return 3001 + index
case .topInvitersExpand:
return 3999
@ -309,8 +310,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed):
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 {
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed, lhsCanPromote):
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
} else {
return false
@ -327,8 +328,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed):
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 {
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed, lhsCanPromote):
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
} else {
return false
@ -345,8 +346,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed):
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 {
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed, lhsCanPromote):
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
} else {
return false
@ -393,7 +394,7 @@ private enum StatsEntry: ItemListNodeEntry {
let .topHoursGraph(_, _, _, graph, type),
let .topWeekdaysGraph(_, _, _, graph, type):
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] = []
if topPoster.messageCount > 0 {
textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount))
@ -402,13 +403,17 @@ private enum StatsEntry: ItemListNodeEntry {
}
}
var options: [ItemListPeerItemRevealOption] = []
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id)
}))
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
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: {
if !peer.isDeleted {
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id)
}))
if canPromote && arguments.context.account.peerId != peer.id {
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
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: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
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: {
arguments.expandTopPosters()
})
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed):
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin, revealed, canPromote):
var textComponents: [String] = []
if topAdmin.deletedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount))
@ -429,13 +434,17 @@ private enum StatsEntry: ItemListNodeEntry {
textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount))
}
var options: [ItemListPeerItemRevealOption] = []
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopAdmin_Actions, action: {
arguments.openPeerAdminActions(peer.id)
}))
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopAdmin_Promote, action: {
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: {
if !peer.isDeleted {
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopAdmin_Actions, action: {
arguments.openPeerAdminActions(peer.id)
}))
if canPromote && arguments.context.account.peerId != peer.id {
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopAdmin_Promote, action: {
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: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
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: {
arguments.expandTopAdmins()
})
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed):
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter, revealed, canPromote):
var textComponents: [String] = []
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
var options: [ItemListPeerItemRevealOption] = []
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id)
}))
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
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: {
if !peer.isDeleted {
options.append(ItemListPeerItemRevealOption(type: .accent, title: strings.Stats_GroupTopPoster_History, action: {
arguments.openPeerHistory(peer.id)
}))
if canPromote && arguments.context.account.peerId != peer.id {
options.append(ItemListPeerItemRevealOption(type: .neutral, title: strings.Stats_GroupTopPoster_Promote, action: {
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: arguments.context.account.peerId != peer.id, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
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] = []
if let data = data {
@ -517,15 +530,16 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
entries.append(.topWeekdaysTitle(presentationData.theme, presentationData.strings.Stats_GroupTopWeekdaysTitle))
entries.append(.topWeekdaysGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topWeekdaysGraph, .pie))
}
if let peers = peers {
let canPromote = canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: nil)
if !data.topPosters.isEmpty {
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle, dates))
var index: Int32 = 0
var topPosters = data.topPosters
var effectiveExpanded = state.topPostersExpanded
if topPosters.count > maxUsersDisplayedLimit && !effectiveExpanded {
if topPosters.count > maxUsersDisplayedHighLimit && !effectiveExpanded {
topPosters = Array(topPosters.prefix(Int(maxUsersDisplayedLimit)))
} else {
effectiveExpanded = true
@ -533,7 +547,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
for topPoster in topPosters {
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
}
}
@ -548,7 +562,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
var topAdmins = data.topAdmins
var effectiveExpanded = state.topAdminsExpanded
if topAdmins.count > maxUsersDisplayedLimit && !effectiveExpanded {
if topAdmins.count > maxUsersDisplayedHighLimit && !effectiveExpanded {
topAdmins = Array(topAdmins.prefix(Int(maxUsersDisplayedLimit)))
} else {
effectiveExpanded = true
@ -556,7 +570,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
for topAdmin in data.topAdmins {
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
}
}
@ -571,7 +585,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
var topInviters = data.topInviters
var effectiveExpanded = state.topInvitersExpanded
if topInviters.count > maxUsersDisplayedLimit && !effectiveExpanded {
if topInviters.count > maxUsersDisplayedHighLimit && !effectiveExpanded {
topInviters = Array(topInviters.prefix(Int(maxUsersDisplayedLimit)))
} else {
effectiveExpanded = true
@ -579,7 +593,7 @@ private func groupStatsControllerEntries(state: GroupStatsState, data: GroupStat
for topInviter in data.topInviters {
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
}
}
@ -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 {
let statePromise = ValuePromise(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 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
|> 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)
var emptyStateItem: ItemListControllerEmptyStateItem?
if data == nil {
@ -804,9 +848,9 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
}
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))
}
@ -851,7 +895,7 @@ public func groupStatsController(context: AccountContext, peerId: PeerId, cached
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> 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)
})
}

View File

@ -170,9 +170,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.item = item
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)
if let image = image {
let imageNode: TransformImageNode
@ -180,6 +178,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
imageNode = current
} else {
imageNode = TransformImageNode()
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
imageNode.layer.mask = shape
strongSelf.imageNode = imageNode
strongSelf.insertSubnode(imageNode, at: 0)
@ -221,7 +221,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
videoNode.updateLayout(size: imageSize, transition: .immediate)
videoNode.frame = imageFrame
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
videoNode.layer.mask = shape
strongSelf.addSubnode(videoNode)

View File

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

View File

@ -78,14 +78,17 @@ final class MultiScaleTextNode: ASDisplayNode {
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
for (_, fraction) in stateFractions {
fractionSum += fraction
}
for (key, fraction) in stateFractions {
if let node = self.stateNodes[key], let nodeLayout = node.currentLayout {
transition.updateAlpha(node: node, alpha: fraction / fractionSum)
if let node = self.stateNodes[key], let _ = node.currentLayout {
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 rightInset = sideInset
let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0
let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
@ -93,7 +94,7 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
}
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))

View File

@ -114,9 +114,10 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
let leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0)
let rightInset = sideInset + 18.0
let separatorInset = item.icon == nil ? sideInset : leftInset - 1.0
let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let textColorValue: UIColor = presentationData.theme.list.itemPrimaryTextColor
let labelColorValue: UIColor
let labelFont: UIFont
@ -125,12 +126,12 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
labelFont = Font.regular(15.0)
} else {
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.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 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.itemsUpdated?(items)
self.currentIndex = 0
@ -767,7 +767,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
}
self.galleryEntries = entries
self.galleryEntries = normalizeEntries(entries)
self.items = items
self.itemsUpdated?(items)
self.currentIndex = max(0, previousIndex - 1)
@ -1019,6 +1019,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if case .immediate = transition, fraction == 1.0 {
return
}
if fraction > 0.0 {
self.videoNode?.pause()
} else {
self.videoNode?.play()
}
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
}
}
@ -1199,8 +1204,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
var tapped: (() -> Void)?
var cancel: (() -> Void)?
var tapped: ((Bool) -> Void)?
var canAttachVideo = true
@ -1219,7 +1223,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.tapped?()
self.tapped?(false)
}
}
@ -1911,7 +1915,8 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
private let requestUpdateLayout: () -> Void
let avatarNode: PeerInfoEditingAvatarNode
let avatarTextNode: HighlightableButtonNode
let avatarTextNode: ImmediateTextNode
let avatarButtonNode: HighlightableButtonNode
var itemNodes: [PeerInfoHeaderTextFieldNodeKey: PeerInfoHeaderTextFieldNode] = [:]
@ -1921,17 +1926,19 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
self.avatarNode = PeerInfoEditingAvatarNode(context: context)
self.avatarTextNode = HighlightableButtonNode()
self.avatarTextNode = ImmediateTextNode()
self.avatarButtonNode = HighlightableButtonNode()
super.init()
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() {
self.avatarNode.tapped?()
self.avatarNode.tapped?(true)
}
func editingTextForKey(_ key: PeerInfoHeaderTextFieldNodeKey) -> String? {
@ -1950,14 +1957,14 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0
if canEditPeerInfo(context: self.context, peer: peer) {
if self.avatarTextNode.supernode == nil {
self.addSubnode(self.avatarTextNode)
if self.avatarButtonNode.supernode == nil {
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.measure(CGSize(width: width, height: 32.0))
transition.updateFrame(node: self.avatarTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - avatarTextSize.width) / 2.0), y: contentHeight - 1.0), size: avatarTextSize))
let avatarTextSize = self.avatarTextNode.updateLayout(CGSize(width: width, height: 32.0))
transition.updateFrame(node: self.avatarTextNode, frame: CGRect(origin: CGPoint(), size: avatarTextSize))
transition.updateFrame(node: self.avatarButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - avatarTextSize.width) / 2.0), y: contentHeight - 1.0), size: avatarTextSize))
contentHeight += 32.0
}
@ -2068,6 +2075,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
private let videoCallsEnabled: Bool
private(set) var isAvatarExpanded: Bool
private(set) var twoLineInfo = false
let avatarListNode: PeerInfoAvatarListNode
@ -2093,7 +2101,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var performButtonAction: ((PeerInfoHeaderButtonKey) -> Void)?
var requestAvatarExpansion: ((Bool, [AvatarGalleryEntry], AvatarGalleryEntry?, (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?) -> Void)?
var requestOpenAvatarForEditing: (() -> Void)?
var requestOpenAvatarForEditing: ((Bool) -> Void)?
var requestUpdateLayout: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition?
@ -2126,11 +2134,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.subtitleNodeRawContainer = ASDisplayNode()
self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.subtitleNode.displaysAsynchronously = false
self.subtitleNode.allowsGroupOpacity = true
self.usernameNodeContainer = ASDisplayNode()
self.usernameNodeRawContainer = ASDisplayNode()
self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.usernameNode.displaysAsynchronously = false
self.usernameNode.allowsGroupOpacity = true
self.regularContentNode = PeerInfoHeaderRegularContentNode()
var requestUpdateLayoutImpl: (() -> Void)?
@ -2176,11 +2186,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
self?.initiateAvatarExpansion()
}
self.editingContentNode.avatarNode.tapped = { [weak self] in
self.editingContentNode.avatarNode.tapped = { [weak self] confirm in
guard let strongSelf = self else {
return
}
strongSelf.requestOpenAvatarForEditing?()
strongSelf.requestOpenAvatarForEditing?(confirm)
}
}
@ -2365,22 +2375,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
], 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 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)
@ -2402,6 +2397,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let subtitleFrame: CGRect
let usernameFrame: CGRect
let usernameSpacing: CGFloat = 4.0
var twoLineInfo = false
if self.isAvatarExpanded {
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)
@ -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)
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)
usernameFrame = CGRect(origin: CGPoint(x: floor((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize)
} else {
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)
}
}
self.twoLineInfo = twoLineInfo
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
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 avatarOffset: CGFloat
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)
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 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.subtitleNodeContainer, 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 {
let titleScale: 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))
} else {
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.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, 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 {
resolvedRegularHeight = expandedAvatarListSize.height + expandedAvatarControlsHeight
} else {
resolvedRegularHeight = (self.isSettings ? 40.0 : 112.0) + avatarSize + navigationHeight
resolvedRegularHeight = panelWithAvatarHeight + navigationHeight
}
let backgroundFrame: CGRect

View File

@ -819,7 +819,7 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
let ItemAddAccountHelp = 6
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 {
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)
}))
items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: {
interaction.openSettings(.addAccount)
}))
items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: presentationData.strings.Settings_AddAnotherAccount_Help))
if let settings = data.globalSettings, settings.accountsAndPeers.count + 1 < maximumNumberOfAccounts {
items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: {
interaction.openSettings(.addAccount)
}))
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: {
interaction.openSettings(.logout)
@ -2110,15 +2112,36 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}))
}
self.headerNode.requestOpenAvatarForEditing = { [weak self] in
self.headerNode.requestOpenAvatarForEditing = { [weak self] confirm in
guard let strongSelf = self else {
return
}
if strongSelf.state.updatingAvatar != nil {
strongSelf.updateAvatarDisposable.set(nil)
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
let proceed = {
strongSelf.updateAvatarDisposable.set(nil)
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
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 {
strongSelf.openAvatarForEditing()
@ -2140,7 +2163,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
switch key {
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)
var updateOnCompletion = false
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)
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)
if case .done = key {
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 index > 0 {
let title: String
let setMainTitle: String
if image.2.isEmpty {
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
setMainTitle = self.presentationData.strings.ProfilePhoto_SetMainPhoto
} 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()
self?.setMainAvatar(item)
}))
@ -3897,7 +3920,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
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()
self?.deleteAvatar(item)
}))
@ -4116,7 +4146,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
assetsController?.dismiss()
self?.updateProfilePhoto(result)
}))
strongSelf.controller?.present(controller, in: .window(.root))
controller.navigationPresentation = .modal
strongSelf.controller?.push(controller)
}
mixin.didFinishWithImage = { [weak self] image in
if let image = image {
@ -5344,7 +5375,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let (_, navigationHeight) = self.validLayout else {
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 targetContentOffset.pointee.y < height {
if targetContentOffset.pointee.y < height / 2.0 {

View File

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