mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-15 13:14:10 +00:00
Support of channel/group ownership transfer
People Nearby initial implementation Ghost Icons for deleted users
This commit is contained in:
parent
339bc3c472
commit
bc96107da1
22
Images.xcassets/Avatar/DeletedIcon.imageset/Contents.json
vendored
Normal file
22
Images.xcassets/Avatar/DeletedIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Ghost@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Ghost@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Avatar/DeletedIcon.imageset/Ghost@2x.png
vendored
Normal file
BIN
Images.xcassets/Avatar/DeletedIcon.imageset/Ghost@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Images.xcassets/Avatar/DeletedIcon.imageset/Ghost@3x.png
vendored
Normal file
BIN
Images.xcassets/Avatar/DeletedIcon.imageset/Ghost@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
22
Images.xcassets/Contact List/PeopleNearbyIcon.imageset/Contents.json
vendored
Normal file
22
Images.xcassets/Contact List/PeopleNearbyIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "locationcontacts@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "locationcontacts@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Images.xcassets/Contact List/PeopleNearbyIcon.imageset/locationcontacts@2x.png
vendored
Normal file
BIN
Images.xcassets/Contact List/PeopleNearbyIcon.imageset/locationcontacts@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 619 B |
BIN
Images.xcassets/Contact List/PeopleNearbyIcon.imageset/locationcontacts@3x.png
vendored
Normal file
BIN
Images.xcassets/Contact List/PeopleNearbyIcon.imageset/locationcontacts@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 736 B |
@ -16,6 +16,11 @@
|
||||
090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; };
|
||||
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
|
||||
090E777922A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift */; };
|
||||
090E778622A9B95A00CD99F5 /* PeopleNearbyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778522A9B95A00CD99F5 /* PeopleNearbyController.swift */; };
|
||||
090E778822A9B96100CD99F5 /* PeopleNearbyHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778722A9B96000CD99F5 /* PeopleNearbyHeaderItem.swift */; };
|
||||
090E778A22A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778922A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift */; };
|
||||
090E778C22AA842300CD99F5 /* anim_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 090E778B22AA842200CD99F5 /* anim_success.json */; };
|
||||
090E778E22AA863A00CD99F5 /* PeopleNearbyIconNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778D22AA863A00CD99F5 /* PeopleNearbyIconNode.swift */; };
|
||||
0910B0ED21FA178C00F8F87D /* WallpaperPreviewMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EC21FA178C00F8F87D /* WallpaperPreviewMedia.swift */; };
|
||||
0910B0EF21FA532D00F8F87D /* WallpaperResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EE21FA532D00F8F87D /* WallpaperResources.swift */; };
|
||||
0910B0F121FB3DE100F8F87D /* WallpaperPatternPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0F021FB3DE100F8F87D /* WallpaperPatternPanelNode.swift */; };
|
||||
@ -1218,6 +1223,11 @@
|
||||
090E63E52195880F00E3C035 /* ContactAddItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAddItem.swift; sourceTree = "<group>"; };
|
||||
090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAddContact.swift; sourceTree = "<group>"; };
|
||||
090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsThemeItem.swift; sourceTree = "<group>"; };
|
||||
090E778522A9B95A00CD99F5 /* PeopleNearbyController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleNearbyController.swift; sourceTree = "<group>"; };
|
||||
090E778722A9B96000CD99F5 /* PeopleNearbyHeaderItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleNearbyHeaderItem.swift; sourceTree = "<group>"; };
|
||||
090E778922A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelOwnershipTransferController.swift; sourceTree = "<group>"; };
|
||||
090E778B22AA842200CD99F5 /* anim_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_success.json; sourceTree = "<group>"; };
|
||||
090E778D22AA863A00CD99F5 /* PeopleNearbyIconNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleNearbyIconNode.swift; sourceTree = "<group>"; };
|
||||
0910B0EC21FA178C00F8F87D /* WallpaperPreviewMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPreviewMedia.swift; sourceTree = "<group>"; };
|
||||
0910B0EE21FA532D00F8F87D /* WallpaperResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperResources.swift; sourceTree = "<group>"; };
|
||||
0910B0F021FB3DE100F8F87D /* WallpaperPatternPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPatternPanelNode.swift; sourceTree = "<group>"; };
|
||||
@ -2544,6 +2554,16 @@
|
||||
name = "Language Suggestion";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
090E778422A9B94700CD99F5 /* People Nearby */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
090E778522A9B95A00CD99F5 /* PeopleNearbyController.swift */,
|
||||
090E778722A9B96000CD99F5 /* PeopleNearbyHeaderItem.swift */,
|
||||
090E778D22AA863A00CD99F5 /* PeopleNearbyIconNode.swift */,
|
||||
);
|
||||
name = "People Nearby";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0919546D229458E900E11046 /* Animated Stickers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2568,6 +2588,7 @@
|
||||
09310D13213BC5DE0020033A /* Animations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
090E778B22AA842200CD99F5 /* anim_success.json */,
|
||||
09FFBCCF227B7F9000C33B4B /* anim_archiveswipe.json */,
|
||||
094735182277483B00EA2312 /* anim_infotip.json */,
|
||||
094735072275D72000EA2312 /* anim_archive.json */,
|
||||
@ -4482,6 +4503,7 @@
|
||||
D0E1199D229809B6008CAE3A /* ChannelDiscussionGroupActionSheetItem.swift */,
|
||||
D050A463229C052A0044F11A /* ChannelDiscussionGroupSetupSearchItem.swift */,
|
||||
D050A465229C06460044F11A /* ChannelDiscussionGroupSearchContainerNode.swift */,
|
||||
090E778922A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift */,
|
||||
);
|
||||
name = "Peer Info";
|
||||
sourceTree = "<group>";
|
||||
@ -4669,6 +4691,7 @@
|
||||
0941A99E210B053300EBE194 /* Open In */,
|
||||
09F215982263E61400AEDF6D /* Passcode */,
|
||||
09B4EE5721A82F5900847FA6 /* Permissions */,
|
||||
090E778422A9B94700CD99F5 /* People Nearby */,
|
||||
);
|
||||
name = Controllers;
|
||||
sourceTree = "<group>";
|
||||
@ -5284,6 +5307,7 @@
|
||||
09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */,
|
||||
094735172275D72100EA2312 /* anim_pin.json in Resources */,
|
||||
094735112275D72100EA2312 /* anim_archive.json in Resources */,
|
||||
090E778C22AA842300CD99F5 /* anim_success.json in Resources */,
|
||||
D0EB42051F3143AB00838FE6 /* LegacyComponentsResources.bundle in Resources */,
|
||||
D0E9BAA21F056F4C00F079A4 /* stp_card_discover@3x.png in Resources */,
|
||||
D0E9BAB01F056F4C00F079A4 /* stp_card_mastercard@3x.png in Resources */,
|
||||
@ -5380,6 +5404,7 @@
|
||||
D0E1199A2297F9C6008CAE3A /* ChannelDiscussionGroupSetupController.swift in Sources */,
|
||||
D083491C209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift in Sources */,
|
||||
D0EC6CB31EB9F58800EBF1C3 /* shader.c in Sources */,
|
||||
090E778A22A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift in Sources */,
|
||||
D06E4C352134AE3C00088087 /* ThemeAutoNightSettingsController.swift in Sources */,
|
||||
D0F0AAE21EC20EF8005EE2A5 /* CallControllerStatusNode.swift in Sources */,
|
||||
D0EC6CB41EB9F58800EBF1C3 /* timing.c in Sources */,
|
||||
@ -5528,6 +5553,7 @@
|
||||
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
|
||||
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
|
||||
D0BE303220601FFC00FBE6D8 /* LocationBroadcastActionSheetItem.swift in Sources */,
|
||||
090E778E22AA863A00CD99F5 /* PeopleNearbyIconNode.swift in Sources */,
|
||||
D0EC6CF41EB9F58800EBF1C3 /* ManagedMediaId.swift in Sources */,
|
||||
09D968A3221F800A00B1458A /* ChatUploadingActivityContentNode.swift in Sources */,
|
||||
D0CFBB971FD8B0F700B65C0D /* ChatBubbleInstantVideoDecoration.swift in Sources */,
|
||||
@ -5536,6 +5562,7 @@
|
||||
D0E9BA521F0559DA00F079A4 /* STPImageLibrary.m in Sources */,
|
||||
D0EC6CF61EB9F58800EBF1C3 /* ChatContextResultManagedMediaId.swift in Sources */,
|
||||
D048B33B203C777500038D05 /* RenderedTotalUnreadCount.swift in Sources */,
|
||||
090E778622A9B95A00CD99F5 /* PeopleNearbyController.swift in Sources */,
|
||||
D04ECD721FFBF22B00DE9029 /* OpenUrl.swift in Sources */,
|
||||
D04B4D661EEA993A00711AF6 /* LegacyLocationController.swift in Sources */,
|
||||
D056CD7A1FF3CC2A00880D28 /* ListMessagePlaybackOverlayNode.swift in Sources */,
|
||||
@ -6387,6 +6414,7 @@
|
||||
D0EC6E801EB9F58900EBF1C3 /* ChangePhoneNumberCodeController.swift in Sources */,
|
||||
D09E637C1F0E7C28003444CD /* SharedMediaPlayer.swift in Sources */,
|
||||
D0EC6E811EB9F58900EBF1C3 /* NotificationContainerController.swift in Sources */,
|
||||
090E778822A9B96100CD99F5 /* PeopleNearbyHeaderItem.swift in Sources */,
|
||||
D0754D271EEE10C800884F6E /* BotCheckoutController.swift in Sources */,
|
||||
D053DADA201A4C4400993D32 /* ChatTextInputAttributes.swift in Sources */,
|
||||
0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */,
|
||||
|
||||
@ -6,6 +6,7 @@ import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
||||
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
|
||||
private let savedMessagesIcon = UIImage(bundleImageName: "Avatar/SavedMessagesIcon")?.precomposed()
|
||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||
|
||||
@ -101,6 +102,7 @@ private enum AvatarNodeIcon: Equatable {
|
||||
case savedMessagesIcon
|
||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||
case editAvatarIcon
|
||||
case deletedIcon
|
||||
}
|
||||
|
||||
public enum AvatarNodeImageOverride: Equatable {
|
||||
@ -109,6 +111,7 @@ public enum AvatarNodeImageOverride: Equatable {
|
||||
case savedMessagesIcon
|
||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||
case editAvatarIcon
|
||||
case deletedIcon
|
||||
}
|
||||
|
||||
public enum AvatarNodeColorOverride {
|
||||
@ -291,6 +294,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .editAvatarIcon:
|
||||
representation = peer?.smallProfileImage
|
||||
icon = .editAvatarIcon
|
||||
case .deletedIcon:
|
||||
representation = nil
|
||||
icon = .deletedIcon
|
||||
}
|
||||
} else if peer?.restrictionText == nil {
|
||||
representation = peer?.smallProfileImage
|
||||
@ -411,7 +417,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
let colorsArray: NSArray
|
||||
var iconColor = UIColor.white
|
||||
if let parameters = parameters as? AvatarNodeParameters, parameters.icon != .none {
|
||||
if case .savedMessagesIcon = parameters.icon {
|
||||
if case .deletedIcon = parameters.icon {
|
||||
colorsArray = grayscaleColors
|
||||
} else if case .savedMessagesIcon = parameters.icon {
|
||||
colorsArray = savedMessagesColors
|
||||
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme {
|
||||
colorsArray = [theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor]
|
||||
@ -444,7 +452,16 @@ public final class AvatarNode: ASDisplayNode {
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
if let parameters = parameters as? AvatarNodeParameters {
|
||||
if case .savedMessagesIcon = parameters.icon {
|
||||
if case .deletedIcon = parameters.icon {
|
||||
let factor = bounds.size.width / 60.0
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
context.scaleBy(x: factor, y: -factor)
|
||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
|
||||
if let deletedIcon = deletedIcon {
|
||||
context.draw(deletedIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - deletedIcon.size.width) / 2.0), y: floor((bounds.size.height - deletedIcon.size.height) / 2.0)), size: deletedIcon.size))
|
||||
}
|
||||
} else if case .savedMessagesIcon = parameters.icon {
|
||||
let factor = bounds.size.width / 60.0
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
context.scaleBy(x: factor, y: -factor)
|
||||
|
||||
@ -217,7 +217,7 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation {
|
||||
|
||||
final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation {
|
||||
var uniqueId: String {
|
||||
return "animated-sticker"
|
||||
return "animated-sticker-v1"
|
||||
}
|
||||
|
||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||
|
||||
@ -6,77 +6,6 @@ import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private final class CallRatingContentActionNode: HighlightableButtonNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
let action: TextAlertAction
|
||||
|
||||
init(theme: AlertControllerTheme, action: TextAlertAction) {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.alpha = 0.0
|
||||
|
||||
self.action = action
|
||||
|
||||
super.init()
|
||||
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
|
||||
self.highligthedChanged = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundNode.alpha = 1.0
|
||||
} else if !strongSelf.backgroundNode.alpha.isZero {
|
||||
strongSelf.backgroundNode.alpha = 0.0
|
||||
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.backgroundNode.backgroundColor = theme.highlightedItemColor
|
||||
|
||||
var font = Font.regular(17.0)
|
||||
var color = theme.accentColor
|
||||
switch self.action.type {
|
||||
case .defaultAction, .genericAction:
|
||||
break
|
||||
case .destructiveAction:
|
||||
color = theme.destructiveColor
|
||||
}
|
||||
switch self.action.type {
|
||||
case .defaultAction:
|
||||
font = Font.semibold(17.0)
|
||||
case .destructiveAction, .genericAction:
|
||||
break
|
||||
}
|
||||
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func pressed() {
|
||||
self.action.action()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.backgroundNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
private final class CallRatingAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let apply: (Int) -> Void
|
||||
@ -87,7 +16,7 @@ private final class CallRatingAlertContentNode: AlertContentNode {
|
||||
private let starNodes: [ASButtonNode]
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [CallRatingContentActionNode]
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
@ -114,8 +43,8 @@ private final class CallRatingAlertContentNode: AlertContentNode {
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> CallRatingContentActionNode in
|
||||
return CallRatingContentActionNode(theme: theme, action: action)
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
|
||||
@ -8,139 +8,6 @@ import TelegramCore
|
||||
|
||||
private func generateIconImage(theme: AlertControllerTheme) -> UIImage? {
|
||||
return UIImage(bundleImageName: "Call List/AlertIcon")
|
||||
// return generateImage(frame.size, contextGenerator: { size, context in
|
||||
// let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
// context.clear(bounds)
|
||||
//
|
||||
// let relativeFrame = CGRect(x: -frame.minX, y: frame.minY - background.size.height + frame.size.height
|
||||
// , width: background.size.width, height: background.size.height)
|
||||
//
|
||||
// context.beginPath()
|
||||
// context.addEllipse(in: bounds)
|
||||
// context.clip()
|
||||
//
|
||||
// context.setAlpha(0.8)
|
||||
// context.draw(background.foregroundImage.cgImage!, in: relativeFrame)
|
||||
//
|
||||
// if highlighted {
|
||||
// context.setFillColor(UIColor(white: 1.0, alpha: 0.65).cgColor)
|
||||
// context.fillEllipse(in: bounds)
|
||||
// }
|
||||
//
|
||||
// context.setAlpha(1.0)
|
||||
// context.textMatrix = .identity
|
||||
//
|
||||
// let titleFont: UIFont
|
||||
// let subtitleFont: UIFont
|
||||
// let titleOffset: CGFloat
|
||||
// let subtitleOffset: CGFloat
|
||||
// if size.width > 80.0 {
|
||||
// titleFont = largeTitleFont
|
||||
// subtitleFont = largeSubtitleFont
|
||||
// if subtitle.isEmpty {
|
||||
// titleOffset = -18.0
|
||||
// } else {
|
||||
// titleOffset = -11.0
|
||||
// }
|
||||
// subtitleOffset = -54.0
|
||||
// } else {
|
||||
// titleFont = regularTitleFont
|
||||
// subtitleFont = regularSubtitleFont
|
||||
// if subtitle.isEmpty {
|
||||
// titleOffset = -17.0
|
||||
// } else {
|
||||
// titleOffset = -10.0
|
||||
// }
|
||||
// subtitleOffset = -48.0
|
||||
// }
|
||||
//
|
||||
// let titlePath = CGMutablePath()
|
||||
// titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: titleOffset))
|
||||
// let titleString = NSAttributedString(string: title, font: titleFont, textColor: .white, paragraphAlignment: .center)
|
||||
// let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
|
||||
// let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
|
||||
// CTFrameDraw(titleFrame, context)
|
||||
//
|
||||
// if !subtitle.isEmpty {
|
||||
// let subtitlePath = CGMutablePath()
|
||||
// subtitlePath.addRect(bounds.offsetBy(dx: 0.0, dy: subtitleOffset))
|
||||
// let subtitleString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: .white, paragraphAlignment: .center)
|
||||
// let subtitleFramesetter = CTFramesetterCreateWithAttributedString(subtitleString as CFAttributedString)
|
||||
// let subtitleFrame = CTFramesetterCreateFrame(subtitleFramesetter, CFRangeMake(0, subtitleString.length), subtitlePath, nil)
|
||||
// CTFrameDraw(subtitleFrame, context)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
private final class CallSuggestTabContentActionNode: HighlightableButtonNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
let action: TextAlertAction
|
||||
|
||||
init(theme: AlertControllerTheme, action: TextAlertAction) {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.alpha = 0.0
|
||||
|
||||
self.action = action
|
||||
|
||||
super.init()
|
||||
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
|
||||
self.highligthedChanged = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundNode.alpha = 1.0
|
||||
} else if !strongSelf.backgroundNode.alpha.isZero {
|
||||
strongSelf.backgroundNode.alpha = 0.0
|
||||
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.backgroundNode.backgroundColor = theme.highlightedItemColor
|
||||
|
||||
var font = Font.regular(17.0)
|
||||
var color = theme.accentColor
|
||||
switch self.action.type {
|
||||
case .defaultAction, .genericAction:
|
||||
break
|
||||
case .destructiveAction:
|
||||
color = theme.destructiveColor
|
||||
}
|
||||
switch self.action.type {
|
||||
case .defaultAction:
|
||||
font = Font.semibold(17.0)
|
||||
case .destructiveAction, .genericAction:
|
||||
break
|
||||
}
|
||||
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func pressed() {
|
||||
self.action.action()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.backgroundNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
private final class CallSuggestTabAlertContentNode: AlertContentNode {
|
||||
@ -151,7 +18,7 @@ private final class CallSuggestTabAlertContentNode: AlertContentNode {
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [CallSuggestTabContentActionNode]
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
@ -174,8 +41,8 @@ private final class CallSuggestTabAlertContentNode: AlertContentNode {
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> CallSuggestTabContentActionNode in
|
||||
return CallSuggestTabContentActionNode(theme: theme, action: action)
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
|
||||
@ -8,11 +8,13 @@ import TelegramCore
|
||||
private final class ChannelAdminControllerArguments {
|
||||
let account: Account
|
||||
let toggleRight: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
||||
let transferOwnership: () -> Void
|
||||
let dismissAdmin: () -> Void
|
||||
|
||||
init(account: Account, toggleRight: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, dismissAdmin: @escaping () -> Void) {
|
||||
init(account: Account, toggleRight: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, dismissAdmin: @escaping () -> Void) {
|
||||
self.account = account
|
||||
self.toggleRight = toggleRight
|
||||
self.transferOwnership = transferOwnership
|
||||
self.dismissAdmin = dismissAdmin
|
||||
}
|
||||
}
|
||||
@ -20,6 +22,7 @@ private final class ChannelAdminControllerArguments {
|
||||
private enum ChannelAdminSection: Int32 {
|
||||
case info
|
||||
case rights
|
||||
case transfer
|
||||
case dismiss
|
||||
}
|
||||
|
||||
@ -28,6 +31,7 @@ private enum ChannelAdminEntryStableId: Hashable {
|
||||
case rightsTitle
|
||||
case right(TelegramChatAdminRightsFlags)
|
||||
case addAdminsInfo
|
||||
case transfer
|
||||
case dismiss
|
||||
|
||||
var hashValue: Int {
|
||||
@ -40,6 +44,8 @@ private enum ChannelAdminEntryStableId: Hashable {
|
||||
return 2
|
||||
case .dismiss:
|
||||
return 3
|
||||
case .transfer:
|
||||
return 4
|
||||
case let .right(flags):
|
||||
return flags.rawValue.hashValue
|
||||
}
|
||||
@ -71,6 +77,12 @@ private enum ChannelAdminEntryStableId: Hashable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .transfer:
|
||||
if case .transfer = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .dismiss:
|
||||
if case .dismiss = rhs {
|
||||
return true
|
||||
@ -86,6 +98,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
case rightsTitle(PresentationTheme, String)
|
||||
case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool)
|
||||
case addAdminsInfo(PresentationTheme, String)
|
||||
case transfer(PresentationTheme, String)
|
||||
case dismiss(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
@ -94,6 +107,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
return ChannelAdminSection.info.rawValue
|
||||
case .rightsTitle, .rightItem, .addAdminsInfo:
|
||||
return ChannelAdminSection.rights.rawValue
|
||||
case .transfer:
|
||||
return ChannelAdminSection.transfer.rawValue
|
||||
case .dismiss:
|
||||
return ChannelAdminSection.dismiss.rawValue
|
||||
}
|
||||
@ -109,6 +124,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
return .right(right)
|
||||
case .addAdminsInfo:
|
||||
return .addAdminsInfo
|
||||
case .transfer:
|
||||
return .transfer
|
||||
case .dismiss:
|
||||
return .dismiss
|
||||
}
|
||||
@ -177,6 +194,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .transfer(lhsTheme, lhsText):
|
||||
if case let .transfer(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .dismiss(lhsTheme, lhsText):
|
||||
if case let .dismiss(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -218,6 +241,13 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .transfer:
|
||||
switch rhs {
|
||||
case .info, .rightsTitle, .rightItem, .addAdminsInfo, .transfer:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case .dismiss:
|
||||
return false
|
||||
}
|
||||
@ -237,8 +267,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .addAdminsInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||
case let .transfer(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.transferOwnership()
|
||||
}, tag: nil)
|
||||
case let .dismiss(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.dismissAdmin()
|
||||
}, tag: nil)
|
||||
}
|
||||
@ -421,6 +455,10 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
if accountUserRightsFlags.contains(.canAddAdmins) {
|
||||
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
||||
}
|
||||
|
||||
if channel.flags.contains(.isCreator) && currentRightsFlags.contains(.canAddAdmins) {
|
||||
entries.append(.transfer(presentationData.theme, isGroup ? presentationData.strings.Group_EditAdmin_TransferOwnership : presentationData.strings.Channel_EditAdmin_TransferOwnership))
|
||||
}
|
||||
|
||||
if let initialParticipant = initialParticipant, case let .member(participant) = initialParticipant, let adminInfo = participant.adminInfo, !adminInfo.rights.flags.isEmpty {
|
||||
var canDismiss = false
|
||||
@ -488,8 +526,11 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
||||
}
|
||||
|
||||
if group.role == .creator && currentRightsFlags.contains(.canAddAdmins) {
|
||||
entries.append(.transfer(presentationData.theme, presentationData.strings.Group_EditAdmin_TransferOwnership))
|
||||
}
|
||||
|
||||
if let initialParticipant = initialParticipant, case let .member(participant) = initialParticipant, let adminInfo = participant.adminInfo, !adminInfo.rights.flags.isEmpty {
|
||||
let canDismiss = true
|
||||
entries.append(.dismiss(presentationData.theme, presentationData.strings.Channel_Moderator_AccessLevelRevoke))
|
||||
}
|
||||
}
|
||||
@ -497,7 +538,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||
return entries
|
||||
}
|
||||
|
||||
public func channelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatAdminRights) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController {
|
||||
public func channelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatAdminRights) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, transferedOwnership: @escaping (PeerId) -> Void) -> ViewController {
|
||||
let statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: ChannelAdminControllerState())
|
||||
let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in
|
||||
@ -509,9 +550,18 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
let updateRightsDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updateRightsDisposable)
|
||||
|
||||
let transferOwnershipDisposable = MetaDisposable()
|
||||
actionsDisposable.add(transferOwnershipDisposable)
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
|
||||
let actualPeerId = Atomic<PeerId>(value: peerId)
|
||||
let upgradedToSupergroupImpl: (PeerId, @escaping () -> Void) -> Void = { peerId, completion in
|
||||
let _ = actualPeerId.swap(peerId)
|
||||
upgradedToSupergroup(peerId, completion)
|
||||
}
|
||||
|
||||
let arguments = ChannelAdminControllerArguments(account: context.account, toggleRight: { right, flags in
|
||||
updateState { current in
|
||||
var updated = flags
|
||||
@ -522,6 +572,65 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
}
|
||||
return current.withUpdatedUpdatedFlags(updated)
|
||||
}
|
||||
}, transferOwnership: {
|
||||
updateState { current in
|
||||
return current.withUpdatedUpdating(true)
|
||||
}
|
||||
|
||||
let _ = (context.account.postbox.transaction { transaction -> (peer: Peer?, member: Peer?) in
|
||||
return (peer: transaction.getPeer(peerId), member: transaction.getPeer(adminId))
|
||||
} |> deliverOnMainQueue).start(next: { peer, member in
|
||||
guard let peer = peer, let member = member as? TelegramUser else {
|
||||
return
|
||||
}
|
||||
|
||||
var signal: Signal<Never, ChannelOwnershipTransferError> = .complete()
|
||||
if let channel = peer as? TelegramChannel {
|
||||
signal = updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: channel.id, memberId: adminId, password: nil)
|
||||
} else if let _ = peer as? TelegramGroup {
|
||||
signal = convertGroupToSupergroup(account: context.account, peerId: peerId)
|
||||
|> map(Optional.init)
|
||||
|> mapError { _ in ChannelOwnershipTransferError.generic }
|
||||
|> mapToSignal { upgradedPeerId -> Signal<Never, ChannelOwnershipTransferError> in
|
||||
guard let upgradedPeerId = upgradedPeerId else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
upgradedToSupergroupImpl(upgradedPeerId, {})
|
||||
|
||||
return updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: upgradedPeerId, memberId: adminId, password: nil)
|
||||
}
|
||||
}
|
||||
|
||||
transferOwnershipDisposable.set((signal |> deliverOnMainQueue).start(error: { error in
|
||||
updateState { current in
|
||||
return current.withUpdatedUpdating(false)
|
||||
}
|
||||
|
||||
let currentPeerId = actualPeerId.with { $0 }
|
||||
let channel: Signal<Peer?, NoError>
|
||||
if currentPeerId == peerId {
|
||||
channel = .single(peer)
|
||||
} else {
|
||||
channel = context.account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(currentPeerId)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (channel |> deliverOnMainQueue).start(next: { channel in
|
||||
guard let channel = channel as? TelegramChannel else {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = channelOwnershipTransferController(context: context, channel: channel, member: member, initialError: error, present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
}, completion: {
|
||||
dismissImpl?()
|
||||
transferedOwnership(member.id)
|
||||
})
|
||||
presentControllerImpl?(controller, nil)
|
||||
})
|
||||
}))
|
||||
})
|
||||
}, dismissAdmin: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||
|
||||
@ -199,12 +199,16 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
||||
let action: (() -> Void)?
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
peerText = strings.Channel_Management_LabelCreator
|
||||
peerText = strings.Channel_Management_LabelOwner
|
||||
action = nil
|
||||
case let .member(_, _, adminInfo, _):
|
||||
if let adminInfo = adminInfo {
|
||||
if let peer = participant.peers[adminInfo.promotedBy] {
|
||||
peerText = strings.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||
if peer.id == participant.peer.id {
|
||||
peerText = strings.Channel_Management_LabelAdministrator
|
||||
} else {
|
||||
peerText = strings.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||
}
|
||||
} else {
|
||||
peerText = ""
|
||||
}
|
||||
@ -491,6 +495,18 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
||||
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
||||
}
|
||||
|
||||
let transferedOwnership: (PeerId) -> Void = { memberId in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (context.account.postbox.transaction { transaction -> (channel: Peer?, user: Peer?) in
|
||||
return (channel: transaction.getPeer(peerId), user: transaction.getPeer(memberId))
|
||||
} |> deliverOnMainQueue).start(next: { peer, user in
|
||||
guard let peer = peer, let user = user else {
|
||||
return
|
||||
}
|
||||
presentControllerImpl?(UndoOverlayController(context: context, content: .succeed(text: presentationData.strings.Channel_OwnershipTransfer_TransferCompleted(peer.displayTitle, user.displayTitle).0), elevatedLayout: false, action: { _ in }), nil)
|
||||
})
|
||||
}
|
||||
|
||||
let peerView = Promise<PeerView>()
|
||||
peerView.set(context.account.viewTracker.peerView(peerId))
|
||||
|
||||
@ -564,7 +580,7 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
||||
}
|
||||
}
|
||||
presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in
|
||||
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
dismissController = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
@ -576,7 +592,7 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
||||
})
|
||||
}, openAdmin: { participant in
|
||||
presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
|
||||
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
|
||||
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
|
||||
@ -701,7 +717,7 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
||||
updateState { state in
|
||||
return state.withUpdatedSearchingMembers(false)
|
||||
}
|
||||
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
}, present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
|
||||
@ -396,7 +396,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
contactsController?.dismiss()
|
||||
|
||||
presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in
|
||||
}, upgradedToSupergroup: { _, f in f () }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, upgradedToSupergroup: { _, f in f () }, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})]), nil)
|
||||
} else {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
|
||||
@ -332,7 +332,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
return state
|
||||
}
|
||||
present(channelAdminController(context: context, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
||||
}, upgradedToSupergroup: { _, f in f() }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, upgradedToSupergroup: { _, f in f() }, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, restrictPeer: { participant in
|
||||
updateState { state in
|
||||
var state = state
|
||||
@ -469,7 +469,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
if case .searchMembers = mode {
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -675,13 +675,13 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
var enabled = true
|
||||
if case .banAndPromoteActions = mode {
|
||||
if case .creator = participant.participant {
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
} else if case .searchMembers = mode {
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
case let .member(member):
|
||||
if member.adminInfo != nil {
|
||||
label = themeAndStrings.1.Channel_Management_LabelEditor
|
||||
@ -712,11 +712,15 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
case .searchAdmins:
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
case let .member(_, _, adminInfo, _):
|
||||
if let adminInfo = adminInfo {
|
||||
if let peer = participant.peers[adminInfo.promotedBy] {
|
||||
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||
if peer.id == participant.peer.id {
|
||||
label = themeAndStrings.1.Channel_Management_LabelAdministrator
|
||||
} else {
|
||||
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -774,7 +778,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
var enabled = true
|
||||
if case .banAndPromoteActions = mode {
|
||||
if case .creator = participant.participant {
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
@ -947,13 +951,13 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
var enabled = true
|
||||
if case .banAndPromoteActions = mode {
|
||||
if case .creator = participant.participant {
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
} else if case .searchMembers = mode {
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
case let .member(member):
|
||||
if member.adminInfo != nil {
|
||||
label = themeAndStrings.1.Channel_Management_LabelEditor
|
||||
@ -980,11 +984,15 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
case .searchAdmins:
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
case let .member(_, _, adminInfo, _):
|
||||
if let adminInfo = adminInfo {
|
||||
if let peer = participant.peers[adminInfo.promotedBy] {
|
||||
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||
if peer.id == participant.peer.id {
|
||||
label = themeAndStrings.1.Channel_Management_LabelAdministrator
|
||||
} else {
|
||||
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1042,7 +1050,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
var enabled = true
|
||||
if case .banAndPromoteActions = mode {
|
||||
if case .creator = participant.participant {
|
||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
||||
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if case .creator = participant {
|
||||
label = strongSelf.presentationData.strings.Channel_Management_LabelCreator
|
||||
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
@ -263,7 +263,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if case .creator = participant.participant {
|
||||
label = strongSelf.presentationData.strings.Channel_Management_LabelCreator
|
||||
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
481
TelegramUI/ChannelOwnershipTransferController.swift
Normal file
481
TelegramUI/ChannelOwnershipTransferController.swift
Normal file
@ -0,0 +1,481 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private final class ChannelOwnershipTransferPasswordFieldNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private var theme: PresentationTheme
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textInputNode: TextFieldNode
|
||||
private let placeholderNode: ASTextNode
|
||||
|
||||
var complete: (() -> Void)?
|
||||
var textChanged: ((String) -> Void)?
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 22.0, bottom: 15.0, right: 22.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 5.0, left: 11.0, bottom: 5.0, right: 11.0)
|
||||
|
||||
var password: String {
|
||||
get {
|
||||
return self.textInputNode.textField.text ?? ""
|
||||
}
|
||||
set {
|
||||
self.textInputNode.textField.text = newValue
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
var placeholder: String = "" {
|
||||
didSet {
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel)
|
||||
|
||||
self.textInputNode = TextFieldNode()
|
||||
|
||||
self.placeholderNode = ASTextNode()
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
self.placeholderNode.displaysAsynchronously = false
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textInputNode)
|
||||
self.addSubnode(self.placeholderNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.textInputNode.textField.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(14.0), NSAttributedStringKey.foregroundColor.rawValue: self.theme.actionSheet.inputTextColor]
|
||||
self.textInputNode.textField.font = Font.regular(14.0)
|
||||
self.textInputNode.textField.textColor = self.theme.list.itemPrimaryTextColor
|
||||
self.textInputNode.textField.isSecureTextEntry = true
|
||||
self.textInputNode.textField.returnKeyType = .done
|
||||
self.textInputNode.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.textInputNode.clipsToBounds = true
|
||||
self.textInputNode.textField.delegate = self
|
||||
self.textInputNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
|
||||
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel)
|
||||
self.textInputNode.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.textInputNode.textField.textColor = theme.list.itemPrimaryTextColor
|
||||
self.textInputNode.textField.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(14.0), NSAttributedStringKey.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(14.0), textColor: theme.actionSheet.inputPlaceholderColor)
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
|
||||
let textFieldHeight: CGFloat = 30.0
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
|
||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.textInputNode.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func deactivateInput() {
|
||||
self.textInputNode.resignFirstResponder()
|
||||
}
|
||||
|
||||
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.textChanged?(editableTextNode.textView.text)
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
}
|
||||
|
||||
@objc func textFieldTextChanged(_ textField: UITextField) {
|
||||
let text = textField.text ?? ""
|
||||
self.textChanged?(text)
|
||||
self.placeholderNode.isHidden = !text.isEmpty
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
if string == "\n" {
|
||||
self.complete?()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
let inputFieldNode: ChannelOwnershipTransferPasswordFieldNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
var complete: (() -> Void)? {
|
||||
didSet {
|
||||
self.inputFieldNode.complete = self.complete
|
||||
}
|
||||
}
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) {
|
||||
self.strings = strings
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
|
||||
self.inputFieldNode = ChannelOwnershipTransferPasswordFieldNode(theme: ptheme, placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder)
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.inputFieldNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
self.actionNodes.last?.actionEnabled = false
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.inputFieldNode.textChanged = { [weak self] text in
|
||||
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
|
||||
lastNode.actionEnabled = !text.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func dismissInput() {
|
||||
self.inputFieldNode.deactivateInput()
|
||||
}
|
||||
|
||||
var password: String {
|
||||
return self.inputFieldNode.password
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPassword, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPasswordText, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||
|
||||
let hadValidLayout = self.validLayout != nil
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let titleSize = self.titleNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 4.0
|
||||
|
||||
let textSize = self.textNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 6.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||
contentWidth = max(contentWidth, 234.0)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultWidth = contentWidth + insets.left + insets.right
|
||||
|
||||
let inputFieldWidth = resultWidth
|
||||
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
|
||||
let inputHeight = inputFieldHeight
|
||||
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
||||
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
||||
|
||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + actionsHeight + inputHeight + insets.top + insets.bottom)
|
||||
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if !hadValidLayout {
|
||||
self.inputFieldNode.activateInput()
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.inputFieldNode.layer.addShakeAnimation()
|
||||
self.hapticFeedback.error()
|
||||
}
|
||||
}
|
||||
|
||||
private func commitChannelOwnershipTransferController(context: AccountContext, channel: TelegramChannel, member: TelegramUser, completion: @escaping () -> Void) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var proceedImpl: (() -> Void)?
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationTheme: presentationData.theme), ptheme: presentationData.theme, strings: presentationData.strings, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: {
|
||||
proceedImpl?()
|
||||
})])
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), contentNode: contentNode)
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
|
||||
})
|
||||
controller.dismissed = {
|
||||
presentationDataDisposable.dispose()
|
||||
disposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller, weak contentNode] in
|
||||
contentNode?.dismissInput()
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
proceedImpl = { [weak contentNode] in
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
disposable.set((updateChannelOwnership(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, channelId: channel.id, memberId: member.id, password: contentNode.password) |> deliverOnMainQueue).start(error: { [weak contentNode] error in
|
||||
contentNode?.animateError()
|
||||
}, completed: {
|
||||
dismissImpl?()
|
||||
completion()
|
||||
}))
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
private func confirmChannelOwnershipTransferController(context: AccountContext, channel: TelegramChannel, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping () -> Void) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
|
||||
var title: String
|
||||
var text: String
|
||||
if case .group = channel.info {
|
||||
title = presentationData.strings.Group_OwnershipTransfer_Title
|
||||
text = presentationData.strings.Group_OwnershipTransfer_DescriptionInfo(channel.displayTitle, member.displayTitle).0
|
||||
} else {
|
||||
title = presentationData.strings.Channel_OwnershipTransfer_Title
|
||||
text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(channel.displayTitle, member.displayTitle).0
|
||||
}
|
||||
|
||||
let attributedTitle = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_OwnershipTransfer_ChangeOwner, action: {
|
||||
dismissImpl?()
|
||||
present(commitChannelOwnershipTransferController(context: context, channel: channel, member: member, completion: completion), nil)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})], actionLayout: .vertical)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
func channelOwnershipTransferController(context: AccountContext, channel: TelegramChannel, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping () -> Void) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
|
||||
var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
|
||||
var text = presentationData.strings.OwnershipTransfer_SecurityRequirements
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var actions: [TextAlertAction] = []
|
||||
|
||||
switch initialError {
|
||||
case .requestPassword:
|
||||
return confirmChannelOwnershipTransferController(context: context, channel: channel, member: member, present: present, completion: completion)
|
||||
case .twoStepAuthTooFresh, .authSessionTooFresh:
|
||||
text = text + presentationData.strings.OwnershipTransfer_ComeBackLater
|
||||
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
case .twoStepAuthMissing:
|
||||
actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: {
|
||||
let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
|
||||
if shouldDismiss {
|
||||
controller.dismiss()
|
||||
}
|
||||
})
|
||||
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
default:
|
||||
title = nil
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
}
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||
|
||||
let controller = richTextAlertController(context: context, title: title, text: attributedText, actions: actions)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
return controller
|
||||
}
|
||||
@ -1449,7 +1449,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
if let strongSelf = self {
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount)
|
||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, peer: peer)
|
||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: peer.isDeleted ? .deletedIcon : .none)
|
||||
}
|
||||
if strongSelf.peerView === peerView {
|
||||
return
|
||||
|
||||
@ -476,6 +476,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if peer.id == item.context.account.peerId {
|
||||
overrideImage = .savedMessagesIcon
|
||||
} else if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
self.avatarNode.setPeer(account: item.context.account, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads)
|
||||
}
|
||||
|
||||
@ -6,77 +6,6 @@ import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private final class ChatMessageActionUrlAuthContentActionNode: HighlightableButtonNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
let action: TextAlertAction
|
||||
|
||||
init(theme: AlertControllerTheme, action: TextAlertAction) {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.alpha = 0.0
|
||||
|
||||
self.action = action
|
||||
|
||||
super.init()
|
||||
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
|
||||
self.highligthedChanged = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundNode.alpha = 1.0
|
||||
} else if !strongSelf.backgroundNode.alpha.isZero {
|
||||
strongSelf.backgroundNode.alpha = 0.0
|
||||
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.backgroundNode.backgroundColor = theme.highlightedItemColor
|
||||
|
||||
var font = Font.regular(17.0)
|
||||
var color = theme.accentColor
|
||||
switch self.action.type {
|
||||
case .defaultAction, .genericAction:
|
||||
break
|
||||
case .destructiveAction:
|
||||
color = theme.destructiveColor
|
||||
}
|
||||
switch self.action.type {
|
||||
case .defaultAction:
|
||||
font = Font.semibold(17.0)
|
||||
case .destructiveAction, .genericAction:
|
||||
break
|
||||
}
|
||||
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func pressed() {
|
||||
self.action.action()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.backgroundNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
private let textFont = Font.regular(13.0)
|
||||
private let boldTextFont = Font.semibold(13.0)
|
||||
|
||||
@ -99,7 +28,7 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
|
||||
private let allowWriteLabelNode: ASTextNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [ChatMessageActionUrlAuthContentActionNode]
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
@ -154,8 +83,8 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> ChatMessageActionUrlAuthContentActionNode in
|
||||
return ChatMessageActionUrlAuthContentActionNode(theme: theme, action: action)
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
|
||||
@ -221,7 +221,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
|
||||
let peerText: String
|
||||
switch participant.participant {
|
||||
case .creator:
|
||||
peerText = strings.Channel_Management_LabelCreator
|
||||
peerText = strings.Channel_Management_LabelOwner
|
||||
case .member:
|
||||
peerText = strings.ChatAdmins_AdminLabel.capitalized
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
|
||||
private let accessoryButtonsWidth: CGFloat = 10.0
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
@ -81,7 +80,6 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
let accessoryButtonsWidth = self.accessoryButtonsWidth
|
||||
|
||||
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
@ -92,7 +90,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: backgroundFrame.size.height)))
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
@ -122,9 +120,8 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
let accessoryButtonsWidth = self.accessoryButtonsWidth
|
||||
|
||||
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||
|
||||
return min(61.0, max(33.0, unboundTextFieldHeight))
|
||||
}
|
||||
@ -146,91 +143,6 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class ChatTextLinkEditContentActionNode: HighlightableButtonNode {
|
||||
private var theme: AlertControllerTheme
|
||||
let action: TextAlertAction
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
init(theme: AlertControllerTheme, action: TextAlertAction) {
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
|
||||
self.highligthedChanged = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundNode.alpha = 1.0
|
||||
} else if !strongSelf.backgroundNode.alpha.isZero {
|
||||
strongSelf.backgroundNode.alpha = 0.0
|
||||
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
var actionEnabled: Bool = true {
|
||||
didSet {
|
||||
self.isUserInteractionEnabled = self.actionEnabled
|
||||
self.updateTitle()
|
||||
}
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.theme = theme
|
||||
self.backgroundNode.backgroundColor = theme.highlightedItemColor
|
||||
self.updateTitle()
|
||||
}
|
||||
|
||||
private func updateTitle() {
|
||||
var font = Font.regular(17.0)
|
||||
var color: UIColor
|
||||
switch self.action.type {
|
||||
case .defaultAction, .genericAction:
|
||||
color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor
|
||||
case .destructiveAction:
|
||||
color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor
|
||||
}
|
||||
switch self.action.type {
|
||||
case .defaultAction:
|
||||
font = Font.semibold(17.0)
|
||||
case .destructiveAction, .genericAction:
|
||||
break
|
||||
}
|
||||
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func pressed() {
|
||||
self.action.action()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.backgroundNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let text: String
|
||||
@ -240,7 +152,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
let inputFieldNode: ChatTextLinkEditInputFieldNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [ChatTextLinkEditContentActionNode]
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
@ -274,8 +186,8 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> ChatTextLinkEditContentActionNode in
|
||||
return ChatTextLinkEditContentActionNode(theme: theme, action: action)
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
|
||||
@ -44,7 +44,7 @@ extension ActionSheetController {
|
||||
public extension AlertControllerTheme {
|
||||
convenience init(presentationTheme: PresentationTheme) {
|
||||
let actionSheet = presentationTheme.actionSheet
|
||||
self.init(backgroundColor: actionSheet.opaqueItemBackgroundColor, separatorColor: actionSheet.opaqueItemSeparatorColor, highlightedItemColor: actionSheet.opaqueItemHighlightedBackgroundColor, primaryColor: actionSheet.primaryTextColor, secondaryColor: actionSheet.secondaryTextColor, accentColor: actionSheet.controlAccentColor, destructiveColor: actionSheet.destructiveActionTextColor, disabledColor: actionSheet.disabledActionTextColor)
|
||||
self.init(backgroundType: actionSheet.backgroundType == .light ? .light : .dark, backgroundColor: actionSheet.itemBackgroundColor, separatorColor: actionSheet.itemHighlightedBackgroundColor, highlightedItemColor: actionSheet.itemHighlightedBackgroundColor, primaryColor: actionSheet.primaryTextColor, secondaryColor: actionSheet.secondaryTextColor, accentColor: actionSheet.controlAccentColor, destructiveColor: actionSheet.destructiveActionTextColor, disabledColor: actionSheet.disabledActionTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -248,8 +248,8 @@ public class ContactsController: ViewController {
|
||||
|
||||
self.contactsNode.openPeopleNearby = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
//let controller = peopleNearbyController(context: strongSelf.context)
|
||||
//(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
let controller = peopleNearbyController(context: strongSelf.context)
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,10 +31,9 @@ final class ContactsControllerNode: ASDisplayNode {
|
||||
|
||||
var addNearbyImpl: (() -> Void)?
|
||||
var inviteImpl: (() -> Void)?
|
||||
//ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
|
||||
// addNearbyImpl?()
|
||||
//}),
|
||||
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
|
||||
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
|
||||
addNearbyImpl?()
|
||||
}), ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
|
||||
inviteImpl?()
|
||||
})]
|
||||
|
||||
|
||||
@ -625,6 +625,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if peer.id == item.account.peerId, case .generalSearch = item.peerMode {
|
||||
overrideImage = .savedMessagesIcon
|
||||
} else if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads)
|
||||
}
|
||||
|
||||
@ -408,7 +408,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
|
||||
dimColor: UIColor(white: 0.0, alpha: 0.4),
|
||||
backgroundType: .light,
|
||||
opaqueItemBackgroundColor: .white,
|
||||
itemBackgroundColor: UIColor(white: 1.0, alpha: 0.8),
|
||||
itemBackgroundColor: UIColor(white: 1.0, alpha: 0.87),
|
||||
opaqueItemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 1.0),
|
||||
itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7),
|
||||
standardActionTextColor: accentColor,
|
||||
|
||||
@ -59,7 +59,11 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
if chatPeer.id == context.account.peerId {
|
||||
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
|
||||
} else {
|
||||
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer)
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if chatPeer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: overrideImage)
|
||||
}
|
||||
|
||||
let text: (String, [(Int, NSRange)])
|
||||
|
||||
@ -1777,7 +1777,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
||||
presentControllerImpl?(channelAdminController(context: context, peerId: peerView.peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
||||
}, upgradedToSupergroup: { upgradedPeerId, f in
|
||||
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
||||
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
}, restrictPeer: { participant in
|
||||
let _ = (peerView.get()
|
||||
|
||||
@ -644,6 +644,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
}
|
||||
} else if case .editSettings = item.mode {
|
||||
overrideImage = AvatarNodeImageOverride.editAvatarIcon
|
||||
} else if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
|
||||
strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: peer, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads)
|
||||
|
||||
@ -709,7 +709,11 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNode {
|
||||
if item.peer.id == item.account.peerId, case .threatSelfAsSaved = item.aliasHandling {
|
||||
strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||
} else {
|
||||
strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: item.peer, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if item.peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||
}
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 50.0 + UIScreenPixel + UIScreenPixel))
|
||||
|
||||
328
TelegramUI/PeopleNearbyController.swift
Normal file
328
TelegramUI/PeopleNearbyController.swift
Normal file
@ -0,0 +1,328 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import MapKit
|
||||
|
||||
private struct PeerNearbyEntry {
|
||||
let peer: Peer
|
||||
let expires: Int32
|
||||
let distance: Int32
|
||||
}
|
||||
|
||||
private func arePeersNearbyEqual(_ lhs: PeerNearbyEntry?, _ rhs: PeerNearbyEntry?) -> Bool {
|
||||
if let lhs = lhs, let rhs = rhs {
|
||||
return lhs.peer.isEqual(rhs.peer) && lhs.expires == rhs.expires && lhs.distance == rhs.distance
|
||||
} else {
|
||||
return (lhs != nil) == (rhs != nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNearbyEntry]) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.count {
|
||||
if !lhs[i].peer.isEqual(rhs[i].peer) || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class PeopleNearbyControllerArguments {
|
||||
let context: AccountContext
|
||||
let openChat: (Peer) -> Void
|
||||
let openCreateGroup: () -> Void
|
||||
|
||||
init(context: AccountContext, openChat: @escaping (Peer) -> Void, openCreateGroup: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.openChat = openChat
|
||||
self.openCreateGroup = openCreateGroup
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeopleNearbySection: Int32 {
|
||||
case header
|
||||
case users
|
||||
case groups
|
||||
case channels
|
||||
}
|
||||
|
||||
private enum PeopleNearbyEntry: ItemListNodeEntry {
|
||||
case header(PresentationTheme, String)
|
||||
|
||||
case usersHeader(PresentationTheme, String)
|
||||
case empty(PresentationTheme, String)
|
||||
case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
||||
|
||||
case groupsHeader(PresentationTheme, String)
|
||||
case createGroup(PresentationTheme, String)
|
||||
case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
||||
|
||||
case channelsHeader(PresentationTheme, String)
|
||||
case channel(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .header:
|
||||
return PeopleNearbySection.header.rawValue
|
||||
case .usersHeader, .empty, .user:
|
||||
return PeopleNearbySection.users.rawValue
|
||||
case .groupsHeader, .createGroup, .group:
|
||||
return PeopleNearbySection.groups.rawValue
|
||||
case .channelsHeader, .channel:
|
||||
return PeopleNearbySection.channels.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .header:
|
||||
return 0
|
||||
case .usersHeader:
|
||||
return 1
|
||||
case .empty:
|
||||
return 2
|
||||
case let .user(index, _, _, _, _, _):
|
||||
return 3 + index
|
||||
case .groupsHeader:
|
||||
return 1000
|
||||
case .createGroup:
|
||||
return 1001
|
||||
case let .group(index, _, _, _, _, _):
|
||||
return 1002 + index
|
||||
case .channelsHeader:
|
||||
return 2000
|
||||
case let .channel(index, _, _, _, _, _):
|
||||
return 2001 + index
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeopleNearbyEntry, rhs: PeopleNearbyEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .header(lhsTheme, lhsText):
|
||||
if case let .header(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .usersHeader(lhsTheme, lhsText):
|
||||
if case let .usersHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .empty(lhsTheme, lhsText):
|
||||
if case let .empty(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .user(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer):
|
||||
if case let .user(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .groupsHeader(lhsTheme, lhsText):
|
||||
if case let .groupsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .createGroup(lhsTheme, lhsText):
|
||||
if case let .createGroup(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .group(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer):
|
||||
if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .channelsHeader(lhsTheme, lhsText):
|
||||
if case let .channelsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .channel(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer):
|
||||
if case let .channel(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PeopleNearbyEntry, rhs: PeopleNearbyEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(_ arguments: PeopleNearbyControllerArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .header(theme, text):
|
||||
return PeopleNearbyHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .usersHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .empty(theme, text):
|
||||
return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
|
||||
case let .user(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
||||
func distance(_ distance: Int32) -> String {
|
||||
let formatter = MKDistanceFormatter()
|
||||
formatter.unitStyle = .abbreviated
|
||||
return formatter.string(fromDistance: Double(distance))
|
||||
}
|
||||
|
||||
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(distance(peer.distance)), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openChat(peer.peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopStripe: false, hasTopGroupInset: false, tag: nil)
|
||||
case let .groupsHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .createGroup(theme, title):
|
||||
return ContactListActionItem(theme: theme, title: title, icon: .generic(UIImage(bundleImageName: "Contact List/CreateGroupActionIcon")!), header: nil, action: {
|
||||
arguments.openCreateGroup()
|
||||
})
|
||||
case let .group(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
||||
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text("10 members"), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openChat(peer.peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopStripe: false, hasTopGroupInset: false, tag: nil)
|
||||
case let .channelsHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .channel(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer):
|
||||
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.context.account, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text("10 members"), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openChat(peer.peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, hasTopStripe: false, hasTopGroupInset: false, tag: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeopleNearbyControllerState: Equatable {
|
||||
static func ==(lhs: PeopleNearbyControllerState, rhs: PeopleNearbyControllerState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeopleNearbyData: Equatable {
|
||||
let users: [PeerNearbyEntry]
|
||||
let groups: [PeerNearbyEntry]
|
||||
let channels: [PeerNearbyEntry]
|
||||
|
||||
init(users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
|
||||
self.users = users
|
||||
self.groups = groups
|
||||
self.channels = channels
|
||||
}
|
||||
|
||||
static func ==(lhs: PeopleNearbyData, rhs: PeopleNearbyData) -> Bool {
|
||||
return arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
|
||||
}
|
||||
}
|
||||
|
||||
private func peopleNearbyControllerEntries(state: PeopleNearbyControllerState, data: PeopleNearbyData?, presentationData: PresentationData) -> [PeopleNearbyEntry] {
|
||||
var entries: [PeopleNearbyEntry] = []
|
||||
|
||||
entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_Description))
|
||||
entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased()))
|
||||
if let data = data, !data.users.isEmpty {
|
||||
var i: Int32 = 0
|
||||
for user in data.users {
|
||||
entries.append(.user(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, user))
|
||||
i += 1
|
||||
}
|
||||
} else {
|
||||
entries.append(.empty(presentationData.theme, presentationData.strings.PeopleNearby_UsersEmpty))
|
||||
}
|
||||
|
||||
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased()))
|
||||
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup))
|
||||
if let data = data, !data.groups.isEmpty {
|
||||
var i: Int32 = 0
|
||||
for group in data.groups {
|
||||
entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group))
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
if let data = data, !data.channels.isEmpty {
|
||||
var i: Int32 = 0
|
||||
for channel in data.channels {
|
||||
entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel))
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public func peopleNearbyController(context: AccountContext) -> ViewController {
|
||||
let statePromise = ValuePromise(PeopleNearbyControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: PeopleNearbyControllerState())
|
||||
let updateState: ((PeopleNearbyControllerState) -> PeopleNearbyControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var navigateToChatImpl: ((Peer) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let dataPromise = Promise<PeopleNearbyData?>(nil)
|
||||
|
||||
let arguments = PeopleNearbyControllerArguments(context: context, openChat: { peer in
|
||||
navigateToChatImpl?(peer)
|
||||
}, openCreateGroup: {
|
||||
|
||||
})
|
||||
|
||||
let dataSignal: Signal<PeopleNearbyData?, NoError> = currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)
|
||||
|> mapToSignal { coordinate -> Signal<PeopleNearbyData?, NoError> in
|
||||
guard let coordinate = coordinate else {
|
||||
return .single(nil)
|
||||
}
|
||||
return peersNearby(network: context.account.network, accountStateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude), radius: 200)
|
||||
|> mapToSignal { peersNearby -> Signal<PeopleNearbyData?, NoError> in
|
||||
return context.account.postbox.transaction { transaction -> PeopleNearbyData? in
|
||||
var result: [PeerNearbyEntry] = []
|
||||
for peerNearby in peersNearby {
|
||||
if peerNearby.id != context.account.peerId, let peer = transaction.getPeer(peerNearby.id) {
|
||||
result.append(PeerNearbyEntry(peer: peer, expires: peerNearby.expires, distance: peerNearby.distance))
|
||||
}
|
||||
}
|
||||
return PeopleNearbyData(users: result, groups: [], channels: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataPromise.set(dataSignal)
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), dataPromise.get())
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, data -> (ItemListControllerState, (ItemListNodeState<PeopleNearbyEntry>, PeopleNearbyEntry.ItemGenerationArguments)) in
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.PeopleNearby_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(entries: peopleNearbyControllerEntries(state: state, data: data, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: true, userInteractionEnabled: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
} |> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
navigateToChatImpl = { [weak controller] peer in
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
navigateToChatController(navigationController: navigationController, context: context, chatLocation: .peer(peer.id))
|
||||
}
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
if let controller = controller {
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
123
TelegramUI/PeopleNearbyHeaderItem.swift
Normal file
123
TelegramUI/PeopleNearbyHeaderItem.swift
Normal file
@ -0,0 +1,123 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
class PeopleNearbyHeaderItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let text: String
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
init(theme: PresentationTheme, text: String, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.text = text
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PeopleNearbyHeaderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
guard let nodeValue = node() as? PeopleNearbyHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(13.0)
|
||||
|
||||
class PeopleNearbyHeaderItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private var iconNode: PeopleNearbyIconNode?
|
||||
|
||||
private var item: PeopleNearbyHeaderItem?
|
||||
|
||||
init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: PeopleNearbyHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 54.0 + params.leftInset
|
||||
let topInset: CGFloat = 92.0
|
||||
|
||||
let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize: CGSize
|
||||
|
||||
contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.accessibilityLabel = attributedText.string
|
||||
|
||||
let iconNode: PeopleNearbyIconNode
|
||||
if let node = strongSelf.iconNode {
|
||||
iconNode = node
|
||||
iconNode.updateTheme(item.theme)
|
||||
} else {
|
||||
iconNode = PeopleNearbyIconNode(theme: item.theme)
|
||||
strongSelf.iconNode = iconNode
|
||||
strongSelf.addSubnode(iconNode)
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 60.0, height: 60.0)
|
||||
iconNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: 5.0), size: iconSize)
|
||||
|
||||
let _ = titleApply()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset), size: titleLayout.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
204
TelegramUI/PeopleNearbyIconNode.swift
Normal file
204
TelegramUI/PeopleNearbyIconNode.swift
Normal file
@ -0,0 +1,204 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
import LegacyComponents
|
||||
|
||||
private final class PeopleNearbyIconWavesNodeParams: NSObject {
|
||||
let color: UIColor
|
||||
let progress: CGFloat
|
||||
|
||||
init(color: UIColor, progress: CGFloat) {
|
||||
self.color = color
|
||||
self.progress = progress
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
private func degToRad(_ degrees: CGFloat) -> CGFloat {
|
||||
return degrees * CGFloat.pi / 180.0
|
||||
}
|
||||
|
||||
final class PeopleNearbyIconWavesNode: ASDisplayNode {
|
||||
var color: UIColor {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var effectiveProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
override func willEnterHierarchy() {
|
||||
super.willEnterHierarchy()
|
||||
|
||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! PeopleNearbyIconWavesNode).effectiveProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! PeopleNearbyIconWavesNode).effectiveProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as! POPAnimatableProperty)
|
||||
animation.fromValue = CGFloat(0.0) as NSNumber
|
||||
animation.toValue = CGFloat(1.0) as NSNumber
|
||||
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
||||
animation.duration = 3.5
|
||||
animation.repeatForever = true
|
||||
self.pop_add(animation, forKey: "indefiniteProgress")
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
let t = CACurrentMediaTime()
|
||||
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
|
||||
return PeopleNearbyIconWavesNodeParams(color: self.color, progress: value)
|
||||
}
|
||||
|
||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(bounds)
|
||||
}
|
||||
|
||||
if let parameters = parameters as? PeopleNearbyIconWavesNodeParams {
|
||||
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||
let radius: CGFloat = bounds.width * 0.3333
|
||||
let range: CGFloat = (bounds.width - radius * 2.0) / 2.0
|
||||
|
||||
context.setFillColor(parameters.color.cgColor)
|
||||
|
||||
let draw: (CGContext, CGFloat) -> Void = { context, pos in
|
||||
let path = CGMutablePath()
|
||||
|
||||
let pathRadius: CGFloat = bounds.width * 0.3333 + range * pos
|
||||
path.addEllipse(in: CGRect(x: center.x - pathRadius, y: center.y - pathRadius, width: pathRadius * 2.0, height: pathRadius * 2.0))
|
||||
|
||||
let strokedPath = path.copy(strokingWithWidth: 1.0, lineCap: .round, lineJoin: .miter, miterLimit: 10.0)
|
||||
context.addPath(strokedPath)
|
||||
context.fillPath()
|
||||
}
|
||||
|
||||
let position = parameters.progress
|
||||
var alpha = position / 0.5
|
||||
if alpha > 1.0 {
|
||||
alpha = 2.0 - alpha
|
||||
}
|
||||
context.setAlpha(alpha * 0.7)
|
||||
|
||||
draw(context, position)
|
||||
|
||||
var progress = parameters.progress + 0.3333
|
||||
if progress > 1.0 {
|
||||
progress = progress - 1.0
|
||||
}
|
||||
|
||||
var largerPos = progress
|
||||
var largerAlpha = largerPos / 0.5
|
||||
if largerAlpha > 1.0 {
|
||||
largerAlpha = 2.0 - largerAlpha
|
||||
}
|
||||
context.setAlpha(largerAlpha * 0.7)
|
||||
|
||||
draw(context, largerPos)
|
||||
|
||||
progress = parameters.progress + 0.6666
|
||||
if progress > 1.0 {
|
||||
progress = progress - 1.0
|
||||
}
|
||||
|
||||
largerPos = progress
|
||||
largerAlpha = largerPos / 0.5
|
||||
if largerAlpha > 1.0 {
|
||||
largerAlpha = 2.0 - largerAlpha
|
||||
}
|
||||
context.setAlpha(largerAlpha * 0.7)
|
||||
|
||||
draw(context, largerPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateIcon(size: CGSize, color: UIColor, contentColor: UIColor) -> UIImage {
|
||||
return generateImage(size, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: size.width / 120.0, y: size.height / 120.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
context.translateBy(x: 0.0, y: 6.0)
|
||||
context.setFillColor(contentColor.cgColor)
|
||||
let _ = try? drawSvgPath(context, path: "M27.8628211,52.2347452 L27.8628211,27.1373017 L2.76505663,27.1373017 C1.55217431,27.1373017 0.568938916,26.1540663 0.568938916,24.941184 C0.568938916,24.0832172 1.06857435,23.3038117 1.84819149,22.9456161 L51.2643819,0.241311309 C52.586928,-0.366333451 54.1516568,0.213208572 54.7593016,1.53575465 C55.0801868,2.23416513 55.080181,3.03785964 54.7592857,3.7362655 L32.0544935,53.1516391 C31.548107,54.2537536 30.2441593,54.7366865 29.1420449,54.2302999 C28.3624433,53.8720978 27.8628211,53.0927006 27.8628211,52.2347452 Z ")
|
||||
})!
|
||||
}
|
||||
|
||||
final class PeopleNearbyIconNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
|
||||
private var iconNode: ASImageNode
|
||||
private var wavesNode: PeopleNearbyIconWavesNode
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isOpaque = false
|
||||
self.wavesNode = PeopleNearbyIconWavesNode(color: theme.list.itemAccentColor)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.wavesNode)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
guard self.theme !== theme else {
|
||||
return
|
||||
}
|
||||
self.theme = theme
|
||||
|
||||
self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
self.wavesNode.color = theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
if let image = self.iconNode.image, image.size.width == self.bounds.width {
|
||||
} else {
|
||||
self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
self.iconNode.frame = self.bounds
|
||||
self.wavesNode.frame = self.bounds.insetBy(dx: -self.bounds.width * 0.3, dy: -self.bounds.height * 0.3)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1
TelegramUI/Resources/Animations/anim_success.json
Normal file
1
TelegramUI/Resources/Animations/anim_success.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -128,6 +128,9 @@ final class SelectablePeerNode: ASDisplayNode {
|
||||
overrideImage = .savedMessagesIcon
|
||||
} else {
|
||||
text = mainPeer.compactDisplayTitle
|
||||
if mainPeer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
}
|
||||
self.textNode.maximumNumberOfLines = UInt(numberOfLines)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
|
||||
|
||||
@ -16,3 +16,28 @@ public func textAlertController(context: AccountContext, title: String?, text: S
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
public func richTextAlertController(context: AccountContext, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions.map { action in
|
||||
return TextAlertAction(type: action.type, title: action.title, action: {
|
||||
dismissImpl?()
|
||||
action.action()
|
||||
})
|
||||
}, actionLayout: actionLayout))
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
})
|
||||
controller.dismissed = {
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ public enum UndoOverlayContent {
|
||||
case archivedChat(peerId: PeerId, title: String, text: String, undo: Bool)
|
||||
case hidArchive(title: String, text: String, undo: Bool)
|
||||
case revealedArchive(title: String, text: String, undo: Bool)
|
||||
case succeed(text: String)
|
||||
}
|
||||
|
||||
public final class UndoOverlayController: ViewController {
|
||||
|
||||
@ -70,6 +70,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.iconNode?.displaysAsynchronously = false
|
||||
self.iconNode?.image = UIImage(bundleImageName: "Chat List/ArchivedUndoIcon")
|
||||
self.iconCheckNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
self.iconCheckNode?.frame = CGRect(x: 0.0, y: 0.0, width: 24.0, height: 24.0)
|
||||
self.animationNode = nil
|
||||
} else {
|
||||
self.iconNode = nil
|
||||
@ -82,20 +83,32 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .hidArchive(title, text, undo):
|
||||
self.iconNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_archiveswipe", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_archiveswipe", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||
displayUndo = undo
|
||||
self.originalRemainingSeconds = 3
|
||||
case let .revealedArchive(title, text, undo):
|
||||
self.iconNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||
displayUndo = undo
|
||||
self.originalRemainingSeconds = 3
|
||||
case let .succeed(text):
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
}
|
||||
|
||||
self.remainingSeconds = self.originalRemainingSeconds
|
||||
@ -127,7 +140,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
self.panelWrapperNode.addSubnode(self.statusNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed:
|
||||
break
|
||||
}
|
||||
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
|
||||
@ -266,8 +279,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
transition.updateFrame(node: iconNode, frame: iconFrame)
|
||||
|
||||
if let iconCheckNode = self.iconCheckNode {
|
||||
let statusSize: CGFloat = 24.0
|
||||
transition.updateFrame(node: iconCheckNode, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floor((iconFrame.width - statusSize) / 2.0), y: iconFrame.minY + floor((iconFrame.height - statusSize) / 2.0) + 3.0), size: CGSize(width: statusSize, height: statusSize)))
|
||||
let statusSize: CGFloat = iconCheckNode.frame.width
|
||||
var offset: CGFloat = 0.0
|
||||
if statusSize < 30.0 {
|
||||
offset = 3.0
|
||||
}
|
||||
transition.updateFrame(node: iconCheckNode, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floor((iconFrame.width - statusSize) / 2.0), y: iconFrame.minY + floor((iconFrame.height - statusSize) / 2.0) + offset), size: CGSize(width: statusSize, height: statusSize)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user