mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-16 13:43:18 +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 */; };
|
090E63E62195880F00E3C035 /* ContactAddItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63E52195880F00E3C035 /* ContactAddItem.swift */; };
|
||||||
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
|
090E63EE2196FE3A00E3C035 /* OpenAddContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E63ED2196FE3A00E3C035 /* OpenAddContact.swift */; };
|
||||||
090E777922A6A32E00CD99F5 /* ThemeSettingsThemeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E777822A6A32E00CD99F5 /* ThemeSettingsThemeItem.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 */; };
|
0910B0ED21FA178C00F8F87D /* WallpaperPreviewMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EC21FA178C00F8F87D /* WallpaperPreviewMedia.swift */; };
|
||||||
0910B0EF21FA532D00F8F87D /* WallpaperResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EE21FA532D00F8F87D /* WallpaperResources.swift */; };
|
0910B0EF21FA532D00F8F87D /* WallpaperResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0EE21FA532D00F8F87D /* WallpaperResources.swift */; };
|
||||||
0910B0F121FB3DE100F8F87D /* WallpaperPatternPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0910B0F021FB3DE100F8F87D /* WallpaperPatternPanelNode.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0910B0F021FB3DE100F8F87D /* WallpaperPatternPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPatternPanelNode.swift; sourceTree = "<group>"; };
|
||||||
@ -2544,6 +2554,16 @@
|
|||||||
name = "Language Suggestion";
|
name = "Language Suggestion";
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
0919546D229458E900E11046 /* Animated Stickers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -2568,6 +2588,7 @@
|
|||||||
09310D13213BC5DE0020033A /* Animations */ = {
|
09310D13213BC5DE0020033A /* Animations */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
090E778B22AA842200CD99F5 /* anim_success.json */,
|
||||||
09FFBCCF227B7F9000C33B4B /* anim_archiveswipe.json */,
|
09FFBCCF227B7F9000C33B4B /* anim_archiveswipe.json */,
|
||||||
094735182277483B00EA2312 /* anim_infotip.json */,
|
094735182277483B00EA2312 /* anim_infotip.json */,
|
||||||
094735072275D72000EA2312 /* anim_archive.json */,
|
094735072275D72000EA2312 /* anim_archive.json */,
|
||||||
@ -4482,6 +4503,7 @@
|
|||||||
D0E1199D229809B6008CAE3A /* ChannelDiscussionGroupActionSheetItem.swift */,
|
D0E1199D229809B6008CAE3A /* ChannelDiscussionGroupActionSheetItem.swift */,
|
||||||
D050A463229C052A0044F11A /* ChannelDiscussionGroupSetupSearchItem.swift */,
|
D050A463229C052A0044F11A /* ChannelDiscussionGroupSetupSearchItem.swift */,
|
||||||
D050A465229C06460044F11A /* ChannelDiscussionGroupSearchContainerNode.swift */,
|
D050A465229C06460044F11A /* ChannelDiscussionGroupSearchContainerNode.swift */,
|
||||||
|
090E778922A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift */,
|
||||||
);
|
);
|
||||||
name = "Peer Info";
|
name = "Peer Info";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -4669,6 +4691,7 @@
|
|||||||
0941A99E210B053300EBE194 /* Open In */,
|
0941A99E210B053300EBE194 /* Open In */,
|
||||||
09F215982263E61400AEDF6D /* Passcode */,
|
09F215982263E61400AEDF6D /* Passcode */,
|
||||||
09B4EE5721A82F5900847FA6 /* Permissions */,
|
09B4EE5721A82F5900847FA6 /* Permissions */,
|
||||||
|
090E778422A9B94700CD99F5 /* People Nearby */,
|
||||||
);
|
);
|
||||||
name = Controllers;
|
name = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -5284,6 +5307,7 @@
|
|||||||
09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */,
|
09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */,
|
||||||
094735172275D72100EA2312 /* anim_pin.json in Resources */,
|
094735172275D72100EA2312 /* anim_pin.json in Resources */,
|
||||||
094735112275D72100EA2312 /* anim_archive.json in Resources */,
|
094735112275D72100EA2312 /* anim_archive.json in Resources */,
|
||||||
|
090E778C22AA842300CD99F5 /* anim_success.json in Resources */,
|
||||||
D0EB42051F3143AB00838FE6 /* LegacyComponentsResources.bundle in Resources */,
|
D0EB42051F3143AB00838FE6 /* LegacyComponentsResources.bundle in Resources */,
|
||||||
D0E9BAA21F056F4C00F079A4 /* stp_card_discover@3x.png in Resources */,
|
D0E9BAA21F056F4C00F079A4 /* stp_card_discover@3x.png in Resources */,
|
||||||
D0E9BAB01F056F4C00F079A4 /* stp_card_mastercard@3x.png in Resources */,
|
D0E9BAB01F056F4C00F079A4 /* stp_card_mastercard@3x.png in Resources */,
|
||||||
@ -5380,6 +5404,7 @@
|
|||||||
D0E1199A2297F9C6008CAE3A /* ChannelDiscussionGroupSetupController.swift in Sources */,
|
D0E1199A2297F9C6008CAE3A /* ChannelDiscussionGroupSetupController.swift in Sources */,
|
||||||
D083491C209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift in Sources */,
|
D083491C209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift in Sources */,
|
||||||
D0EC6CB31EB9F58800EBF1C3 /* shader.c in Sources */,
|
D0EC6CB31EB9F58800EBF1C3 /* shader.c in Sources */,
|
||||||
|
090E778A22A9F23C00CD99F5 /* ChannelOwnershipTransferController.swift in Sources */,
|
||||||
D06E4C352134AE3C00088087 /* ThemeAutoNightSettingsController.swift in Sources */,
|
D06E4C352134AE3C00088087 /* ThemeAutoNightSettingsController.swift in Sources */,
|
||||||
D0F0AAE21EC20EF8005EE2A5 /* CallControllerStatusNode.swift in Sources */,
|
D0F0AAE21EC20EF8005EE2A5 /* CallControllerStatusNode.swift in Sources */,
|
||||||
D0EC6CB41EB9F58800EBF1C3 /* timing.c in Sources */,
|
D0EC6CB41EB9F58800EBF1C3 /* timing.c in Sources */,
|
||||||
@ -5528,6 +5553,7 @@
|
|||||||
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
|
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
|
||||||
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
|
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
|
||||||
D0BE303220601FFC00FBE6D8 /* LocationBroadcastActionSheetItem.swift in Sources */,
|
D0BE303220601FFC00FBE6D8 /* LocationBroadcastActionSheetItem.swift in Sources */,
|
||||||
|
090E778E22AA863A00CD99F5 /* PeopleNearbyIconNode.swift in Sources */,
|
||||||
D0EC6CF41EB9F58800EBF1C3 /* ManagedMediaId.swift in Sources */,
|
D0EC6CF41EB9F58800EBF1C3 /* ManagedMediaId.swift in Sources */,
|
||||||
09D968A3221F800A00B1458A /* ChatUploadingActivityContentNode.swift in Sources */,
|
09D968A3221F800A00B1458A /* ChatUploadingActivityContentNode.swift in Sources */,
|
||||||
D0CFBB971FD8B0F700B65C0D /* ChatBubbleInstantVideoDecoration.swift in Sources */,
|
D0CFBB971FD8B0F700B65C0D /* ChatBubbleInstantVideoDecoration.swift in Sources */,
|
||||||
@ -5536,6 +5562,7 @@
|
|||||||
D0E9BA521F0559DA00F079A4 /* STPImageLibrary.m in Sources */,
|
D0E9BA521F0559DA00F079A4 /* STPImageLibrary.m in Sources */,
|
||||||
D0EC6CF61EB9F58800EBF1C3 /* ChatContextResultManagedMediaId.swift in Sources */,
|
D0EC6CF61EB9F58800EBF1C3 /* ChatContextResultManagedMediaId.swift in Sources */,
|
||||||
D048B33B203C777500038D05 /* RenderedTotalUnreadCount.swift in Sources */,
|
D048B33B203C777500038D05 /* RenderedTotalUnreadCount.swift in Sources */,
|
||||||
|
090E778622A9B95A00CD99F5 /* PeopleNearbyController.swift in Sources */,
|
||||||
D04ECD721FFBF22B00DE9029 /* OpenUrl.swift in Sources */,
|
D04ECD721FFBF22B00DE9029 /* OpenUrl.swift in Sources */,
|
||||||
D04B4D661EEA993A00711AF6 /* LegacyLocationController.swift in Sources */,
|
D04B4D661EEA993A00711AF6 /* LegacyLocationController.swift in Sources */,
|
||||||
D056CD7A1FF3CC2A00880D28 /* ListMessagePlaybackOverlayNode.swift in Sources */,
|
D056CD7A1FF3CC2A00880D28 /* ListMessagePlaybackOverlayNode.swift in Sources */,
|
||||||
@ -6387,6 +6414,7 @@
|
|||||||
D0EC6E801EB9F58900EBF1C3 /* ChangePhoneNumberCodeController.swift in Sources */,
|
D0EC6E801EB9F58900EBF1C3 /* ChangePhoneNumberCodeController.swift in Sources */,
|
||||||
D09E637C1F0E7C28003444CD /* SharedMediaPlayer.swift in Sources */,
|
D09E637C1F0E7C28003444CD /* SharedMediaPlayer.swift in Sources */,
|
||||||
D0EC6E811EB9F58900EBF1C3 /* NotificationContainerController.swift in Sources */,
|
D0EC6E811EB9F58900EBF1C3 /* NotificationContainerController.swift in Sources */,
|
||||||
|
090E778822A9B96100CD99F5 /* PeopleNearbyHeaderItem.swift in Sources */,
|
||||||
D0754D271EEE10C800884F6E /* BotCheckoutController.swift in Sources */,
|
D0754D271EEE10C800884F6E /* BotCheckoutController.swift in Sources */,
|
||||||
D053DADA201A4C4400993D32 /* ChatTextInputAttributes.swift in Sources */,
|
D053DADA201A4C4400993D32 /* ChatTextInputAttributes.swift in Sources */,
|
||||||
0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */,
|
0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Display
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
|
||||||
private let savedMessagesIcon = UIImage(bundleImageName: "Avatar/SavedMessagesIcon")?.precomposed()
|
private let savedMessagesIcon = UIImage(bundleImageName: "Avatar/SavedMessagesIcon")?.precomposed()
|
||||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||||
|
|
||||||
@ -101,6 +102,7 @@ private enum AvatarNodeIcon: Equatable {
|
|||||||
case savedMessagesIcon
|
case savedMessagesIcon
|
||||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||||
case editAvatarIcon
|
case editAvatarIcon
|
||||||
|
case deletedIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AvatarNodeImageOverride: Equatable {
|
public enum AvatarNodeImageOverride: Equatable {
|
||||||
@ -109,6 +111,7 @@ public enum AvatarNodeImageOverride: Equatable {
|
|||||||
case savedMessagesIcon
|
case savedMessagesIcon
|
||||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||||
case editAvatarIcon
|
case editAvatarIcon
|
||||||
|
case deletedIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AvatarNodeColorOverride {
|
public enum AvatarNodeColorOverride {
|
||||||
@ -291,6 +294,9 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
case .editAvatarIcon:
|
case .editAvatarIcon:
|
||||||
representation = peer?.smallProfileImage
|
representation = peer?.smallProfileImage
|
||||||
icon = .editAvatarIcon
|
icon = .editAvatarIcon
|
||||||
|
case .deletedIcon:
|
||||||
|
representation = nil
|
||||||
|
icon = .deletedIcon
|
||||||
}
|
}
|
||||||
} else if peer?.restrictionText == nil {
|
} else if peer?.restrictionText == nil {
|
||||||
representation = peer?.smallProfileImage
|
representation = peer?.smallProfileImage
|
||||||
@ -411,7 +417,9 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
let colorsArray: NSArray
|
let colorsArray: NSArray
|
||||||
var iconColor = UIColor.white
|
var iconColor = UIColor.white
|
||||||
if let parameters = parameters as? AvatarNodeParameters, parameters.icon != .none {
|
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
|
colorsArray = savedMessagesColors
|
||||||
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme {
|
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme {
|
||||||
colorsArray = [theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor]
|
colorsArray = [theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor]
|
||||||
@ -444,7 +452,16 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
|
|
||||||
if let parameters = parameters as? AvatarNodeParameters {
|
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
|
let factor = bounds.size.width / 60.0
|
||||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||||
context.scaleBy(x: factor, y: -factor)
|
context.scaleBy(x: factor, y: -factor)
|
||||||
|
|||||||
@ -217,7 +217,7 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation {
|
|||||||
|
|
||||||
final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation {
|
final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation {
|
||||||
var uniqueId: String {
|
var uniqueId: String {
|
||||||
return "animated-sticker"
|
return "animated-sticker-v1"
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||||
|
|||||||
@ -6,77 +6,6 @@ import Display
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
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 final class CallRatingAlertContentNode: AlertContentNode {
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let apply: (Int) -> Void
|
private let apply: (Int) -> Void
|
||||||
@ -87,7 +16,7 @@ private final class CallRatingAlertContentNode: AlertContentNode {
|
|||||||
private let starNodes: [ASButtonNode]
|
private let starNodes: [ASButtonNode]
|
||||||
|
|
||||||
private let actionNodesSeparator: ASDisplayNode
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
private let actionNodes: [CallRatingContentActionNode]
|
private let actionNodes: [TextAlertContentActionNode]
|
||||||
private let actionVerticalSeparators: [ASDisplayNode]
|
private let actionVerticalSeparators: [ASDisplayNode]
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
@ -114,8 +43,8 @@ private final class CallRatingAlertContentNode: AlertContentNode {
|
|||||||
self.actionNodesSeparator = ASDisplayNode()
|
self.actionNodesSeparator = ASDisplayNode()
|
||||||
self.actionNodesSeparator.isLayerBacked = true
|
self.actionNodesSeparator.isLayerBacked = true
|
||||||
|
|
||||||
self.actionNodes = actions.map { action -> CallRatingContentActionNode in
|
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||||
return CallRatingContentActionNode(theme: theme, action: action)
|
return TextAlertContentActionNode(theme: theme, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||||
|
|||||||
@ -8,139 +8,6 @@ import TelegramCore
|
|||||||
|
|
||||||
private func generateIconImage(theme: AlertControllerTheme) -> UIImage? {
|
private func generateIconImage(theme: AlertControllerTheme) -> UIImage? {
|
||||||
return UIImage(bundleImageName: "Call List/AlertIcon")
|
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 {
|
private final class CallSuggestTabAlertContentNode: AlertContentNode {
|
||||||
@ -151,7 +18,7 @@ private final class CallSuggestTabAlertContentNode: AlertContentNode {
|
|||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
|
|
||||||
private let actionNodesSeparator: ASDisplayNode
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
private let actionNodes: [CallSuggestTabContentActionNode]
|
private let actionNodes: [TextAlertContentActionNode]
|
||||||
private let actionVerticalSeparators: [ASDisplayNode]
|
private let actionVerticalSeparators: [ASDisplayNode]
|
||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
@ -174,8 +41,8 @@ private final class CallSuggestTabAlertContentNode: AlertContentNode {
|
|||||||
self.actionNodesSeparator = ASDisplayNode()
|
self.actionNodesSeparator = ASDisplayNode()
|
||||||
self.actionNodesSeparator.isLayerBacked = true
|
self.actionNodesSeparator.isLayerBacked = true
|
||||||
|
|
||||||
self.actionNodes = actions.map { action -> CallSuggestTabContentActionNode in
|
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||||
return CallSuggestTabContentActionNode(theme: theme, action: action)
|
return TextAlertContentActionNode(theme: theme, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||||
|
|||||||
@ -8,11 +8,13 @@ import TelegramCore
|
|||||||
private final class ChannelAdminControllerArguments {
|
private final class ChannelAdminControllerArguments {
|
||||||
let account: Account
|
let account: Account
|
||||||
let toggleRight: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
let toggleRight: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
|
||||||
|
let transferOwnership: () -> Void
|
||||||
let dismissAdmin: () -> 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.account = account
|
||||||
self.toggleRight = toggleRight
|
self.toggleRight = toggleRight
|
||||||
|
self.transferOwnership = transferOwnership
|
||||||
self.dismissAdmin = dismissAdmin
|
self.dismissAdmin = dismissAdmin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,6 +22,7 @@ private final class ChannelAdminControllerArguments {
|
|||||||
private enum ChannelAdminSection: Int32 {
|
private enum ChannelAdminSection: Int32 {
|
||||||
case info
|
case info
|
||||||
case rights
|
case rights
|
||||||
|
case transfer
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +31,7 @@ private enum ChannelAdminEntryStableId: Hashable {
|
|||||||
case rightsTitle
|
case rightsTitle
|
||||||
case right(TelegramChatAdminRightsFlags)
|
case right(TelegramChatAdminRightsFlags)
|
||||||
case addAdminsInfo
|
case addAdminsInfo
|
||||||
|
case transfer
|
||||||
case dismiss
|
case dismiss
|
||||||
|
|
||||||
var hashValue: Int {
|
var hashValue: Int {
|
||||||
@ -40,6 +44,8 @@ private enum ChannelAdminEntryStableId: Hashable {
|
|||||||
return 2
|
return 2
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
return 3
|
return 3
|
||||||
|
case .transfer:
|
||||||
|
return 4
|
||||||
case let .right(flags):
|
case let .right(flags):
|
||||||
return flags.rawValue.hashValue
|
return flags.rawValue.hashValue
|
||||||
}
|
}
|
||||||
@ -71,6 +77,12 @@ private enum ChannelAdminEntryStableId: Hashable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .transfer:
|
||||||
|
if case .transfer = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
if case .dismiss = rhs {
|
if case .dismiss = rhs {
|
||||||
return true
|
return true
|
||||||
@ -86,6 +98,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
case rightsTitle(PresentationTheme, String)
|
case rightsTitle(PresentationTheme, String)
|
||||||
case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool)
|
case rightItem(PresentationTheme, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool)
|
||||||
case addAdminsInfo(PresentationTheme, String)
|
case addAdminsInfo(PresentationTheme, String)
|
||||||
|
case transfer(PresentationTheme, String)
|
||||||
case dismiss(PresentationTheme, String)
|
case dismiss(PresentationTheme, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
@ -94,6 +107,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
return ChannelAdminSection.info.rawValue
|
return ChannelAdminSection.info.rawValue
|
||||||
case .rightsTitle, .rightItem, .addAdminsInfo:
|
case .rightsTitle, .rightItem, .addAdminsInfo:
|
||||||
return ChannelAdminSection.rights.rawValue
|
return ChannelAdminSection.rights.rawValue
|
||||||
|
case .transfer:
|
||||||
|
return ChannelAdminSection.transfer.rawValue
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
return ChannelAdminSection.dismiss.rawValue
|
return ChannelAdminSection.dismiss.rawValue
|
||||||
}
|
}
|
||||||
@ -109,6 +124,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
return .right(right)
|
return .right(right)
|
||||||
case .addAdminsInfo:
|
case .addAdminsInfo:
|
||||||
return .addAdminsInfo
|
return .addAdminsInfo
|
||||||
|
case .transfer:
|
||||||
|
return .transfer
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
return .dismiss
|
return .dismiss
|
||||||
}
|
}
|
||||||
@ -177,6 +194,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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):
|
case let .dismiss(lhsTheme, lhsText):
|
||||||
if case let .dismiss(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .dismiss(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -218,6 +241,13 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case .transfer:
|
||||||
|
switch rhs {
|
||||||
|
case .info, .rightsTitle, .rightItem, .addAdminsInfo, .transfer:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -237,8 +267,12 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
case let .addAdminsInfo(theme, text):
|
case let .addAdminsInfo(theme, text):
|
||||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
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):
|
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()
|
arguments.dismissAdmin()
|
||||||
}, tag: nil)
|
}, tag: nil)
|
||||||
}
|
}
|
||||||
@ -422,6 +456,10 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
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 {
|
if let initialParticipant = initialParticipant, case let .member(participant) = initialParticipant, let adminInfo = participant.adminInfo, !adminInfo.rights.flags.isEmpty {
|
||||||
var canDismiss = false
|
var canDismiss = false
|
||||||
if channel.flags.contains(.isCreator) {
|
if channel.flags.contains(.isCreator) {
|
||||||
@ -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))
|
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 {
|
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))
|
entries.append(.dismiss(presentationData.theme, presentationData.strings.Channel_Moderator_AccessLevelRevoke))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,7 +538,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
return entries
|
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 statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: ChannelAdminControllerState())
|
let stateValue = Atomic(value: ChannelAdminControllerState())
|
||||||
let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in
|
let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in
|
||||||
@ -509,9 +550,18 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
|||||||
let updateRightsDisposable = MetaDisposable()
|
let updateRightsDisposable = MetaDisposable()
|
||||||
actionsDisposable.add(updateRightsDisposable)
|
actionsDisposable.add(updateRightsDisposable)
|
||||||
|
|
||||||
|
let transferOwnershipDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(transferOwnershipDisposable)
|
||||||
|
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, Any?) -> 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
|
let arguments = ChannelAdminControllerArguments(account: context.account, toggleRight: { right, flags in
|
||||||
updateState { current in
|
updateState { current in
|
||||||
var updated = flags
|
var updated = flags
|
||||||
@ -522,6 +572,65 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
|||||||
}
|
}
|
||||||
return current.withUpdatedUpdatedFlags(updated)
|
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: {
|
}, dismissAdmin: {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
|||||||
@ -199,12 +199,16 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
|
|||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
peerText = strings.Channel_Management_LabelCreator
|
peerText = strings.Channel_Management_LabelOwner
|
||||||
action = nil
|
action = nil
|
||||||
case let .member(_, _, adminInfo, _):
|
case let .member(_, _, adminInfo, _):
|
||||||
if let adminInfo = adminInfo {
|
if let adminInfo = adminInfo {
|
||||||
if let peer = participant.peers[adminInfo.promotedBy] {
|
if let peer = participant.peers[adminInfo.promotedBy] {
|
||||||
|
if peer.id == participant.peer.id {
|
||||||
|
peerText = strings.Channel_Management_LabelAdministrator
|
||||||
|
} else {
|
||||||
peerText = strings.Channel_Management_PromotedBy(peer.displayTitle).0
|
peerText = strings.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
peerText = ""
|
peerText = ""
|
||||||
}
|
}
|
||||||
@ -491,6 +495,18 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
|||||||
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
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>()
|
let peerView = Promise<PeerView>()
|
||||||
peerView.set(context.account.viewTracker.peerView(peerId))
|
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
|
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
|
dismissController = { [weak controller] in
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
@ -576,7 +592,7 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
|||||||
})
|
})
|
||||||
}, openAdmin: { participant in
|
}, openAdmin: { participant in
|
||||||
presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ 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?)
|
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
|
||||||
@ -701,7 +717,7 @@ public func channelAdminsController(context: AccountContext, peerId: PeerId, loa
|
|||||||
updateState { state in
|
updateState { state in
|
||||||
return state.withUpdatedSearchingMembers(false)
|
return state.withUpdatedSearchingMembers(false)
|
||||||
}
|
}
|
||||||
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
}, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: transferedOwnership), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
}, present: { c, a in
|
}, present: { c, a in
|
||||||
presentControllerImpl?(c, a)
|
presentControllerImpl?(c, a)
|
||||||
|
|||||||
@ -396,7 +396,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
|||||||
contactsController?.dismiss()
|
contactsController?.dismiss()
|
||||||
|
|
||||||
presentControllerImpl?(channelAdminController(context: context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in
|
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)
|
})]), nil)
|
||||||
} else {
|
} else {
|
||||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_AddBotErrorHaveRights, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
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
|
return state
|
||||||
}
|
}
|
||||||
present(channelAdminController(context: context, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
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
|
}, restrictPeer: { participant in
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
@ -469,7 +469,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
if case .searchMembers = mode {
|
if case .searchMembers = mode {
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -675,13 +675,13 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
var enabled = true
|
var enabled = true
|
||||||
if case .banAndPromoteActions = mode {
|
if case .banAndPromoteActions = mode {
|
||||||
if case .creator = participant.participant {
|
if case .creator = participant.participant {
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
} else if case .searchMembers = mode {
|
} else if case .searchMembers = mode {
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
case let .member(member):
|
case let .member(member):
|
||||||
if member.adminInfo != nil {
|
if member.adminInfo != nil {
|
||||||
label = themeAndStrings.1.Channel_Management_LabelEditor
|
label = themeAndStrings.1.Channel_Management_LabelEditor
|
||||||
@ -712,14 +712,18 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
case .searchAdmins:
|
case .searchAdmins:
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
case let .member(_, _, adminInfo, _):
|
case let .member(_, _, adminInfo, _):
|
||||||
if let adminInfo = adminInfo {
|
if let adminInfo = adminInfo {
|
||||||
if let peer = participant.peers[adminInfo.promotedBy] {
|
if let peer = participant.peers[adminInfo.promotedBy] {
|
||||||
|
if peer.id == participant.peer.id {
|
||||||
|
label = themeAndStrings.1.Channel_Management_LabelAdministrator
|
||||||
|
} else {
|
||||||
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case .searchBanned:
|
case .searchBanned:
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case let .member(_, _, _, banInfo):
|
case let .member(_, _, _, banInfo):
|
||||||
@ -774,7 +778,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
var enabled = true
|
var enabled = true
|
||||||
if case .banAndPromoteActions = mode {
|
if case .banAndPromoteActions = mode {
|
||||||
if case .creator = participant.participant {
|
if case .creator = participant.participant {
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -947,13 +951,13 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
var enabled = true
|
var enabled = true
|
||||||
if case .banAndPromoteActions = mode {
|
if case .banAndPromoteActions = mode {
|
||||||
if case .creator = participant.participant {
|
if case .creator = participant.participant {
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
} else if case .searchMembers = mode {
|
} else if case .searchMembers = mode {
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
case let .member(member):
|
case let .member(member):
|
||||||
if member.adminInfo != nil {
|
if member.adminInfo != nil {
|
||||||
label = themeAndStrings.1.Channel_Management_LabelEditor
|
label = themeAndStrings.1.Channel_Management_LabelEditor
|
||||||
@ -980,14 +984,18 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
case .searchAdmins:
|
case .searchAdmins:
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
case let .member(_, _, adminInfo, _):
|
case let .member(_, _, adminInfo, _):
|
||||||
if let adminInfo = adminInfo {
|
if let adminInfo = adminInfo {
|
||||||
if let peer = participant.peers[adminInfo.promotedBy] {
|
if let peer = participant.peers[adminInfo.promotedBy] {
|
||||||
|
if peer.id == participant.peer.id {
|
||||||
|
label = themeAndStrings.1.Channel_Management_LabelAdministrator
|
||||||
|
} else {
|
||||||
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
label = themeAndStrings.1.Channel_Management_PromotedBy(peer.displayTitle).0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case .searchBanned:
|
case .searchBanned:
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case let .member(_, _, _, banInfo):
|
case let .member(_, _, _, banInfo):
|
||||||
@ -1042,7 +1050,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
var enabled = true
|
var enabled = true
|
||||||
if case .banAndPromoteActions = mode {
|
if case .banAndPromoteActions = mode {
|
||||||
if case .creator = participant.participant {
|
if case .creator = participant.participant {
|
||||||
label = themeAndStrings.1.Channel_Management_LabelCreator
|
label = themeAndStrings.1.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -195,7 +195,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if case .creator = participant {
|
if case .creator = participant {
|
||||||
label = strongSelf.presentationData.strings.Channel_Management_LabelCreator
|
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if case .creator = participant.participant {
|
if case .creator = participant.participant {
|
||||||
label = strongSelf.presentationData.strings.Channel_Management_LabelCreator
|
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||||
enabled = false
|
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 strongSelf = self {
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
if let peer = peerViewMainPeer(peerView) {
|
||||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount)
|
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 {
|
if strongSelf.peerView === peerView {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -476,6 +476,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
var overrideImage: AvatarNodeImageOverride?
|
var overrideImage: AvatarNodeImageOverride?
|
||||||
if peer.id == item.context.account.peerId {
|
if peer.id == item.context.account.peerId {
|
||||||
overrideImage = .savedMessagesIcon
|
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)
|
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 Postbox
|
||||||
import TelegramCore
|
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 textFont = Font.regular(13.0)
|
||||||
private let boldTextFont = Font.semibold(13.0)
|
private let boldTextFont = Font.semibold(13.0)
|
||||||
|
|
||||||
@ -99,7 +28,7 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
|
|||||||
private let allowWriteLabelNode: ASTextNode
|
private let allowWriteLabelNode: ASTextNode
|
||||||
|
|
||||||
private let actionNodesSeparator: ASDisplayNode
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
private let actionNodes: [ChatMessageActionUrlAuthContentActionNode]
|
private let actionNodes: [TextAlertContentActionNode]
|
||||||
private let actionVerticalSeparators: [ASDisplayNode]
|
private let actionVerticalSeparators: [ASDisplayNode]
|
||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
@ -154,8 +83,8 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
|
|||||||
self.actionNodesSeparator = ASDisplayNode()
|
self.actionNodesSeparator = ASDisplayNode()
|
||||||
self.actionNodesSeparator.isLayerBacked = true
|
self.actionNodesSeparator.isLayerBacked = true
|
||||||
|
|
||||||
self.actionNodes = actions.map { action -> ChatMessageActionUrlAuthContentActionNode in
|
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||||
return ChatMessageActionUrlAuthContentActionNode(theme: theme, action: action)
|
return TextAlertContentActionNode(theme: theme, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||||
|
|||||||
@ -221,7 +221,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry {
|
|||||||
let peerText: String
|
let peerText: String
|
||||||
switch participant.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
peerText = strings.Channel_Management_LabelCreator
|
peerText = strings.Channel_Management_LabelOwner
|
||||||
case .member:
|
case .member:
|
||||||
peerText = strings.ChatAdmins_AdminLabel.capitalized
|
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 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 inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
|
||||||
private let accessoryButtonsWidth: CGFloat = 10.0
|
|
||||||
|
|
||||||
var text: String {
|
var text: String {
|
||||||
get {
|
get {
|
||||||
@ -81,7 +80,6 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
|||||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
let backgroundInsets = self.backgroundInsets
|
let backgroundInsets = self.backgroundInsets
|
||||||
let inputInsets = self.inputInsets
|
let inputInsets = self.inputInsets
|
||||||
let accessoryButtonsWidth = self.accessoryButtonsWidth
|
|
||||||
|
|
||||||
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
||||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||||
@ -92,7 +90,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
|||||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
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.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
|
return panelHeight
|
||||||
}
|
}
|
||||||
@ -122,9 +120,8 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
|||||||
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
||||||
let backgroundInsets = self.backgroundInsets
|
let backgroundInsets = self.backgroundInsets
|
||||||
let inputInsets = self.inputInsets
|
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))
|
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 final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let text: String
|
private let text: String
|
||||||
@ -240,7 +152,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
let inputFieldNode: ChatTextLinkEditInputFieldNode
|
let inputFieldNode: ChatTextLinkEditInputFieldNode
|
||||||
|
|
||||||
private let actionNodesSeparator: ASDisplayNode
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
private let actionNodes: [ChatTextLinkEditContentActionNode]
|
private let actionNodes: [TextAlertContentActionNode]
|
||||||
private let actionVerticalSeparators: [ASDisplayNode]
|
private let actionVerticalSeparators: [ASDisplayNode]
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
@ -274,8 +186,8 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
self.actionNodesSeparator = ASDisplayNode()
|
self.actionNodesSeparator = ASDisplayNode()
|
||||||
self.actionNodesSeparator.isLayerBacked = true
|
self.actionNodesSeparator.isLayerBacked = true
|
||||||
|
|
||||||
self.actionNodes = actions.map { action -> ChatTextLinkEditContentActionNode in
|
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||||
return ChatTextLinkEditContentActionNode(theme: theme, action: action)
|
return TextAlertContentActionNode(theme: theme, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||||
|
|||||||
@ -44,7 +44,7 @@ extension ActionSheetController {
|
|||||||
public extension AlertControllerTheme {
|
public extension AlertControllerTheme {
|
||||||
convenience init(presentationTheme: PresentationTheme) {
|
convenience init(presentationTheme: PresentationTheme) {
|
||||||
let actionSheet = presentationTheme.actionSheet
|
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
|
self.contactsNode.openPeopleNearby = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
//let controller = peopleNearbyController(context: strongSelf.context)
|
let controller = peopleNearbyController(context: strongSelf.context)
|
||||||
//(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,10 +31,9 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
var addNearbyImpl: (() -> Void)?
|
var addNearbyImpl: (() -> Void)?
|
||||||
var inviteImpl: (() -> Void)?
|
var inviteImpl: (() -> Void)?
|
||||||
//ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
|
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_AddPeopleNearby, icon: .generic(UIImage(bundleImageName: "Contact List/PeopleNearbyIcon")!), action: {
|
||||||
// addNearbyImpl?()
|
addNearbyImpl?()
|
||||||
//}),
|
}), ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
|
||||||
let options = [ContactListAdditionalOption(title: presentationData.strings.Contacts_InviteFriends, icon: .generic(UIImage(bundleImageName: "Contact List/AddMemberIcon")!), action: {
|
|
||||||
inviteImpl?()
|
inviteImpl?()
|
||||||
})]
|
})]
|
||||||
|
|
||||||
|
|||||||
@ -625,6 +625,8 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
var overrideImage: AvatarNodeImageOverride?
|
var overrideImage: AvatarNodeImageOverride?
|
||||||
if peer.id == item.account.peerId, case .generalSearch = item.peerMode {
|
if peer.id == item.account.peerId, case .generalSearch = item.peerMode {
|
||||||
overrideImage = .savedMessagesIcon
|
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)
|
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),
|
dimColor: UIColor(white: 0.0, alpha: 0.4),
|
||||||
backgroundType: .light,
|
backgroundType: .light,
|
||||||
opaqueItemBackgroundColor: .white,
|
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),
|
opaqueItemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 1.0),
|
||||||
itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7),
|
itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7),
|
||||||
standardActionTextColor: accentColor,
|
standardActionTextColor: accentColor,
|
||||||
|
|||||||
@ -59,7 +59,11 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
|||||||
if chatPeer.id == context.account.peerId {
|
if chatPeer.id == context.account.peerId {
|
||||||
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
|
self.avatarNode.setPeer(account: context.account, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
|
||||||
} else {
|
} 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)])
|
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
|
presentControllerImpl?(channelAdminController(context: context, peerId: peerView.peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
||||||
}, upgradedToSupergroup: { upgradedPeerId, f in
|
}, upgradedToSupergroup: { upgradedPeerId, f in
|
||||||
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
||||||
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
}, transferedOwnership: { _ in }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
})
|
})
|
||||||
}, restrictPeer: { participant in
|
}, restrictPeer: { participant in
|
||||||
let _ = (peerView.get()
|
let _ = (peerView.get()
|
||||||
|
|||||||
@ -644,6 +644,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
|||||||
}
|
}
|
||||||
} else if case .editSettings = item.mode {
|
} else if case .editSettings = item.mode {
|
||||||
overrideImage = AvatarNodeImageOverride.editAvatarIcon
|
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)
|
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 {
|
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)
|
strongSelf.avatarNode.setPeer(account: item.account, theme: item.theme, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||||
} else {
|
} 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))
|
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
|
overrideImage = .savedMessagesIcon
|
||||||
} else {
|
} else {
|
||||||
text = mainPeer.compactDisplayTitle
|
text = mainPeer.compactDisplayTitle
|
||||||
|
if mainPeer.isDeleted {
|
||||||
|
overrideImage = .deletedIcon
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.textNode.maximumNumberOfLines = UInt(numberOfLines)
|
self.textNode.maximumNumberOfLines = UInt(numberOfLines)
|
||||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
|
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
|
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 archivedChat(peerId: PeerId, title: String, text: String, undo: Bool)
|
||||||
case hidArchive(title: String, text: String, undo: Bool)
|
case hidArchive(title: String, text: String, undo: Bool)
|
||||||
case revealedArchive(title: String, text: String, undo: Bool)
|
case revealedArchive(title: String, text: String, undo: Bool)
|
||||||
|
case succeed(text: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class UndoOverlayController: ViewController {
|
public final class UndoOverlayController: ViewController {
|
||||||
|
|||||||
@ -70,6 +70,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.iconNode?.displaysAsynchronously = false
|
self.iconNode?.displaysAsynchronously = false
|
||||||
self.iconNode?.image = UIImage(bundleImageName: "Chat List/ArchivedUndoIcon")
|
self.iconNode?.image = UIImage(bundleImageName: "Chat List/ArchivedUndoIcon")
|
||||||
self.iconCheckNode = RadialStatusNode(backgroundNodeColor: .clear)
|
self.iconCheckNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||||
|
self.iconCheckNode?.frame = CGRect(x: 0.0, y: 0.0, width: 24.0, height: 24.0)
|
||||||
self.animationNode = nil
|
self.animationNode = nil
|
||||||
} else {
|
} else {
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
@ -82,20 +83,32 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 5
|
||||||
case let .hidArchive(title, text, undo):
|
case let .hidArchive(title, text, undo):
|
||||||
self.iconNode = nil
|
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.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.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)
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||||
displayUndo = undo
|
displayUndo = undo
|
||||||
self.originalRemainingSeconds = 3
|
self.originalRemainingSeconds = 3
|
||||||
case let .revealedArchive(title, text, undo):
|
case let .revealedArchive(title, text, undo):
|
||||||
self.iconNode = nil
|
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.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.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)
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||||
displayUndo = undo
|
displayUndo = undo
|
||||||
self.originalRemainingSeconds = 3
|
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
|
self.remainingSeconds = self.originalRemainingSeconds
|
||||||
@ -127,7 +140,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
case .removedChat:
|
case .removedChat:
|
||||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||||
self.panelWrapperNode.addSubnode(self.statusNode)
|
self.panelWrapperNode.addSubnode(self.statusNode)
|
||||||
case .archivedChat, .hidArchive, .revealedArchive:
|
case .archivedChat, .hidArchive, .revealedArchive, .succeed:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
|
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
|
||||||
@ -266,8 +279,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
transition.updateFrame(node: iconNode, frame: iconFrame)
|
transition.updateFrame(node: iconNode, frame: iconFrame)
|
||||||
|
|
||||||
if let iconCheckNode = self.iconCheckNode {
|
if let iconCheckNode = self.iconCheckNode {
|
||||||
let statusSize: CGFloat = 24.0
|
let statusSize: CGFloat = iconCheckNode.frame.width
|
||||||
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)))
|
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