mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-19 07:03:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/TelegramUI
This commit is contained in:
commit
bdd47b456f
@ -2,17 +2,7 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"filename" : "add.pdf"
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "ic_addoption@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "ic_addoption@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
BIN
Images.xcassets/Item List/AddItemIcon.imageset/add.pdf
vendored
Normal file
BIN
Images.xcassets/Item List/AddItemIcon.imageset/add.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
@ -2,17 +2,7 @@
|
|||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"filename" : "delete.pdf"
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "ic_deleteotion@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "ic_deleteotion@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
BIN
Images.xcassets/Item List/RemoveItemIcon.imageset/delete.pdf
vendored
Normal file
BIN
Images.xcassets/Item List/RemoveItemIcon.imageset/delete.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
@ -72,7 +72,6 @@
|
|||||||
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
|
099529B021D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */; };
|
||||||
099529B221D24F5800805E13 /* RadialDownloadContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */; };
|
099529B221D24F5800805E13 /* RadialDownloadContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */; };
|
||||||
099529B421D3E5D800805E13 /* CheckDiskSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B321D3E5D800805E13 /* CheckDiskSpace.swift */; };
|
099529B421D3E5D800805E13 /* CheckDiskSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B321D3E5D800805E13 /* CheckDiskSpace.swift */; };
|
||||||
099529F321D8FE8300805E13 /* RadialPlayPauseContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529F221D8FE8300805E13 /* RadialPlayPauseContentNode.swift */; };
|
|
||||||
099529FA21DD8A3100805E13 /* NavigationBarSearchContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529F921DD8A3100805E13 /* NavigationBarSearchContentNode.swift */; };
|
099529FA21DD8A3100805E13 /* NavigationBarSearchContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529F921DD8A3100805E13 /* NavigationBarSearchContentNode.swift */; };
|
||||||
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
|
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
|
||||||
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */; };
|
09B4EE4721A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */; };
|
||||||
@ -111,6 +110,7 @@
|
|||||||
D005808B21CAB8F000CB7CD3 /* VoipDerivedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005808A21CAB8F000CB7CD3 /* VoipDerivedState.swift */; };
|
D005808B21CAB8F000CB7CD3 /* VoipDerivedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005808A21CAB8F000CB7CD3 /* VoipDerivedState.swift */; };
|
||||||
D00580A021DCF0A200CB7CD3 /* WallpaperListPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005809F21DCF0A200CB7CD3 /* WallpaperListPreviewController.swift */; };
|
D00580A021DCF0A200CB7CD3 /* WallpaperListPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005809F21DCF0A200CB7CD3 /* WallpaperListPreviewController.swift */; };
|
||||||
D00580A221DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00580A121DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift */; };
|
D00580A221DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00580A121DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift */; };
|
||||||
|
D00580B121E3DEF200CB7CD3 /* MergedItemListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00580B021E3DEF200CB7CD3 /* MergedItemListItem.swift */; };
|
||||||
D0068FA821760FA300D1B315 /* StoreDownloadedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */; };
|
D0068FA821760FA300D1B315 /* StoreDownloadedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */; };
|
||||||
D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */; };
|
D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */; };
|
||||||
D007019E2029EFDD006B9E34 /* ICloudResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019D2029EFDD006B9E34 /* ICloudResources.swift */; };
|
D007019E2029EFDD006B9E34 /* ICloudResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019D2029EFDD006B9E34 /* ICloudResources.swift */; };
|
||||||
@ -1180,7 +1180,6 @@
|
|||||||
099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageUnsupportedBubbleContentNode.swift; sourceTree = "<group>"; };
|
099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageUnsupportedBubbleContentNode.swift; sourceTree = "<group>"; };
|
||||||
099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialDownloadContentNode.swift; sourceTree = "<group>"; };
|
099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialDownloadContentNode.swift; sourceTree = "<group>"; };
|
||||||
099529B321D3E5D800805E13 /* CheckDiskSpace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckDiskSpace.swift; sourceTree = "<group>"; };
|
099529B321D3E5D800805E13 /* CheckDiskSpace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckDiskSpace.swift; sourceTree = "<group>"; };
|
||||||
099529F221D8FE8300805E13 /* RadialPlayPauseContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialPlayPauseContentNode.swift; sourceTree = "<group>"; };
|
|
||||||
099529F921DD8A3100805E13 /* NavigationBarSearchContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarSearchContentNode.swift; sourceTree = "<group>"; };
|
099529F921DD8A3100805E13 /* NavigationBarSearchContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarSearchContentNode.swift; sourceTree = "<group>"; };
|
||||||
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
|
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
|
||||||
09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSessionsEmptyStateItem.swift; sourceTree = "<group>"; };
|
09B4EE4621A6D33F00847FA6 /* RecentSessionsEmptyStateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSessionsEmptyStateItem.swift; sourceTree = "<group>"; };
|
||||||
@ -1230,6 +1229,7 @@
|
|||||||
D005808A21CAB8F000CB7CD3 /* VoipDerivedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipDerivedState.swift; sourceTree = "<group>"; };
|
D005808A21CAB8F000CB7CD3 /* VoipDerivedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipDerivedState.swift; sourceTree = "<group>"; };
|
||||||
D005809F21DCF0A200CB7CD3 /* WallpaperListPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperListPreviewController.swift; sourceTree = "<group>"; };
|
D005809F21DCF0A200CB7CD3 /* WallpaperListPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperListPreviewController.swift; sourceTree = "<group>"; };
|
||||||
D00580A121DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperListPreviewControllerNode.swift; sourceTree = "<group>"; };
|
D00580A121DCF0B700CB7CD3 /* WallpaperListPreviewControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperListPreviewControllerNode.swift; sourceTree = "<group>"; };
|
||||||
|
D00580B021E3DEF200CB7CD3 /* MergedItemListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergedItemListItem.swift; sourceTree = "<group>"; };
|
||||||
D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreDownloadedMedia.swift; sourceTree = "<group>"; };
|
D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreDownloadedMedia.swift; sourceTree = "<group>"; };
|
||||||
D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegqacyICloudFileController.swift; sourceTree = "<group>"; };
|
D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegqacyICloudFileController.swift; sourceTree = "<group>"; };
|
||||||
D007019D2029EFDD006B9E34 /* ICloudResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICloudResources.swift; sourceTree = "<group>"; };
|
D007019D2029EFDD006B9E34 /* ICloudResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICloudResources.swift; sourceTree = "<group>"; };
|
||||||
@ -2549,7 +2549,6 @@
|
|||||||
D01776B91F1D704F0044446D /* RadialStatusIconContentNode.swift */,
|
D01776B91F1D704F0044446D /* RadialStatusIconContentNode.swift */,
|
||||||
D0380DAA204EA72F000414AB /* RadialStatusSecretTimeoutContentNode.swift */,
|
D0380DAA204EA72F000414AB /* RadialStatusSecretTimeoutContentNode.swift */,
|
||||||
099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */,
|
099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */,
|
||||||
099529F221D8FE8300805E13 /* RadialPlayPauseContentNode.swift */,
|
|
||||||
);
|
);
|
||||||
name = "Radial Status";
|
name = "Radial Status";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3921,6 +3920,7 @@
|
|||||||
D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */,
|
D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */,
|
||||||
D09AEFD31E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift */,
|
D09AEFD31E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift */,
|
||||||
D0BFAE5A20AB35D200793CF2 /* IconSwitchNode.swift */,
|
D0BFAE5A20AB35D200793CF2 /* IconSwitchNode.swift */,
|
||||||
|
D00580B021E3DEF200CB7CD3 /* MergedItemListItem.swift */,
|
||||||
);
|
);
|
||||||
name = Items;
|
name = Items;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -5480,6 +5480,7 @@
|
|||||||
9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */,
|
9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */,
|
||||||
D0EC6D891EB9F58800EBF1C3 /* ChatSecretAutoremoveTimerActionSheet.swift in Sources */,
|
D0EC6D891EB9F58800EBF1C3 /* ChatSecretAutoremoveTimerActionSheet.swift in Sources */,
|
||||||
D05D8B782195E0050064586F /* SetupTwoStepVerificationContentNode.swift in Sources */,
|
D05D8B782195E0050064586F /* SetupTwoStepVerificationContentNode.swift in Sources */,
|
||||||
|
D00580B121E3DEF200CB7CD3 /* MergedItemListItem.swift in Sources */,
|
||||||
D0EC6D8A1EB9F58800EBF1C3 /* ChatInfo.swift in Sources */,
|
D0EC6D8A1EB9F58800EBF1C3 /* ChatInfo.swift in Sources */,
|
||||||
D0EC6D8B1EB9F58800EBF1C3 /* ChatHistoryNavigationStack.swift in Sources */,
|
D0EC6D8B1EB9F58800EBF1C3 /* ChatHistoryNavigationStack.swift in Sources */,
|
||||||
D0EC6D8C1EB9F58800EBF1C3 /* NavigateToChatController.swift in Sources */,
|
D0EC6D8C1EB9F58800EBF1C3 /* NavigateToChatController.swift in Sources */,
|
||||||
@ -5733,7 +5734,6 @@
|
|||||||
D0EC6E031EB9F58900EBF1C3 /* GalleryFooterContentNode.swift in Sources */,
|
D0EC6E031EB9F58900EBF1C3 /* GalleryFooterContentNode.swift in Sources */,
|
||||||
D0E9BA0A1F0457DD00F079A4 /* BotCheckoutWebInteractionController.swift in Sources */,
|
D0E9BA0A1F0457DD00F079A4 /* BotCheckoutWebInteractionController.swift in Sources */,
|
||||||
D0EC6E041EB9F58900EBF1C3 /* SecretMediaPreviewController.swift in Sources */,
|
D0EC6E041EB9F58900EBF1C3 /* SecretMediaPreviewController.swift in Sources */,
|
||||||
099529F321D8FE8300805E13 /* RadialPlayPauseContentNode.swift in Sources */,
|
|
||||||
D0C26D571FDF2388004ABF18 /* OpenChatMessage.swift in Sources */,
|
D0C26D571FDF2388004ABF18 /* OpenChatMessage.swift in Sources */,
|
||||||
D0FA08BE20481EA300DD23FC /* Locale.swift in Sources */,
|
D0FA08BE20481EA300DD23FC /* Locale.swift in Sources */,
|
||||||
D0E412CE206A707400BEE4A2 /* FormControllerTextItem.swift in Sources */,
|
D0E412CE206A707400BEE4A2 /* FormControllerTextItem.swift in Sources */,
|
||||||
|
|||||||
@ -66,6 +66,8 @@ final class BotPaymentSwitchItemNode: BotPaymentItemNode {
|
|||||||
self.switchNode.frameColor = theme.list.itemSwitchColors.frameColor
|
self.switchNode.frameColor = theme.list.itemSwitchColors.frameColor
|
||||||
self.switchNode.contentColor = theme.list.itemSwitchColors.contentColor
|
self.switchNode.contentColor = theme.list.itemSwitchColors.contentColor
|
||||||
self.switchNode.handleColor = theme.list.itemSwitchColors.handleColor
|
self.switchNode.handleColor = theme.list.itemSwitchColors.handleColor
|
||||||
|
self.switchNode.positiveContentColor = theme.list.itemSwitchColors.positiveColor
|
||||||
|
self.switchNode.negativeContentColor = theme.list.itemSwitchColors.negativeColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftInset: CGFloat = 16.0
|
let leftInset: CGFloat = 16.0
|
||||||
|
|||||||
@ -336,7 +336,7 @@ private func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: I
|
|||||||
let id = arc4random64()
|
let id = arc4random64()
|
||||||
let name = "\(callId.id)_\(callId.accessHash).log"
|
let name = "\(callId.id)_\(callId.accessHash).log"
|
||||||
let path = callLogsPath(account: account) + "/" + name
|
let path = callLogsPath(account: account) + "/" + name
|
||||||
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
||||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
||||||
|
|
||||||
return rateSignal
|
return rateSignal
|
||||||
|
|||||||
@ -677,38 +677,50 @@ public func channelAdminController(account: Account, peerId: PeerId, adminId: Pe
|
|||||||
updateFlags = defaultFlags
|
updateFlags = defaultFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
if let updateFlags = updateFlags, updateFlags != defaultFlags {
|
if let updateFlags = updateFlags {
|
||||||
let signal = convertGroupToSupergroup(account: account, peerId: peerId)
|
if initialParticipant?.adminInfo == nil && updateFlags == defaultFlags {
|
||||||
|> map(Optional.init)
|
updateState { current in
|
||||||
|> `catch` { _ -> Signal<PeerId?, NoError> in
|
return current.withUpdatedUpdating(true)
|
||||||
return .single(nil)
|
}
|
||||||
}
|
updateRightsDisposable.set((addGroupAdmin(account: account, peerId: peerId, adminId: adminId)
|
||||||
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
|
|> deliverOnMainQueue).start(completed: {
|
||||||
guard let upgradedPeerId = upgradedPeerId else {
|
dismissImpl?()
|
||||||
|
}))
|
||||||
|
} else if updateFlags != defaultFlags {
|
||||||
|
let signal = convertGroupToSupergroup(account: account, peerId: peerId)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<PeerId?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: account, peerId: upgradedPeerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: updateFlags))
|
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
|
||||||
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
|
guard let upgradedPeerId = upgradedPeerId else {
|
||||||
return .complete()
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: account, peerId: upgradedPeerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: updateFlags))
|
||||||
|
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> then(.single(upgradedPeerId))
|
||||||
}
|
}
|
||||||
|> then(.single(upgradedPeerId))
|
|> deliverOnMainQueue
|
||||||
}
|
|
||||||
|> deliverOnMainQueue
|
|
||||||
|
|
||||||
updateState { current in
|
|
||||||
return current.withUpdatedUpdating(true)
|
|
||||||
}
|
|
||||||
updateRightsDisposable.set(signal.start(next: { upgradedPeerId in
|
|
||||||
if let upgradedPeerId = upgradedPeerId {
|
|
||||||
upgradedToSupergroup(upgradedPeerId, {
|
|
||||||
dismissImpl?()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, error: { _ in
|
|
||||||
updateState { current in
|
updateState { current in
|
||||||
return current.withUpdatedUpdating(false)
|
return current.withUpdatedUpdating(true)
|
||||||
}
|
}
|
||||||
}))
|
updateRightsDisposable.set(signal.start(next: { upgradedPeerId in
|
||||||
|
if let upgradedPeerId = upgradedPeerId {
|
||||||
|
upgradedToSupergroup(upgradedPeerId, {
|
||||||
|
dismissImpl?()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
updateState { current in
|
||||||
|
return current.withUpdatedUpdating(false)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dismissImpl?()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -490,6 +490,9 @@ public func channelAdminsController(account: Account, peerId: PeerId, loadComple
|
|||||||
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let peerView = Promise<PeerView>()
|
||||||
|
peerView.set(account.viewTracker.peerView(peerId))
|
||||||
|
|
||||||
let arguments = ChannelAdminsControllerArguments(account: account, openRecentActions: {
|
let arguments = ChannelAdminsControllerArguments(account: account, openRecentActions: {
|
||||||
let _ = (account.postbox.loadedPeerWithId(peerId)
|
let _ = (account.postbox.loadedPeerWithId(peerId)
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
@ -526,21 +529,12 @@ public func channelAdminsController(account: Account, peerId: PeerId, loadComple
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}, addAdmin: {
|
}, addAdmin: {
|
||||||
updateState { current in
|
let _ = (peerView.get()
|
||||||
var dismissController: (() -> Void)?
|
|> take(1)
|
||||||
let controller = ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in
|
|> deliverOnMainQueue).start(next: { peerView in
|
||||||
if peerId.namespace == Namespaces.Peer.CloudGroup {
|
updateState { current in
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
var dismissController: (() -> Void)?
|
||||||
let progress = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
let controller = ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in
|
||||||
presentControllerImpl?(progress, nil)
|
|
||||||
|
|
||||||
addAdminDisposable.set((addGroupAdmin(account: account, peerId: peerId, adminId: peer.id)
|
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|
||||||
[weak progress] in
|
|
||||||
dismissController?()
|
|
||||||
progress?.dismiss()
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
dismissController?()
|
dismissController?()
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
if peer.id == account.peerId {
|
if peer.id == account.peerId {
|
||||||
@ -551,32 +545,39 @@ public func channelAdminsController(account: Account, peerId: PeerId, loadComple
|
|||||||
case .creator:
|
case .creator:
|
||||||
return
|
return
|
||||||
case let .member(_, _, _, banInfo):
|
case let .member(_, _, _, banInfo):
|
||||||
if let banInfo = banInfo, banInfo.restrictedBy != account.peerId {
|
if let banInfo = banInfo {
|
||||||
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Channel_Members_AddAdminErrorBlacklisted, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
var canUnban = false
|
||||||
return
|
if banInfo.restrictedBy != account.peerId {
|
||||||
|
canUnban = true
|
||||||
|
}
|
||||||
|
if let channel = peerView.peers[peerId] as? TelegramChannel {
|
||||||
|
if channel.hasPermission(.banMembers) {
|
||||||
|
canUnban = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !canUnban {
|
||||||
|
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Channel_Members_AddAdminErrorBlacklisted, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in
|
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: peer.id, initialParticipant: participant?.participant, updated: { _ in
|
||||||
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
})
|
||||||
|
dismissController = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
}
|
}
|
||||||
})
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
dismissController = { [weak controller] in
|
|
||||||
controller?.dismiss()
|
return current
|
||||||
}
|
}
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
})
|
||||||
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
}, openAdmin: { participant in
|
}, openAdmin: { participant in
|
||||||
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
|
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
|
||||||
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
})
|
})
|
||||||
|
|
||||||
let peerView = Promise<PeerView>()
|
|
||||||
peerView.set(account.viewTracker.peerView(peerId))
|
|
||||||
|
|
||||||
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
|
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
var didReportLoadCompleted = false
|
var didReportLoadCompleted = false
|
||||||
|
|||||||
@ -510,7 +510,7 @@ public func channelBannedMemberController(account: Account, peerId: PeerId, memb
|
|||||||
if state.updating {
|
if state.updating {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
} else {
|
} else {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.GroupPermission_ApplyAlertAction), style: .bold, enabled: true, action: {
|
||||||
let _ = (peerView.get()
|
let _ = (peerView.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { view in
|
|> deliverOnMainQueue).start(next: { view in
|
||||||
|
|||||||
@ -301,15 +301,15 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
})
|
})
|
||||||
case let .admins(theme, text, value):
|
case let .admins(theme, text, value):
|
||||||
return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesChat.groupInfoAdminsIcon(theme), title: text, label: value, sectionId: self.section, style: .plain, action: {
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
||||||
arguments.openAdmins()
|
arguments.openAdmins()
|
||||||
})
|
})
|
||||||
case let .members(theme, text, value):
|
case let .members(theme, text, value):
|
||||||
return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesChat.groupInfoMembersIcon(theme), title: text, label: value, sectionId: self.section, style: .plain, action: {
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
||||||
arguments.openMembers()
|
arguments.openMembers()
|
||||||
})
|
})
|
||||||
case let .banned(theme, text, value):
|
case let .banned(theme, text, value):
|
||||||
return ItemListDisclosureItem(theme: theme, icon: PresentationResourcesChat.groupInfoBannedIcon(theme), title: text, label: value, sectionId: self.section, style: .plain, action: {
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
||||||
arguments.openBanned()
|
arguments.openBanned()
|
||||||
})
|
})
|
||||||
case let .signMessages(theme, text, value):
|
case let .signMessages(theme, text, value):
|
||||||
@ -451,7 +451,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let cachedChannelData = view.cachedData as? CachedChannelData {
|
if let cachedChannelData = view.cachedData as? CachedChannelData {
|
||||||
if state.editingState != nil && canEditMembers {
|
if canEditMembers {
|
||||||
if peer.adminRights != nil || peer.flags.contains(.isCreator) {
|
if peer.adminRights != nil || peer.flags.contains(.isCreator) {
|
||||||
let adminCount = cachedChannelData.participantsSummary.adminCount ?? 0
|
let adminCount = cachedChannelData.participantsSummary.adminCount ?? 0
|
||||||
entries.append(.admins(theme: presentationData.theme, text: presentationData.strings.GroupInfo_Administrators, value: "\(adminCount == 0 ? "" : "\(adminCount)")"))
|
entries.append(.admins(theme: presentationData.theme, text: presentationData.strings.GroupInfo_Administrators, value: "\(adminCount == 0 ? "" : "\(adminCount)")"))
|
||||||
@ -473,6 +473,8 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
|
|||||||
} else {
|
} else {
|
||||||
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
|
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
|
||||||
}
|
}
|
||||||
|
} else if case .default = notificationSettings.messageSound {
|
||||||
|
notificationsText = presentationData.strings.UserInfo_NotificationsEnabled
|
||||||
} else {
|
} else {
|
||||||
notificationsText = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
notificationsText = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
||||||
}
|
}
|
||||||
@ -794,6 +796,11 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var canEditChannel = false
|
||||||
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
||||||
|
canEditChannel = peer.hasPermission(.changeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
var leftNavigationButton: ItemListNavigationButton?
|
var leftNavigationButton: ItemListNavigationButton?
|
||||||
var rightNavigationButton: ItemListNavigationButton?
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
if let editingState = state.editingState {
|
if let editingState = state.editingState {
|
||||||
@ -856,7 +863,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else if canEditChannel {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
||||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
var text = ""
|
var text = ""
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
private let initialBatchSize: Int32 = 64
|
private let initialBatchSize: Int32 = 64
|
||||||
private let emptyTimeout: Double = 2.0 * 60.0
|
private let defaultEmptyTimeout: Double = 2.0 * 60.0
|
||||||
private let headUpdateTimeout: Double = 30.0
|
private let headUpdateTimeout: Double = 30.0
|
||||||
private let requestBatchSize: Int32 = 64
|
private let requestBatchSize: Int32 = 64
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
var listStateValue: ChannelMemberListState {
|
var listStateValue: ChannelMemberListState {
|
||||||
didSet {
|
didSet {
|
||||||
self.listStatePromise.set(.single(self.listStateValue))
|
self.listStatePromise.set(.single(self.listStateValue))
|
||||||
if case .admins = self.category, case .ready = self.listStateValue.loadingState {
|
if case .admins(nil) = self.category, case .ready = self.listStateValue.loadingState {
|
||||||
let ids: Set<PeerId> = Set(self.listStateValue.list.map { $0.peer.id })
|
let ids: Set<PeerId> = Set(self.listStateValue.list.map { $0.peer.id })
|
||||||
let previousIds: Set<PeerId> = Set(oldValue.list.map { $0.peer.id })
|
let previousIds: Set<PeerId> = Set(oldValue.list.map { $0.peer.id })
|
||||||
if ids != previousIds {
|
if ids != previousIds {
|
||||||
@ -288,8 +288,8 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch self.category {
|
switch self.category {
|
||||||
case .admins:
|
case let .admins(query):
|
||||||
if let updated = updated, let _ = updated.participant.adminInfo {
|
if let updated = updated, let _ = updated.participant.adminInfo, (query == nil || updated.peer.indexName.matchesByTokens(query!)) {
|
||||||
var found = false
|
var found = false
|
||||||
loop: for i in 0 ..< list.count {
|
loop: for i in 0 ..< list.count {
|
||||||
if list[i].peer.id == updated.peer.id {
|
if list[i].peer.id == updated.peer.id {
|
||||||
@ -512,14 +512,16 @@ struct PeerChannelMemberCategoryControl {
|
|||||||
|
|
||||||
private final class PeerChannelMemberContextWithSubscribers {
|
private final class PeerChannelMemberContextWithSubscribers {
|
||||||
let context: ChannelMemberCategoryListContext
|
let context: ChannelMemberCategoryListContext
|
||||||
|
private let emptyTimeout: Double
|
||||||
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
|
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
private let becameEmpty: () -> Void
|
private let becameEmpty: () -> Void
|
||||||
|
|
||||||
private var emptyTimer: SwiftSignalKit.Timer?
|
private var emptyTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
init(context: ChannelMemberCategoryListContext, becameEmpty: @escaping () -> Void) {
|
init(context: ChannelMemberCategoryListContext, emptyTimeout: Double, becameEmpty: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.emptyTimeout = emptyTimeout
|
||||||
self.becameEmpty = becameEmpty
|
self.becameEmpty = becameEmpty
|
||||||
self.disposable.set((context.listState
|
self.disposable.set((context.listState
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
@ -539,7 +541,7 @@ private final class PeerChannelMemberContextWithSubscribers {
|
|||||||
private func resetAndBeginEmptyTimer() {
|
private func resetAndBeginEmptyTimer() {
|
||||||
self.context.reset(false)
|
self.context.reset(false)
|
||||||
self.emptyTimer?.invalidate()
|
self.emptyTimer?.invalidate()
|
||||||
let emptyTimer = SwiftSignalKit.Timer(timeout: emptyTimeout, repeat: false, completion: { [weak self] in
|
let emptyTimer = SwiftSignalKit.Timer(timeout: self.emptyTimeout, repeat: false, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if strongSelf.subscribers.isEmpty {
|
if strongSelf.subscribers.isEmpty {
|
||||||
strongSelf.becameEmpty()
|
strongSelf.becameEmpty()
|
||||||
@ -605,6 +607,13 @@ final class PeerChannelMemberCategoriesContext {
|
|||||||
return (current.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
|
return (current.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
|
||||||
}
|
}
|
||||||
let context: ChannelMemberCategoryListContext
|
let context: ChannelMemberCategoryListContext
|
||||||
|
let emptyTimeout: Double
|
||||||
|
switch key {
|
||||||
|
case .admins(nil), .banned(nil), .recentSearch(nil), .restricted(nil), .restrictedAndBanned(nil):
|
||||||
|
emptyTimeout = defaultEmptyTimeout
|
||||||
|
default:
|
||||||
|
emptyTimeout = 0.0
|
||||||
|
}
|
||||||
switch key {
|
switch key {
|
||||||
case .recent, .recentSearch, .admins:
|
case .recent, .recentSearch, .admins:
|
||||||
let mappedCategory: ChannelMemberListCategory
|
let mappedCategory: ChannelMemberListCategory
|
||||||
@ -626,7 +635,7 @@ final class PeerChannelMemberCategoriesContext {
|
|||||||
case let .banned(query):
|
case let .banned(query):
|
||||||
context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: .banned(query))
|
context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: .banned(query))
|
||||||
}
|
}
|
||||||
let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, becameEmpty: { [weak self] in
|
let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, emptyTimeout: emptyTimeout, becameEmpty: { [weak self] in
|
||||||
assert(Queue.mainQueue().isCurrent())
|
assert(Queue.mainQueue().isCurrent())
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contexts.removeValue(forKey: key)
|
strongSelf.contexts.removeValue(forKey: key)
|
||||||
|
|||||||
@ -336,40 +336,40 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
|
|||||||
let disabledIds = members?.compactMap({$0.peer.id}) ?? []
|
let disabledIds = members?.compactMap({$0.peer.id}) ?? []
|
||||||
let contactsController = ContactMultiselectionController(account: account, mode: .peerSelection(searchChatList: false), options: [], filters: [.excludeSelf, .disable(disabledIds)])
|
let contactsController = ContactMultiselectionController(account: account, mode: .peerSelection(searchChatList: false), options: [], filters: [.excludeSelf, .disable(disabledIds)])
|
||||||
|
|
||||||
let addMembers: ([ContactListPeerId]) -> Signal<Void, AddChannelMemberError> = { members -> Signal<Void, AddChannelMemberError> in
|
|
||||||
let peerIds = members.compactMap { contact -> PeerId? in
|
|
||||||
switch contact {
|
|
||||||
case let .peer(peerId):
|
|
||||||
return peerId
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMembers(account: account, peerId: peerId, memberIds: peerIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
addMembersDisposable.set((contactsController.result
|
addMembersDisposable.set((contactsController.result
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> mapToSignal { [weak contactsController] contacts -> Signal<Never, NoError> in
|
|> introduceError(AddChannelMemberError.self)
|
||||||
|
|> mapToSignal { [weak contactsController] contacts -> Signal<Never, AddChannelMemberError> in
|
||||||
contactsController?.displayProgress = true
|
contactsController?.displayProgress = true
|
||||||
|
|
||||||
let signals = contacts.compactMap({ contact -> Signal<Never, NoError>? in
|
let signal = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMembers(account: account, peerId: peerId, memberIds: contacts.compactMap({ contact -> PeerId? in
|
||||||
switch contact {
|
switch contact {
|
||||||
case let .peer(contactId):
|
case let .peer(contactId):
|
||||||
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMember(account: account, peerId: peerId, memberId: contactId)
|
return contactId
|
||||||
|> ignoreValues
|
default:
|
||||||
case .deviceContact:
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
return combineLatest(signals)
|
return signal
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> afterCompleted {
|
|> afterCompleted {
|
||||||
contactsController?.dismiss()
|
contactsController?.dismiss()
|
||||||
}
|
}
|
||||||
}).start())
|
}).start(error: { error in
|
||||||
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
let text: String
|
||||||
|
switch error {
|
||||||
|
case .limitExceeded:
|
||||||
|
text = presentationData.strings.Channel_ErrorAddTooMuch
|
||||||
|
case .generic:
|
||||||
|
text = presentationData.strings.Login_UnknownError
|
||||||
|
case .restricted:
|
||||||
|
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||||
|
}
|
||||||
|
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
}))
|
||||||
|
|
||||||
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -260,6 +260,19 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
if case .searchAdmins = mode {
|
||||||
|
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: account, peerId: peerId, memberId: memberId, adminRights: TelegramChatAdminRights(flags: []))
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.removingParticipantIds.remove(memberId)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||||
|> afterDisposed {
|
|> afterDisposed {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@ -272,6 +285,21 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case .searchAdmins = mode {
|
||||||
|
return removeGroupAdmin(account: account, peerId: peerId, adminId: memberId)
|
||||||
|
|> `catch` { _ -> Signal<Void, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> afterDisposed {
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.removingParticipantIds.remove(memberId)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return removePeerMember(account: account, peerId: peerId, memberId: memberId)
|
return removePeerMember(account: account, peerId: peerId, memberId: memberId)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> afterDisposed {
|
|> afterDisposed {
|
||||||
@ -320,11 +348,10 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in
|
||||||
if case .ready = state.loadingState {
|
if case .ready = state.loadingState {
|
||||||
subscriber.putNext(state.list)
|
subscriber.putNext(state.list)
|
||||||
subscriber.putCompletion()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return disposable
|
return disposable
|
||||||
} |> runOn(Queue.mainQueue())
|
} |> runOn(Queue.mainQueue())
|
||||||
foundMembers = .single([])
|
foundMembers = .single([])
|
||||||
case .searchBanned:
|
case .searchBanned:
|
||||||
foundGroupMembers = Signal { subscriber in
|
foundGroupMembers = Signal { subscriber in
|
||||||
@ -473,6 +500,10 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
|||||||
peerActions.append(ParticipantRevealAction(type: .warning, title: themeAndStrings.1.GroupInfo_ActionRestrict, action: .restrict))
|
peerActions.append(ParticipantRevealAction(type: .warning, title: themeAndStrings.1.GroupInfo_ActionRestrict, action: .restrict))
|
||||||
peerActions.append(ParticipantRevealAction(type: .destructive, title: themeAndStrings.1.Common_Delete, action: .remove))
|
peerActions.append(ParticipantRevealAction(type: .destructive, title: themeAndStrings.1.Common_Delete, action: .remove))
|
||||||
}
|
}
|
||||||
|
} else if case .searchAdmins = mode {
|
||||||
|
if canRestrict {
|
||||||
|
peerActions.append(ParticipantRevealAction(type: .destructive, title: themeAndStrings.1.Common_Delete, action: .remove))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
|
|||||||
@ -14,8 +14,9 @@ private final class ChannelPermissionsControllerArguments {
|
|||||||
let openPeer: (ChannelParticipant) -> Void
|
let openPeer: (ChannelParticipant) -> Void
|
||||||
let openPeerInfo: (Peer) -> Void
|
let openPeerInfo: (Peer) -> Void
|
||||||
let openKicked: () -> Void
|
let openKicked: () -> Void
|
||||||
|
let presentRestrictedPublicGroupPermissionsAlert: () -> Void
|
||||||
|
|
||||||
init(account: Account, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (Peer) -> Void, openKicked: @escaping () -> Void) {
|
init(account: Account, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (Peer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPublicGroupPermissionsAlert: @escaping () -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.updatePermission = updatePermission
|
self.updatePermission = updatePermission
|
||||||
self.addPeer = addPeer
|
self.addPeer = addPeer
|
||||||
@ -24,6 +25,7 @@ private final class ChannelPermissionsControllerArguments {
|
|||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.openPeerInfo = openPeerInfo
|
self.openPeerInfo = openPeerInfo
|
||||||
self.openKicked = openKicked
|
self.openKicked = openKicked
|
||||||
|
self.presentRestrictedPublicGroupPermissionsAlert = presentRestrictedPublicGroupPermissionsAlert
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ private enum ChannelPermissionsEntryStableId: Hashable {
|
|||||||
|
|
||||||
private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
||||||
case permissionsHeader(PresentationTheme, String)
|
case permissionsHeader(PresentationTheme, String)
|
||||||
case permission(PresentationTheme, Int, String, Bool, TelegramChatBannedRightsFlags, Bool)
|
case permission(PresentationTheme, Int, String, Bool, TelegramChatBannedRightsFlags, Bool?)
|
||||||
case kicked(PresentationTheme, String, String)
|
case kicked(PresentationTheme, String, String)
|
||||||
case exceptionsHeader(PresentationTheme, String)
|
case exceptionsHeader(PresentationTheme, String)
|
||||||
case add(PresentationTheme, String)
|
case add(PresentationTheme, String)
|
||||||
@ -173,8 +175,12 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
|
|||||||
case let .permissionsHeader(theme, text):
|
case let .permissionsHeader(theme, text):
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||||
case let .permission(theme, _, title, value, rights, enabled):
|
case let .permission(theme, _, title, value, rights, enabled):
|
||||||
return ItemListSwitchItem(theme: theme, title: title, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(theme: theme, title: title, value: value, type: .icon, enableInteractiveChanges: enabled != nil, enabled: enabled ?? true, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
arguments.updatePermission(rights, value)
|
if let _ = enabled {
|
||||||
|
arguments.updatePermission(rights, value)
|
||||||
|
} else {
|
||||||
|
arguments.presentRestrictedPublicGroupPermissionsAlert()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
case let .kicked(theme, text, value):
|
case let .kicked(theme, text, value):
|
||||||
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||||
@ -341,9 +347,9 @@ private func channelPermissionsControllerEntries(presentationData: PresentationD
|
|||||||
entries.append(.permissionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SectionTitle))
|
entries.append(.permissionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SectionTitle))
|
||||||
var rightIndex: Int = 0
|
var rightIndex: Int = 0
|
||||||
for rights in allGroupPermissionList {
|
for rights in allGroupPermissionList {
|
||||||
var enabled = true
|
var enabled: Bool? = true
|
||||||
if channel.addressName != nil {
|
if channel.addressName != nil && publicGroupRestrictedPermissions.contains(rights) {
|
||||||
enabled = !publicGroupRestrictedPermissions.contains(rights)
|
enabled = nil
|
||||||
}
|
}
|
||||||
entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights), !effectiveRightsFlags.contains(rights), rights, enabled))
|
entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights), !effectiveRightsFlags.contains(rights), rights, enabled))
|
||||||
rightIndex += 1
|
rightIndex += 1
|
||||||
@ -358,7 +364,7 @@ private func channelPermissionsControllerEntries(presentationData: PresentationD
|
|||||||
entries.append(.peerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, ItemListPeerItemEditing(editable: true, editing: false, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, true, effectiveRightsFlags))
|
entries.append(.peerItem(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index, participant, ItemListPeerItemEditing(editable: true, editing: false, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id, true, effectiveRightsFlags))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
} else if let group = view.peers[view.peerId] as? TelegramGroup, let cachedData = view.cachedData as? CachedGroupData, let defaultBannedRights = group.defaultBannedRights {
|
} else if let group = view.peers[view.peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData, let defaultBannedRights = group.defaultBannedRights {
|
||||||
let effectiveRightsFlags: TelegramChatBannedRightsFlags
|
let effectiveRightsFlags: TelegramChatBannedRightsFlags
|
||||||
if let modifiedRightsFlags = state.modifiedRightsFlags {
|
if let modifiedRightsFlags = state.modifiedRightsFlags {
|
||||||
effectiveRightsFlags = modifiedRightsFlags
|
effectiveRightsFlags = modifiedRightsFlags
|
||||||
@ -436,7 +442,7 @@ public func channelPermissionsController(account: Account, peerId: PeerId, loadC
|
|||||||
let _ = (peerView.get()
|
let _ = (peerView.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { view in
|
|> deliverOnMainQueue).start(next: { view in
|
||||||
if let channel = view.peers[peerId] as? TelegramChannel, let cachedData = view.cachedData as? CachedChannelData {
|
if let channel = view.peers[peerId] as? TelegramChannel, let _ = view.cachedData as? CachedChannelData {
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
var effectiveRightsFlags: TelegramChatBannedRightsFlags
|
var effectiveRightsFlags: TelegramChatBannedRightsFlags
|
||||||
@ -466,7 +472,7 @@ public func channelPermissionsController(account: Account, peerId: PeerId, loadC
|
|||||||
updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: account, peerId: peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|
updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: account, peerId: peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max))
|
||||||
|> deliverOnMainQueue).start())
|
|> deliverOnMainQueue).start())
|
||||||
}
|
}
|
||||||
} else if let group = view.peers[peerId] as? TelegramGroup, let cachedData = view.cachedData as? CachedGroupData {
|
} else if let group = view.peers[peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData {
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
var effectiveRightsFlags: TelegramChatBannedRightsFlags
|
var effectiveRightsFlags: TelegramChatBannedRightsFlags
|
||||||
@ -566,6 +572,9 @@ public func channelPermissionsController(account: Account, peerId: PeerId, loadC
|
|||||||
}
|
}
|
||||||
}, openKicked: {
|
}, openKicked: {
|
||||||
pushControllerImpl?(channelBlacklistController(account: account, peerId: peerId))
|
pushControllerImpl?(channelBlacklistController(account: account, peerId: peerId))
|
||||||
|
}, presentRestrictedPublicGroupPermissionsAlert: {
|
||||||
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.GroupPermission_NotAvailableInPublicGroups, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
let previousParticipants = Atomic<[RenderedChannelParticipant]?>(value: nil)
|
let previousParticipants = Atomic<[RenderedChannelParticipant]?>(value: nil)
|
||||||
|
|||||||
@ -158,7 +158,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
|
|||||||
imageDimensions = content?.dimensions
|
imageDimensions = content?.dimensions
|
||||||
if let content = content, type == "gif", let thumbnailResource = imageResource
|
if let content = content, type == "gif", let thumbnailResource = imageResource
|
||||||
, let dimensions = content.dimensions {
|
, let dimensions = content.dimensions {
|
||||||
videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
|
videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
|
||||||
imageResource = nil
|
imageResource = nil
|
||||||
}
|
}
|
||||||
case let .internalReference(_, _, _, title, _, image, file, _):
|
case let .internalReference(_, _, _, title, _, image, file, _):
|
||||||
|
|||||||
@ -207,6 +207,8 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
|
|
||||||
private weak var silentPostTooltipController: TooltipController?
|
private weak var silentPostTooltipController: TooltipController?
|
||||||
private weak var mediaRecordingModeTooltipController: TooltipController?
|
private weak var mediaRecordingModeTooltipController: TooltipController?
|
||||||
|
private weak var mediaRestrictedTooltipController: TooltipController?
|
||||||
|
private var mediaRestrictedTooltipControllerMode = true
|
||||||
|
|
||||||
private var screenCaptureEventsDisposable: Disposable?
|
private var screenCaptureEventsDisposable: Disposable?
|
||||||
private let chatAdditionalDataDisposable = MetaDisposable()
|
private let chatAdditionalDataDisposable = MetaDisposable()
|
||||||
@ -2578,8 +2580,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
let banDescription: String
|
let banDescription: String
|
||||||
switch subject {
|
switch subject {
|
||||||
case .stickers:
|
case .stickers:
|
||||||
if personal {
|
if untilDate != 0 && untilDate != Int32.max {
|
||||||
banDescription = strongSelf.presentationInterfaceState.strings.Group_ErrorSendRestrictedStickers
|
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedStickersTimed(stringForFullDate(timestamp: untilDate, strings: strongSelf.presentationInterfaceState.strings, dateTimeFormat: strongSelf.presentationInterfaceState.dateTimeFormat)).0
|
||||||
|
} else if personal {
|
||||||
|
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_RestrictedStickers
|
||||||
} else {
|
} else {
|
||||||
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedStickers
|
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedStickers
|
||||||
}
|
}
|
||||||
@ -2600,6 +2604,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
strongSelf.recordingModeFeedback?.error()
|
strongSelf.recordingModeFeedback?.error()
|
||||||
|
|
||||||
let rect: CGRect?
|
let rect: CGRect?
|
||||||
|
let isStickers: Bool = subject == .stickers
|
||||||
switch subject {
|
switch subject {
|
||||||
case .stickers:
|
case .stickers:
|
||||||
rect = strongSelf.chatDisplayNode.frameForStickersButton()
|
rect = strongSelf.chatDisplayNode.frameForStickersButton()
|
||||||
@ -2607,14 +2612,16 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tooltipController = strongSelf.mediaRecordingModeTooltipController {
|
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
|
||||||
tooltipController.text = banDescription
|
tooltipController.text = banDescription
|
||||||
} else if let rect = rect {
|
} else if let rect = rect {
|
||||||
|
strongSelf.mediaRestrictedTooltipController?.dismiss()
|
||||||
let tooltipController = TooltipController(text: banDescription)
|
let tooltipController = TooltipController(text: banDescription)
|
||||||
strongSelf.mediaRecordingModeTooltipController = tooltipController
|
strongSelf.mediaRestrictedTooltipController = tooltipController
|
||||||
|
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
|
||||||
tooltipController.dismissed = { [weak tooltipController] in
|
tooltipController.dismissed = { [weak tooltipController] in
|
||||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController {
|
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
|
||||||
strongSelf.mediaRecordingModeTooltipController = nil
|
strongSelf.mediaRestrictedTooltipController = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||||
@ -3739,10 +3746,21 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
|
|
||||||
var bannedSendMedia: (Int32, Bool)?
|
var bannedSendMedia: (Int32, Bool)?
|
||||||
if let channel = peer as? TelegramChannel, let value = channel.hasBannedPermission(.banSendMedia) {
|
var canSendPolls = true
|
||||||
bannedSendMedia = value
|
if let channel = peer as? TelegramChannel {
|
||||||
} else if let group = peer as? TelegramGroup, group.hasBannedPermission(.banSendMedia) {
|
if let value = channel.hasBannedPermission(.banSendMedia) {
|
||||||
bannedSendMedia = (Int32.max, false)
|
bannedSendMedia = value
|
||||||
|
}
|
||||||
|
if channel.hasBannedPermission(.banSendPolls) != nil {
|
||||||
|
canSendPolls = false
|
||||||
|
}
|
||||||
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendMedia) {
|
||||||
|
bannedSendMedia = (Int32.max, false)
|
||||||
|
}
|
||||||
|
if group.hasBannedPermission(.banSendPolls) {
|
||||||
|
canSendPolls = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if editMediaOptions == nil, let (untilDate, personal) = bannedSendMedia {
|
if editMediaOptions == nil, let (untilDate, personal) = bannedSendMedia {
|
||||||
@ -3756,17 +3774,23 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
|
|
||||||
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
var items: [ActionSheetItem] = []
|
||||||
ActionSheetTextItem(title: banDescription),
|
items.append(ActionSheetTextItem(title: banDescription))
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Location, color: .accent, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Location, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
self?.presentMapPicker(editingMessage: false)
|
||||||
|
}))
|
||||||
|
if canSendPolls {
|
||||||
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.AttachmentMenu_Poll, color: .accent, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
self?.presentMapPicker(editingMessage: false)
|
self?.presentPollCreation()
|
||||||
}),
|
}))
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in
|
}
|
||||||
actionSheet?.dismissAnimated()
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in
|
||||||
self?.presentContactPicker()
|
actionSheet?.dismissAnimated()
|
||||||
})
|
self?.presentContactPicker()
|
||||||
]), ActionSheetItemGroup(items: [
|
}))
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
})
|
})
|
||||||
@ -3885,7 +3909,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
for item in results {
|
for item in results {
|
||||||
if let item = item {
|
if let item = item {
|
||||||
let fileId = arc4random64()
|
let fileId = arc4random64()
|
||||||
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData), previewRepresentations: [], mimeType: guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension), size: item.fileSize, attributes: [.FileName(fileName: item.fileName)])
|
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData), previewRepresentations: [], immediateThumbnailData: nil, mimeType: guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension), size: item.fileSize, attributes: [.FileName(fileName: item.fileName)])
|
||||||
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: replyMessageId, localGroupingKey: nil)
|
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: replyMessageId, localGroupingKey: nil)
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
}
|
}
|
||||||
@ -4275,7 +4299,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
|
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
|
||||||
fileAttributes.append(.ImageSize(size: size))
|
fileAttributes.append(.ImageSize(size: size))
|
||||||
|
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: [], mimeType: "image/webp", size: data.count, attributes: fileAttributes)
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "image/webp", size: data.count, attributes: fileAttributes)
|
||||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil)
|
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil)
|
||||||
|
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
@ -4444,7 +4468,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], mimeType: "audio/ogg", size: data.compressedData.count, attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: data.compressedData.count, attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
||||||
|
|
||||||
strongSelf.recorderFeedback?.tap()
|
strongSelf.recorderFeedback?.tap()
|
||||||
strongSelf.recorderFeedback = nil
|
strongSelf.recorderFeedback = nil
|
||||||
@ -4515,7 +4539,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: recordedMediaPreview.resource, previewRepresentations: [], mimeType: "audio/ogg", size: Int(recordedMediaPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedMediaPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
self.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: recordedMediaPreview.resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int(recordedMediaPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedMediaPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,35 @@ func chatControllerBackgroundImage(wallpaper: TelegramWallpaper, postbox: Postbo
|
|||||||
return backgroundImage
|
return backgroundImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func serviceColor(for data: Signal<MediaResourceData, NoError>) -> Signal<UIColor, NoError> {
|
||||||
|
return data
|
||||||
|
|> mapToSignal { data -> Signal<UIColor, NoError> in
|
||||||
|
if data.complete {
|
||||||
|
let image = UIImage(contentsOfFile: data.path)
|
||||||
|
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
|
||||||
|
context.withFlippedContext({ context in
|
||||||
|
if let cgImage = image?.cgImage {
|
||||||
|
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var color = context.colorAt(CGPoint())
|
||||||
|
|
||||||
|
var hue: CGFloat = 0.0
|
||||||
|
var saturation: CGFloat = 0.0
|
||||||
|
var brightness: CGFloat = 0.0
|
||||||
|
var alpha: CGFloat = 0.0
|
||||||
|
if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
|
||||||
|
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
|
||||||
|
brightness = max(0.0, brightness * 0.65)
|
||||||
|
alpha = 0.4
|
||||||
|
color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
||||||
|
}
|
||||||
|
return .single(color)
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox) -> Signal<UIColor, NoError> {
|
func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox) -> Signal<UIColor, NoError> {
|
||||||
if wallpaper == serviceBackgroundColorForWallpaper?.0, let color = serviceBackgroundColorForWallpaper?.1 {
|
if wallpaper == serviceBackgroundColorForWallpaper?.0, let color = serviceBackgroundColorForWallpaper?.1 {
|
||||||
return .single(color)
|
return .single(color)
|
||||||
@ -51,32 +80,7 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox)
|
|||||||
if let largest = largestImageRepresentation(representations) {
|
if let largest = largestImageRepresentation(representations) {
|
||||||
return Signal<UIColor, NoError> { subscriber in
|
return Signal<UIColor, NoError> { subscriber in
|
||||||
let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start()
|
let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start()
|
||||||
let data = (postbox.mediaBox.resourceData(largest.resource)
|
let data = serviceColor(for: postbox.mediaBox.resourceData(largest.resource)).start(next: { next in
|
||||||
|> mapToSignal { data -> Signal<UIColor, NoError> in
|
|
||||||
if data.complete {
|
|
||||||
let image = UIImage(contentsOfFile: data.path)
|
|
||||||
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
|
|
||||||
context.withFlippedContext({ context in
|
|
||||||
if let cgImage = image?.cgImage {
|
|
||||||
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var color = context.colorAt(CGPoint())
|
|
||||||
|
|
||||||
var hue: CGFloat = 0.0
|
|
||||||
var saturation: CGFloat = 0.0
|
|
||||||
var brightness: CGFloat = 0.0
|
|
||||||
var alpha: CGFloat = 0.0
|
|
||||||
if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
|
|
||||||
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
|
|
||||||
brightness = max(0.0, brightness * 0.65)
|
|
||||||
alpha = 0.4
|
|
||||||
color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
|
||||||
}
|
|
||||||
return .single(color)
|
|
||||||
}
|
|
||||||
return .complete()
|
|
||||||
}).start(next: { next in
|
|
||||||
subscriber.putNext(next)
|
subscriber.putNext(next)
|
||||||
}, completed: {
|
}, completed: {
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
@ -95,32 +99,7 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox)
|
|||||||
case let .file(file):
|
case let .file(file):
|
||||||
return Signal<UIColor, NoError> { subscriber in
|
return Signal<UIColor, NoError> { subscriber in
|
||||||
let fetch = postbox.mediaBox.fetchedResource(file.file.resource, parameters: nil).start()
|
let fetch = postbox.mediaBox.fetchedResource(file.file.resource, parameters: nil).start()
|
||||||
let data = (postbox.mediaBox.resourceData(file.file.resource)
|
let data = serviceColor(for: postbox.mediaBox.resourceData(file.file.resource)).start(next: { next in
|
||||||
|> mapToSignal { data -> Signal<UIColor, NoError> in
|
|
||||||
if data.complete {
|
|
||||||
let image = UIImage(contentsOfFile: data.path)
|
|
||||||
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
|
|
||||||
context.withFlippedContext({ context in
|
|
||||||
if let cgImage = image?.cgImage {
|
|
||||||
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var color = context.colorAt(CGPoint())
|
|
||||||
|
|
||||||
var hue: CGFloat = 0.0
|
|
||||||
var saturation: CGFloat = 0.0
|
|
||||||
var brightness: CGFloat = 0.0
|
|
||||||
var alpha: CGFloat = 0.0
|
|
||||||
if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
|
|
||||||
saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation))
|
|
||||||
brightness = max(0.0, brightness * 0.65)
|
|
||||||
alpha = 0.4
|
|
||||||
color = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
|
||||||
}
|
|
||||||
return .single(color)
|
|
||||||
}
|
|
||||||
return .complete()
|
|
||||||
}).start(next: { next in
|
|
||||||
subscriber.putNext(next)
|
subscriber.putNext(next)
|
||||||
}, completed: {
|
}, completed: {
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
@ -136,3 +115,96 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func chatBackgroundContrastColor(wallpaper: TelegramWallpaper, postbox: Postbox) -> Signal<UIColor, NoError> {
|
||||||
|
// if wallpaper == serviceBackgroundColorForWallpaper?.0, let color = serviceBackgroundColorForWallpaper?.1 {
|
||||||
|
// return .single(color)
|
||||||
|
// } else {
|
||||||
|
switch wallpaper {
|
||||||
|
case .builtin:
|
||||||
|
return .single(UIColor(rgb: 0x888f96))
|
||||||
|
case let .color(color):
|
||||||
|
return .single(contrastingColor(for: UIColor(rgb: UInt32(bitPattern: color))))
|
||||||
|
case let .image(representations):
|
||||||
|
if let largest = largestImageRepresentation(representations) {
|
||||||
|
return Signal<UIColor, NoError> { subscriber in
|
||||||
|
let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start()
|
||||||
|
let data = backgroundContrastColor(for: postbox.mediaBox.resourceData(largest.resource)).start(next: { next in
|
||||||
|
subscriber.putNext(next)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
return ActionDisposable {
|
||||||
|
fetch.dispose()
|
||||||
|
data.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// |> afterNext { color in
|
||||||
|
// serviceBackgroundColorForWallpaper = (wallpaper, color)
|
||||||
|
//}
|
||||||
|
} else {
|
||||||
|
return .single(.white)
|
||||||
|
}
|
||||||
|
case let .file(file):
|
||||||
|
return Signal<UIColor, NoError> { subscriber in
|
||||||
|
let fetch = postbox.mediaBox.fetchedResource(file.file.resource, parameters: nil).start()
|
||||||
|
let data = backgroundContrastColor(for: postbox.mediaBox.resourceData(file.file.resource)).start(next: { next in
|
||||||
|
subscriber.putNext(next)
|
||||||
|
}, completed: {
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
return ActionDisposable {
|
||||||
|
fetch.dispose()
|
||||||
|
data.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// |> afterNext { color in
|
||||||
|
// serviceBackgroundColorForWallpaper = (wallpaper, color)
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func backgroundContrastColor(for data: Signal<MediaResourceData, NoError>) -> Signal<UIColor, NoError> {
|
||||||
|
return data
|
||||||
|
|> mapToSignal { data -> Signal<UIColor, NoError> in
|
||||||
|
if data.complete {
|
||||||
|
let image = UIImage(contentsOfFile: data.path)
|
||||||
|
let context = DrawingContext(size: CGSize(width: 128.0, height: 32.0), scale: 1.0, clear: false)
|
||||||
|
context.withFlippedContext({ context in
|
||||||
|
if let image = image, let cgImage = image.cgImage {
|
||||||
|
let size = image.size.aspectFilled(CGSize(width: 128.0, height: 128.0))
|
||||||
|
context.draw(cgImage, in: CGRect(x: floor((128.0 - size.width) / 2.0), y: 0.0, width: size.width, height: size.height))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let finalContext = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)
|
||||||
|
finalContext.withFlippedContext({ c in
|
||||||
|
if let cgImage = context.generateImage()?.cgImage {
|
||||||
|
c.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let color = finalContext.colorAt(CGPoint())
|
||||||
|
return .single(contrastingColor(for: color))
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func contrastingColor(for color: UIColor) -> UIColor {
|
||||||
|
var red: CGFloat = 0.0
|
||||||
|
var green: CGFloat = 0.0
|
||||||
|
var blue: CGFloat = 0.0
|
||||||
|
var luminance: CGFloat = 0.0
|
||||||
|
var alpha: CGFloat = 0.0;
|
||||||
|
|
||||||
|
if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
|
||||||
|
luminance = red * 0.2126 + green * 0.7152 + blue * 0.0722
|
||||||
|
} else if color.getWhite(&luminance, alpha: &alpha) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if luminance > 0.6 {
|
||||||
|
return .black
|
||||||
|
} else {
|
||||||
|
return .white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, Bool)] = []
|
var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, Bool)] = []
|
||||||
for entry in view.entries {
|
loop: for entry in view.entries {
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .HoleEntry(hole, _):
|
case let .HoleEntry(hole, _):
|
||||||
if !groupBucket.isEmpty {
|
if !groupBucket.isEmpty {
|
||||||
@ -28,6 +28,19 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
entries.append(.HoleEntry(hole, presentationData))
|
entries.append(.HoleEntry(hole, presentationData))
|
||||||
}
|
}
|
||||||
case let .MessageEntry(message, read, _, monthLocation):
|
case let .MessageEntry(message, read, _, monthLocation):
|
||||||
|
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
for media in message.media {
|
||||||
|
if let action = media as? TelegramMediaAction {
|
||||||
|
switch action.action {
|
||||||
|
case .channelMigratedFromGroup, .groupMigratedToChannel:
|
||||||
|
continue loop
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var isAdmin = false
|
var isAdmin = false
|
||||||
if let author = message.author {
|
if let author = message.author {
|
||||||
isAdmin = adminIds.contains(author.id)
|
isAdmin = adminIds.contains(author.id)
|
||||||
@ -100,7 +113,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
entries.insert(.ChatInfoEntry(botInfo.description, presentationData), at: 0)
|
entries.insert(.ChatInfoEntry(botInfo.description, presentationData), at: 0)
|
||||||
}
|
}
|
||||||
var isEmpty = true
|
var isEmpty = true
|
||||||
if entries.count <= 2 {
|
if entries.count <= 3 {
|
||||||
loop: for entry in view.entries {
|
loop: for entry in view.entries {
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(entry):
|
case let .MessageEntry(entry):
|
||||||
@ -108,7 +121,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
for media in entry.0.media {
|
for media in entry.0.media {
|
||||||
if let action = media as? TelegramMediaAction {
|
if let action = media as? TelegramMediaAction {
|
||||||
switch action.action {
|
switch action.action {
|
||||||
case .groupCreated, .photoUpdated:
|
case .groupCreated, .photoUpdated, .channelMigratedFromGroup, .groupMigratedToChannel:
|
||||||
isEmptyMedia = true
|
isEmptyMedia = true
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -121,27 +121,48 @@ struct ChatHistoryListViewTransition {
|
|||||||
let animateIn: Bool
|
let animateIn: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
private func maxMessageIndexForEntries(_ entries: [ChatHistoryEntry], indexRange: (Int, Int)) -> (incoming: MessageIndex?, overall: MessageIndex?) {
|
private func maxMessageIndexForEntries(_ view: ChatHistoryView, indexRange: (Int, Int)) -> (incoming: MessageIndex?, overall: MessageIndex?) {
|
||||||
|
var incoming: MessageIndex?
|
||||||
var overall: MessageIndex?
|
var overall: MessageIndex?
|
||||||
for i in (indexRange.0 ... indexRange.1).reversed() {
|
var nextLowestIndex: MessageIndex?
|
||||||
if case let .MessageEntry(message, _, _, _, _, _) = entries[i] {
|
if indexRange.0 >= 0 && indexRange.0 < view.filteredEntries.count {
|
||||||
if overall == nil {
|
if indexRange.0 > 0 {
|
||||||
overall = MessageIndex(message)
|
nextLowestIndex = view.filteredEntries[indexRange.0 - 1].index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nextHighestIndex: MessageIndex?
|
||||||
|
if indexRange.1 >= 0 && indexRange.1 < view.filteredEntries.count {
|
||||||
|
if indexRange.1 < view.filteredEntries.count - 1 {
|
||||||
|
nextHighestIndex = view.filteredEntries[indexRange.1 + 1].index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in (0 ..< view.originalView.entries.count).reversed() {
|
||||||
|
let index = view.originalView.entries[i].index
|
||||||
|
if let nextLowestIndex = nextLowestIndex {
|
||||||
|
if index <= nextLowestIndex {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if message.flags.contains(.Incoming) {
|
}
|
||||||
return (MessageIndex(message), overall)
|
if let nextHighestIndex = nextHighestIndex {
|
||||||
|
if index >= nextHighestIndex {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
} else if case let .MessageGroupEntry(_, messages, _) = entries[i] {
|
}
|
||||||
let index = MessageIndex(messages[messages.count - 1].0)
|
if case let .MessageEntry(messageEntry) = view.originalView.entries[i] {
|
||||||
if overall == nil {
|
if overall == nil || overall! < index {
|
||||||
overall = index
|
overall = index
|
||||||
}
|
}
|
||||||
if messages[messages.count - 1].0.flags.contains(.Incoming) {
|
if messageEntry.0.flags.contains(.Incoming) {
|
||||||
return (index, overall)
|
if incoming == nil || incoming! < index {
|
||||||
|
incoming = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if incoming != nil {
|
||||||
|
return (incoming, overall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (nil, overall)
|
return (incoming, overall)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func mappedInsertEntries(account: Account, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
private func mappedInsertEntries(account: Account, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||||
@ -654,7 +675,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if readIndexRange.0 <= readIndexRange.1 {
|
if readIndexRange.0 <= readIndexRange.1 {
|
||||||
let (maxIncomingIndex, maxOverallIndex) = maxMessageIndexForEntries(historyView.filteredEntries, indexRange: readIndexRange)
|
let (maxIncomingIndex, maxOverallIndex) = maxMessageIndexForEntries(historyView, indexRange: readIndexRange)
|
||||||
|
|
||||||
if let maxIncomingIndex = maxIncomingIndex {
|
if let maxIncomingIndex = maxIncomingIndex {
|
||||||
strongSelf.updateMaxVisibleReadIncomingMessageIndex(maxIncomingIndex)
|
strongSelf.updateMaxVisibleReadIncomingMessageIndex(maxIncomingIndex)
|
||||||
@ -980,7 +1001,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
visibleFirstIndex += 1
|
visibleFirstIndex += 1
|
||||||
}*/
|
}*/
|
||||||
if visibleFirstIndex <= visible.lastIndex {
|
if visibleFirstIndex <= visible.lastIndex {
|
||||||
let (messageIndex, _) = maxMessageIndexForEntries(transition.historyView.filteredEntries, indexRange: (transition.historyView.filteredEntries.count - 1 - visible.lastIndex, transition.historyView.filteredEntries.count - 1 - visibleFirstIndex))
|
let (messageIndex, _) = maxMessageIndexForEntries(transition.historyView, indexRange: (transition.historyView.filteredEntries.count - 1 - visible.lastIndex, transition.historyView.filteredEntries.count - 1 - visibleFirstIndex))
|
||||||
if let messageIndex = messageIndex {
|
if let messageIndex = messageIndex {
|
||||||
strongSelf.updateMaxVisibleReadIncomingMessageIndex(messageIndex)
|
strongSelf.updateMaxVisibleReadIncomingMessageIndex(messageIndex)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -585,12 +585,6 @@ private func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel {
|
|
||||||
if peer.hasPermission(.pinMessages) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -442,10 +442,11 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
|||||||
|
|
||||||
self.chatListDisplayNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
|
self.chatListDisplayNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer) |> deliverOnMainQueue).start(completed: { [weak strongSelf] in
|
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak strongSelf] actualPeerId in
|
||||||
if let strongSelf = strongSelf {
|
if let strongSelf = strongSelf {
|
||||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||||
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(messageId.peerId), messageId: messageId, purposefulAction: {
|
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(actualPeerId), messageId: messageId, purposefulAction: {
|
||||||
self?.deactivateSearch(animated: false)
|
self?.deactivateSearch(animated: false)
|
||||||
})
|
})
|
||||||
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||||
@ -968,7 +969,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
|||||||
|
|
||||||
let signal: Signal<Void, NoError> = strongSelf.account.postbox.transaction { transaction -> Void in
|
let signal: Signal<Void, NoError> = strongSelf.account.postbox.transaction { transaction -> Void in
|
||||||
for peerId in peerIds {
|
for peerId in peerIds {
|
||||||
removePeerChat(transaction: transaction, mediaBox: account.postbox.mediaBox, peerId: peerId, reportChatSpam: false)
|
removePeerChat(transaction: transaction, mediaBox: account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> afterDisposed {
|
|> afterDisposed {
|
||||||
|
|||||||
@ -100,8 +100,10 @@ class ChatListItem: ListViewItem {
|
|||||||
func selected(listView: ListView) {
|
func selected(listView: ListView) {
|
||||||
switch self.content {
|
switch self.content {
|
||||||
case let .peer(message, peer, _, _, _, _, _, isAd, _):
|
case let .peer(message, peer, _, _, _, _, _, isAd, _):
|
||||||
if let message = message {
|
if let message = message, let peer = peer.peer {
|
||||||
self.interaction.messageSelected(message, isAd)
|
self.interaction.messageSelected(peer, message, isAd)
|
||||||
|
} else if let peer = peer.peer {
|
||||||
|
self.interaction.peerSelected(peer)
|
||||||
} else if let peer = peer.peers[peer.peerId] {
|
} else if let peer = peer.peers[peer.peerId] {
|
||||||
self.interaction.peerSelected(peer)
|
self.interaction.peerSelected(peer)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ final class ChatListNodeInteraction {
|
|||||||
let activateSearch: () -> Void
|
let activateSearch: () -> Void
|
||||||
let peerSelected: (Peer) -> Void
|
let peerSelected: (Peer) -> Void
|
||||||
let togglePeerSelected: (PeerId) -> Void
|
let togglePeerSelected: (PeerId) -> Void
|
||||||
let messageSelected: (Message, Bool) -> Void
|
let messageSelected: (Peer, Message, Bool) -> Void
|
||||||
let groupSelected: (PeerGroupId) -> Void
|
let groupSelected: (PeerGroupId) -> Void
|
||||||
let addContact: (String) -> Void
|
let addContact: (String) -> Void
|
||||||
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
@ -72,7 +72,7 @@ final class ChatListNodeInteraction {
|
|||||||
|
|
||||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||||
|
|
||||||
init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) {
|
init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (PeerId) -> Void, messageSelected: @escaping (Peer, Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) {
|
||||||
self.activateSearch = activateSearch
|
self.activateSearch = activateSearch
|
||||||
self.peerSelected = peerSelected
|
self.peerSelected = peerSelected
|
||||||
self.togglePeerSelected = togglePeerSelected
|
self.togglePeerSelected = togglePeerSelected
|
||||||
@ -386,9 +386,9 @@ final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}, messageSelected: { [weak self] message, isAd in
|
}, messageSelected: { [weak self] peer, message, isAd in
|
||||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||||
peerSelected(message.id.peerId, true, isAd)
|
peerSelected(peer.id, true, isAd)
|
||||||
}
|
}
|
||||||
}, groupSelected: { [weak self] groupId in
|
}, groupSelected: { [weak self] groupId in
|
||||||
if let strongSelf = self, let groupSelected = strongSelf.groupSelected {
|
if let strongSelf = self, let groupSelected = strongSelf.groupSelected {
|
||||||
|
|||||||
@ -842,7 +842,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
|||||||
let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start()
|
let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start()
|
||||||
self?.listNode.clearHighlightAnimated(true)
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
}, togglePeerSelected: { _ in
|
}, togglePeerSelected: { _ in
|
||||||
}, messageSelected: { [weak self] message, _ in
|
}, messageSelected: { [weak self] peer, message, _ in
|
||||||
if let peer = message.peers[message.id.peerId] {
|
if let peer = message.peers[message.id.peerId] {
|
||||||
openMessage(peer, message.id)
|
openMessage(peer, message.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -232,7 +232,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st
|
|||||||
case .joinedByLink:
|
case .joinedByLink:
|
||||||
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||||
case .channelMigratedFromGroup, .groupMigratedToChannel:
|
case .channelMigratedFromGroup, .groupMigratedToChannel:
|
||||||
attributedString = NSAttributedString(string: strings.Notification_ChannelMigratedFrom, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor)
|
||||||
case let .messageAutoremoveTimeoutUpdated(timeout):
|
case let .messageAutoremoveTimeoutUpdated(timeout):
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
let timeValue = timeIntervalString(strings: strings, value: timeout)
|
let timeValue = timeIntervalString(strings: strings, value: timeout)
|
||||||
|
|||||||
@ -195,10 +195,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
statusUpdated = true
|
statusUpdated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasThumbnail = !file.previewRepresentations.isEmpty && !file.isMusic && !file.isVoice
|
let hasThumbnail = (!file.previewRepresentations.isEmpty || file.immediateThumbnailData != nil) && !file.isMusic && !file.isVoice
|
||||||
|
|
||||||
if mediaUpdated {
|
if mediaUpdated {
|
||||||
if let _ = largestImageRepresentation(file.previewRepresentations) {
|
if largestImageRepresentation(file.previewRepresentations) != nil || file.immediateThumbnailData != nil {
|
||||||
updateImageSignal = chatMessageImageFile(account: account, fileReference: .message(message: MessageReference(message), media: file), thumbnail: true)
|
updateImageSignal = chatMessageImageFile(account: account, fileReference: .message(message: MessageReference(message), media: file), thumbnail: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -354,7 +354,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let shareButtonNode = strongSelf.shareButtonNode {
|
if let shareButtonNode = strongSelf.shareButtonNode {
|
||||||
shareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0))
|
shareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - 30.0 - 10.0), size: CGSize(width: 29.0, height: 29.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
dateAndStatusApply(false)
|
dateAndStatusApply(false)
|
||||||
@ -419,7 +419,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
let actionButtonsNode = actionButtonsSizeAndApply.1(animated)
|
let actionButtonsNode = actionButtonsSizeAndApply.1(animated)
|
||||||
let previousFrame = actionButtonsNode.frame
|
let previousFrame = actionButtonsNode.frame
|
||||||
let actionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY), size: actionButtonsSizeAndApply.0)
|
let actionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY - 10.0), size: actionButtonsSizeAndApply.0)
|
||||||
actionButtonsNode.frame = actionButtonsFrame
|
actionButtonsNode.frame = actionButtonsFrame
|
||||||
if actionButtonsNode !== strongSelf.actionButtonsNode {
|
if actionButtonsNode !== strongSelf.actionButtonsNode {
|
||||||
strongSelf.actionButtonsNode = actionButtonsNode
|
strongSelf.actionButtonsNode = actionButtonsNode
|
||||||
|
|||||||
@ -770,6 +770,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, dismissInput: {
|
}, dismissInput: {
|
||||||
self?.view.endEditing(true)
|
self?.view.endEditing(true)
|
||||||
})
|
})
|
||||||
|
case let .wallpaper(slug):
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -767,8 +767,6 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
var text: String = ""
|
var text: String = ""
|
||||||
var entities: [MessageTextEntity] = []
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
let newFlags = new.flags
|
|
||||||
|
|
||||||
var addedRights = new.flags
|
var addedRights = new.flags
|
||||||
var removedRights: TelegramChatBannedRightsFlags = []
|
var removedRights: TelegramChatBannedRightsFlags = []
|
||||||
addedRights = addedRights.subtracting(prev.flags)
|
addedRights = addedRights.subtracting(prev.flags)
|
||||||
@ -777,8 +775,6 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
text += self.presentationData.strings.Channel_AdminLog_DefaultRestrictionsUpdated
|
text += self.presentationData.strings.Channel_AdminLog_DefaultRestrictionsUpdated
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
let prevFlags = prev.flags
|
|
||||||
|
|
||||||
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
let order: [(TelegramChatBannedRightsFlags, String)] = [
|
||||||
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
|
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
|
||||||
(.banSendMessages, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
|
(.banSendMessages, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
|
||||||
@ -786,6 +782,10 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickers),
|
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickers),
|
||||||
(.banSendGifs, self.presentationData.strings.Channel_AdminLog_BanSendGifs),
|
(.banSendGifs, self.presentationData.strings.Channel_AdminLog_BanSendGifs),
|
||||||
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
(.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks),
|
||||||
|
(.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls),
|
||||||
|
(.banAddMembers, self.presentationData.strings.Channel_AdminLog_AddMembers),
|
||||||
|
(.banPinMessages, self.presentationData.strings.Channel_AdminLog_PinMessages),
|
||||||
|
(.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls)
|
||||||
]
|
]
|
||||||
|
|
||||||
for (flag, string) in order {
|
for (flag, string) in order {
|
||||||
|
|||||||
@ -20,11 +20,17 @@ private let searchLayoutProgressImage = generateImage(CGSize(width: 22.0, height
|
|||||||
|
|
||||||
private let accessoryButtonFont = Font.medium(14.0)
|
private let accessoryButtonFont = Font.medium(14.0)
|
||||||
|
|
||||||
private final class AccessoryItemIconButton: HighlightableButton {
|
private final class AccessoryItemIconButton: HighlightTrackingButton {
|
||||||
private let item: ChatTextInputAccessoryItem
|
private let item: ChatTextInputAccessoryItem
|
||||||
private var width: CGFloat
|
private var width: CGFloat
|
||||||
|
private let imageNode: ASImageNode
|
||||||
|
|
||||||
init(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) {
|
init(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
|
self.imageNode = ASImageNode()
|
||||||
|
self.imageNode.isLayerBacked = true
|
||||||
|
self.imageNode.displaysAsynchronously = false
|
||||||
|
self.imageNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
let (image, text, alpha, insets) = AccessoryItemIconButton.imageAndInsets(item: item, theme: theme, strings: strings)
|
let (image, text, alpha, insets) = AccessoryItemIconButton.imageAndInsets(item: item, theme: theme, strings: strings)
|
||||||
@ -33,15 +39,29 @@ private final class AccessoryItemIconButton: HighlightableButton {
|
|||||||
|
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.addSubnode(self.imageNode)
|
||||||
|
|
||||||
if let text = text {
|
if let text = text {
|
||||||
self.titleLabel?.font = accessoryButtonFont
|
self.titleLabel?.font = accessoryButtonFont
|
||||||
self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: [])
|
self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: [])
|
||||||
self.setTitle(text, for: [])
|
self.setTitle(text, for: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setImage(image, for: [])
|
self.imageNode.image = image
|
||||||
|
self.imageNode.alpha = alpha
|
||||||
self.imageEdgeInsets = insets
|
self.imageEdgeInsets = insets
|
||||||
self.imageView?.alpha = alpha
|
|
||||||
|
self.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
strongSelf.layer.removeAnimation(forKey: "opacity")
|
||||||
|
strongSelf.alpha = 0.4
|
||||||
|
} else {
|
||||||
|
strongSelf.alpha = 1.0
|
||||||
|
strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
@ -57,9 +77,9 @@ private final class AccessoryItemIconButton: HighlightableButton {
|
|||||||
self.setTitle("", for: [])
|
self.setTitle("", for: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setImage(image, for: [])
|
self.imageNode.image = image
|
||||||
self.imageEdgeInsets = insets
|
self.imageEdgeInsets = insets
|
||||||
self.imageView?.alpha = alpha
|
self.imageNode.alpha = alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -105,6 +125,12 @@ private final class AccessoryItemIconButton: HighlightableButton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize) {
|
||||||
|
if let image = self.imageNode.image {
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.width - image.size.width) / 2.0) - self.imageEdgeInsets.bottom), size: image.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var buttonWidth: CGFloat {
|
var buttonWidth: CGFloat {
|
||||||
return self.width
|
return self.width
|
||||||
}
|
}
|
||||||
@ -984,6 +1010,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: panelHeight - textFieldInsets.bottom - minimalInputHeight + audioRecordingItemsVerticalOffset)
|
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: panelHeight - textFieldInsets.bottom - minimalInputHeight + audioRecordingItemsVerticalOffset)
|
||||||
for (_, button) in self.accessoryItemButtons.reversed() {
|
for (_, button) in self.accessoryItemButtons.reversed() {
|
||||||
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
||||||
|
button.updateLayout(size: buttonSize)
|
||||||
let buttonFrame = CGRect(origin: CGPoint(x: nextButtonTopRight.x - buttonSize.width, y: nextButtonTopRight.y + floor((minimalInputHeight - buttonSize.height) / 2.0)), size: buttonSize)
|
let buttonFrame = CGRect(origin: CGPoint(x: nextButtonTopRight.x - buttonSize.width, y: nextButtonTopRight.y + floor((minimalInputHeight - buttonSize.height) / 2.0)), size: buttonSize)
|
||||||
if button.superview == nil {
|
if button.superview == nil {
|
||||||
self.view.addSubview(button)
|
self.view.addSubview(button)
|
||||||
|
|||||||
@ -629,7 +629,12 @@ private func preparedContactListNodeTransition(account: Account, from fromEntrie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, indexSections: indexSections, firstTime: firstTime, isEmpty: isEmpty, animation: animation)
|
var scrollToItem: ListViewScrollToItem?
|
||||||
|
if firstTime && toEntries.count >= 1 {
|
||||||
|
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight - 50.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, indexSections: indexSections, firstTime: firstTime, isEmpty: isEmpty, scrollToItem: scrollToItem, animation: animation)
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ContactsListNodeTransition {
|
private struct ContactsListNodeTransition {
|
||||||
@ -639,6 +644,7 @@ private struct ContactsListNodeTransition {
|
|||||||
let indexSections: [String]
|
let indexSections: [String]
|
||||||
let firstTime: Bool
|
let firstTime: Bool
|
||||||
let isEmpty: Bool
|
let isEmpty: Bool
|
||||||
|
let scrollToItem: ListViewScrollToItem?
|
||||||
let animation: ContactListAnimation
|
let animation: ContactListAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,6 +733,9 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
self.selectionStatePromise.set(.single(self.selectionStateValue))
|
self.selectionStatePromise.set(.single(self.selectionStateValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var selectionState: ContactListNodeGroupSelectionState? {
|
||||||
|
return self.selectionStateValue
|
||||||
|
}
|
||||||
|
|
||||||
private var enableUpdatesValue = false
|
private var enableUpdatesValue = false
|
||||||
var enableUpdates: Bool {
|
var enableUpdates: Bool {
|
||||||
@ -1271,7 +1280,7 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .animated(duration: 0.2, curve: .easeInOut))
|
self.indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: transition.scrollToItem, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if !strongSelf.didSetReady {
|
if !strongSelf.didSetReady {
|
||||||
strongSelf.didSetReady = true
|
strongSelf.didSetReady = true
|
||||||
|
|||||||
@ -131,11 +131,12 @@ class ContactMultiselectionController: ViewController {
|
|||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .groupCreation:
|
case .groupCreation:
|
||||||
let maxCount: Int32 = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
let maxCount: Int32 = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.Compose_NewGroupTitle, counter: "0/\(maxCount)")
|
let count = self.contactsNode.contactListNode.selectionState?.selectedPeerIndices.count ?? 0
|
||||||
|
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.Compose_NewGroupTitle, counter: "\(count)/\(maxCount)")
|
||||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||||
self.rightNavigationButton = rightNavigationButton
|
self.rightNavigationButton = rightNavigationButton
|
||||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||||
rightNavigationButton.isEnabled = false
|
rightNavigationButton.isEnabled = count != 0
|
||||||
case .channelCreation:
|
case .channelCreation:
|
||||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "")
|
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "")
|
||||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||||
|
|||||||
@ -5,6 +5,25 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
|
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||||
|
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
|
||||||
|
let scrollToItem: ListViewScrollToItem
|
||||||
|
let targetProgress: CGFloat
|
||||||
|
if searchNode.expansionProgress < 0.6 {
|
||||||
|
scrollToItem = ListViewScrollToItem(index: 1, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: 0.25), directionHint: .Up)
|
||||||
|
targetProgress = 0.0
|
||||||
|
} else {
|
||||||
|
scrollToItem = ListViewScrollToItem(index: 1, position: .top(0.0), animated: true, curve: .Default(duration: 0.25), directionHint: .Up)
|
||||||
|
targetProgress = 1.0
|
||||||
|
}
|
||||||
|
searchNode.updateExpansionProgress(targetProgress, animated: true)
|
||||||
|
|
||||||
|
listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
public class ContactsController: ViewController {
|
public class ContactsController: ViewController {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
|
|
||||||
@ -229,7 +248,7 @@ public class ContactsController: ViewController {
|
|||||||
|
|
||||||
self.contactsNode.contactListNode.contentScrollingEnded = { [weak self] listView in
|
self.contactsNode.contactListNode.contentScrollingEnded = { [weak self] listView in
|
||||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||||
return fixNavigationSearchableListNodeScrolling(listView, searchNode: searchContentNode)
|
return fixListNodeScrolling(listView, searchNode: searchContentNode)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,10 +105,31 @@ public func debugAccountsController(account: Account, accountManager: AccountMan
|
|||||||
transaction.setCurrentId(id)
|
transaction.setCurrentId(id)
|
||||||
}).start()
|
}).start()
|
||||||
}, loginNewAccount: {
|
}, loginNewAccount: {
|
||||||
let _ = accountManager.transaction({ transaction -> Void in
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
let id = transaction.createRecord([])
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
transaction.setCurrentId(id)
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
}).start()
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: "Production", color: .accent, action: {
|
||||||
|
dismissAction()
|
||||||
|
let _ = accountManager.transaction({ transaction -> Void in
|
||||||
|
let id = transaction.createRecord([AccountEnvironmentAttribute(environment: .production)])
|
||||||
|
transaction.setCurrentId(id)
|
||||||
|
}).start()
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: "Test", color: .accent, action: {
|
||||||
|
dismissAction()
|
||||||
|
let _ = accountManager.transaction({ transaction -> Void in
|
||||||
|
let id = transaction.createRecord([AccountEnvironmentAttribute(environment: .test)])
|
||||||
|
transaction.setCurrentId(id)
|
||||||
|
}).start()
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
})
|
})
|
||||||
|
|
||||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, accountManager.accountRecords())
|
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, accountManager.accountRecords())
|
||||||
|
|||||||
@ -118,7 +118,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
let messages = logs.map { (name, path) -> EnqueueMessage in
|
let messages = logs.map { (name, path) -> EnqueueMessage in
|
||||||
let id = arc4random64()
|
let id = arc4random64()
|
||||||
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
||||||
return .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
return .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
||||||
}
|
}
|
||||||
let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start()
|
let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start()
|
||||||
@ -140,7 +140,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
let messages = updatedLogs.map { (name, path) -> EnqueueMessage in
|
let messages = updatedLogs.map { (name, path) -> EnqueueMessage in
|
||||||
let id = arc4random64()
|
let id = arc4random64()
|
||||||
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
||||||
return .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
return .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
|
||||||
}
|
}
|
||||||
let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start()
|
let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start()
|
||||||
|
|||||||
@ -57,7 +57,9 @@ private let rootController = PresentationThemeRootController(
|
|||||||
private let switchColors = PresentationThemeSwitch(
|
private let switchColors = PresentationThemeSwitch(
|
||||||
frameColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
|
frameColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
|
||||||
handleColor: UIColor(rgb: 0x121212),
|
handleColor: UIColor(rgb: 0x121212),
|
||||||
contentColor: accentColor
|
contentColor: accentColor,
|
||||||
|
positiveColor: accentColor,
|
||||||
|
negativeColor: destructiveColor
|
||||||
)
|
)
|
||||||
|
|
||||||
private let list = PresentationThemeList(
|
private let list = PresentationThemeList(
|
||||||
|
|||||||
@ -57,7 +57,9 @@ private let rootController = PresentationThemeRootController(
|
|||||||
private let switchColors = PresentationThemeSwitch(
|
private let switchColors = PresentationThemeSwitch(
|
||||||
frameColor: UIColor(rgb: 0x545454),
|
frameColor: UIColor(rgb: 0x545454),
|
||||||
handleColor: UIColor(rgb: 0x121212),
|
handleColor: UIColor(rgb: 0x121212),
|
||||||
contentColor: UIColor(rgb: 0xb2b2b2)
|
contentColor: UIColor(rgb: 0xb2b2b2),
|
||||||
|
positiveColor: UIColor(rgb: 0x000000),
|
||||||
|
negativeColor: destructiveColor
|
||||||
)
|
)
|
||||||
|
|
||||||
private let list = PresentationThemeList(
|
private let list = PresentationThemeList(
|
||||||
|
|||||||
@ -57,7 +57,9 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
|
|||||||
let switchColors = PresentationThemeSwitch(
|
let switchColors = PresentationThemeSwitch(
|
||||||
frameColor: UIColor(rgb: 0xe0e0e0),
|
frameColor: UIColor(rgb: 0xe0e0e0),
|
||||||
handleColor: UIColor(rgb: 0xffffff),
|
handleColor: UIColor(rgb: 0xffffff),
|
||||||
contentColor: UIColor(rgb: 0x42d451)
|
contentColor: UIColor(rgb: 0x42d451),
|
||||||
|
positiveColor: UIColor(rgb: 0x00B12C),
|
||||||
|
negativeColor: destructiveColor
|
||||||
)
|
)
|
||||||
|
|
||||||
let list = PresentationThemeList(
|
let list = PresentationThemeList(
|
||||||
|
|||||||
@ -28,7 +28,6 @@ private final class GroupInfoArguments {
|
|||||||
let promotePeer: (RenderedChannelParticipant) -> Void
|
let promotePeer: (RenderedChannelParticipant) -> Void
|
||||||
let restrictPeer: (RenderedChannelParticipant) -> Void
|
let restrictPeer: (RenderedChannelParticipant) -> Void
|
||||||
let removePeer: (PeerId) -> Void
|
let removePeer: (PeerId) -> Void
|
||||||
let convertToSupergroup: () -> Void
|
|
||||||
let leave: () -> Void
|
let leave: () -> Void
|
||||||
let displayUsernameShareMenu: (String) -> Void
|
let displayUsernameShareMenu: (String) -> Void
|
||||||
let displayUsernameContextMenu: (String) -> Void
|
let displayUsernameContextMenu: (String) -> Void
|
||||||
@ -37,7 +36,7 @@ private final class GroupInfoArguments {
|
|||||||
let openStickerPackSetup: () -> Void
|
let openStickerPackSetup: () -> Void
|
||||||
let openGroupTypeSetup: () -> Void
|
let openGroupTypeSetup: () -> Void
|
||||||
|
|
||||||
init(account: Account, peerId: PeerId, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, changeNotificationMuteSettings: @escaping () -> Void, openPreHistory: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdministrators: @escaping () -> Void, openPermissions: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addMember: @escaping () -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void, convertToSupergroup: @escaping () -> Void, leave: @escaping () -> Void, displayUsernameShareMenu: @escaping (String) -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, openStickerPackSetup: @escaping () -> Void, openGroupTypeSetup: @escaping () -> Void) {
|
init(account: Account, peerId: PeerId, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, changeNotificationMuteSettings: @escaping () -> Void, openPreHistory: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdministrators: @escaping () -> Void, openPermissions: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addMember: @escaping () -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void, leave: @escaping () -> Void, displayUsernameShareMenu: @escaping (String) -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, openStickerPackSetup: @escaping () -> Void, openGroupTypeSetup: @escaping () -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
||||||
@ -57,7 +56,6 @@ private final class GroupInfoArguments {
|
|||||||
self.promotePeer = promotePeer
|
self.promotePeer = promotePeer
|
||||||
self.restrictPeer = restrictPeer
|
self.restrictPeer = restrictPeer
|
||||||
self.removePeer = removePeer
|
self.removePeer = removePeer
|
||||||
self.convertToSupergroup = convertToSupergroup
|
|
||||||
self.leave = leave
|
self.leave = leave
|
||||||
self.displayUsernameShareMenu = displayUsernameShareMenu
|
self.displayUsernameShareMenu = displayUsernameShareMenu
|
||||||
self.displayUsernameContextMenu = displayUsernameContextMenu
|
self.displayUsernameContextMenu = displayUsernameContextMenu
|
||||||
@ -147,7 +145,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
case permissions(PresentationTheme, String, String)
|
case permissions(PresentationTheme, String, String)
|
||||||
case addMember(PresentationTheme, String, editing: Bool)
|
case addMember(PresentationTheme, String, editing: Bool)
|
||||||
case member(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, index: Int, peerId: PeerId, peer: Peer, participant: RenderedChannelParticipant?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus, editing: ItemListPeerItemEditing, revealActions: [ParticipantRevealAction], enabled: Bool)
|
case member(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, index: Int, peerId: PeerId, peer: Peer, participant: RenderedChannelParticipant?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus, editing: ItemListPeerItemEditing, revealActions: [ParticipantRevealAction], enabled: Bool)
|
||||||
case convertToSupergroup(PresentationTheme, String)
|
|
||||||
case leave(PresentationTheme, String)
|
case leave(PresentationTheme, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
@ -164,7 +161,7 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
return GroupInfoSection.memberManagement.rawValue
|
return GroupInfoSection.memberManagement.rawValue
|
||||||
case .addMember, .member:
|
case .addMember, .member:
|
||||||
return GroupInfoSection.members.rawValue
|
return GroupInfoSection.members.rawValue
|
||||||
case .convertToSupergroup, .leave:
|
case .leave:
|
||||||
return GroupInfoSection.leave.rawValue
|
return GroupInfoSection.leave.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,12 +227,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .convertToSupergroup(lhsTheme, lhsText):
|
|
||||||
if case let .convertToSupergroup(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .aboutHeader(lhsTheme, lhsText):
|
case let .aboutHeader(lhsTheme, lhsText):
|
||||||
if case let .aboutHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .aboutHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -407,8 +398,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
return 17
|
return 17
|
||||||
case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _):
|
case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _):
|
||||||
return 20 + index
|
return 20 + index
|
||||||
case .convertToSupergroup:
|
|
||||||
return 100000
|
|
||||||
case .leave:
|
case .leave:
|
||||||
return 100000 + 1
|
return 100000 + 1
|
||||||
}
|
}
|
||||||
@ -516,10 +505,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
|||||||
}, removePeer: { peerId in
|
}, removePeer: { peerId in
|
||||||
arguments.removePeer(peerId)
|
arguments.removePeer(peerId)
|
||||||
})
|
})
|
||||||
case let .convertToSupergroup(theme, title):
|
|
||||||
return ItemListActionItem(theme: theme, title: title, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
|
||||||
arguments.convertToSupergroup()
|
|
||||||
})
|
|
||||||
case let .leave(theme, title):
|
case let .leave(theme, title):
|
||||||
return ItemListActionItem(theme: theme, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
return ItemListActionItem(theme: theme, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.leave()
|
arguments.leave()
|
||||||
@ -698,9 +683,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
var canAddMembers = false
|
var canAddMembers = false
|
||||||
var isPublic = false
|
var isPublic = false
|
||||||
var isCreator = false
|
var isCreator = false
|
||||||
var isGroup = false
|
|
||||||
if let group = view.peers[view.peerId] as? TelegramGroup {
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
isGroup = true
|
|
||||||
if case .creator = group.role {
|
if case .creator = group.role {
|
||||||
isCreator = true
|
isCreator = true
|
||||||
}
|
}
|
||||||
@ -713,10 +696,13 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
case .member:
|
case .member:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
if !group.hasBannedPermission(.banChangeInfo) {
|
||||||
if case .group = channel.info {
|
canEditGroupInfo = true
|
||||||
isGroup = true
|
|
||||||
}
|
}
|
||||||
|
if !group.hasBannedPermission(.banAddMembers) {
|
||||||
|
canAddMembers = true
|
||||||
|
}
|
||||||
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
||||||
highlightAdmins = true
|
highlightAdmins = true
|
||||||
isPublic = channel.username != nil
|
isPublic = channel.username != nil
|
||||||
isCreator = channel.flags.contains(.isCreator)
|
isCreator = channel.flags.contains(.isCreator)
|
||||||
@ -745,6 +731,8 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
} else {
|
} else {
|
||||||
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
|
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
|
||||||
}
|
}
|
||||||
|
} else if case .default = peerNotificationSettings.messageSound {
|
||||||
|
notificationsText = presentationData.strings.UserInfo_NotificationsEnabled
|
||||||
} else {
|
} else {
|
||||||
notificationsText = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: peerNotificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
notificationsText = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: peerNotificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
||||||
}
|
}
|
||||||
@ -1022,35 +1010,40 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
|
|
||||||
var canPromote: Bool
|
var canPromote: Bool
|
||||||
var canRestrict: Bool
|
var canRestrict: Bool
|
||||||
switch participant.participant {
|
if participant.peer.id == account.peerId {
|
||||||
case .creator:
|
canPromote = false
|
||||||
canPromote = false
|
canRestrict = false
|
||||||
canRestrict = false
|
} else {
|
||||||
case let .member(_, _, adminRights, bannedRights):
|
switch participant.participant {
|
||||||
if channel.hasPermission(.addAdmins) {
|
case .creator:
|
||||||
canPromote = true
|
|
||||||
} else {
|
|
||||||
canPromote = false
|
canPromote = false
|
||||||
}
|
|
||||||
if channel.hasPermission(.banMembers) {
|
|
||||||
canRestrict = true
|
|
||||||
} else {
|
|
||||||
canRestrict = false
|
canRestrict = false
|
||||||
}
|
case let .member(_, _, adminRights, bannedRights):
|
||||||
if canPromote {
|
if channel.hasPermission(.addAdmins) {
|
||||||
if let bannedRights = bannedRights {
|
canPromote = true
|
||||||
if bannedRights.restrictedBy != account.peerId && !channel.flags.contains(.isCreator) {
|
} else {
|
||||||
canPromote = false
|
canPromote = false
|
||||||
|
}
|
||||||
|
if channel.hasPermission(.banMembers) {
|
||||||
|
canRestrict = true
|
||||||
|
} else {
|
||||||
|
canRestrict = false
|
||||||
|
}
|
||||||
|
if canPromote {
|
||||||
|
if let bannedRights = bannedRights {
|
||||||
|
if bannedRights.restrictedBy != account.peerId && !channel.flags.contains(.isCreator) {
|
||||||
|
canPromote = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if canRestrict {
|
||||||
if canRestrict {
|
if let adminRights = adminRights {
|
||||||
if let adminRights = adminRights {
|
if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) {
|
||||||
if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) {
|
canRestrict = false
|
||||||
canRestrict = false
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var peerActions: [ParticipantRevealAction] = []
|
var peerActions: [ParticipantRevealAction] = []
|
||||||
@ -1068,14 +1061,21 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
|
|||||||
|
|
||||||
if let group = view.peers[view.peerId] as? TelegramGroup {
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
if case .Member = group.membership {
|
if case .Member = group.membership {
|
||||||
if case .creator = group.role, state.editingState != nil {
|
|
||||||
entries.append(.convertToSupergroup(presentationData.theme, presentationData.strings.GroupInfo_ConvertToSupergroup))
|
|
||||||
}
|
|
||||||
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
||||||
}
|
}
|
||||||
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
||||||
if case .member = channel.participationStatus, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount <= 200 {
|
if case .member = channel.participationStatus {
|
||||||
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
if channel.flags.contains(.isCreator) {
|
||||||
|
if let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount <= 200 {
|
||||||
|
if state.editingState != nil {
|
||||||
|
entries.append(.leave(presentationData.theme, presentationData.strings.ChannelInfo_DeleteGroup))
|
||||||
|
} else {
|
||||||
|
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1177,6 +1177,9 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
|
|
||||||
var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)?
|
var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
|
let peerView = Promise<PeerView>()
|
||||||
|
peerView.set(account.viewTracker.peerView(peerId))
|
||||||
|
|
||||||
let arguments = GroupInfoArguments(account: account, peerId: peerId, avatarAndNameInfoContext: avatarAndNameInfoContext, tapAvatarAction: {
|
let arguments = GroupInfoArguments(account: account, peerId: peerId, avatarAndNameInfoContext: avatarAndNameInfoContext, tapAvatarAction: {
|
||||||
let _ = (account.postbox.loadedPeerWithId(peerId)
|
let _ = (account.postbox.loadedPeerWithId(peerId)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -1427,7 +1430,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMember(account: account, peerId: peerId, memberId: memberId)
|
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMember(account: account, peerId: peerId, memberId: memberId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return account.postbox.peerView(id: memberId)
|
return peerView.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> mapToSignal { view -> Signal<Void, NoError> in
|
|> mapToSignal { view -> Signal<Void, NoError> in
|
||||||
@ -1645,31 +1648,56 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMemberDisposable.set(signal.start())
|
removeMemberDisposable.set(signal.start())
|
||||||
}, convertToSupergroup: {
|
|
||||||
pushControllerImpl?(convertToSupergroupController(account: account, peerId: peerId))
|
|
||||||
}, leave: {
|
}, leave: {
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
let _ = (peerView.get()
|
||||||
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
|> take(1)
|
||||||
let dismissAction: () -> Void = { [weak controller] in
|
|> deliverOnMainQueue).start(next: { peerView in
|
||||||
controller?.dismissAnimated()
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
}
|
|
||||||
|
if let channel = peerView.peers[peerId] as? TelegramChannel, channel.flags.contains(.isCreator), stateValue.with({ $0 }).editingState != nil {
|
||||||
var items: [ActionSheetItem] = []
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
if peerId.namespace == Namespaces.Peer.CloudGroup {
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
items.append(ActionSheetTextItem(title: presentationData.strings.GroupInfo_DeleteAndExitConfirmation))
|
controller?.dismissAnimated()
|
||||||
}
|
}
|
||||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Group_LeaveGroup, color: .destructive, action: {
|
|
||||||
dismissAction()
|
var items: [ActionSheetItem] = []
|
||||||
let _ = (removePeerChat(postbox: account.postbox, peerId: peerId, reportChatSpam: false)
|
items.append(ActionSheetTextItem(title: presentationData.strings.ChannelInfo_DeleteGroupConfirmation))
|
||||||
|> deliverOnMainQueue).start(completed: {
|
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteGroup, color: .destructive, action: {
|
||||||
popToRootImpl?()
|
dismissAction()
|
||||||
})
|
let _ = (removePeerChat(postbox: account.postbox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: true)
|
||||||
}))
|
|> deliverOnMainQueue).start(completed: {
|
||||||
controller.setItemGroups([
|
popToRootImpl?()
|
||||||
ActionSheetItemGroup(items: items),
|
})
|
||||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
}))
|
||||||
])
|
controller.setItemGroups([
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
ActionSheetItemGroup(items: items),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
} else {
|
||||||
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
|
||||||
|
var items: [ActionSheetItem] = []
|
||||||
|
if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||||
|
items.append(ActionSheetTextItem(title: presentationData.strings.GroupInfo_DeleteAndExitConfirmation))
|
||||||
|
}
|
||||||
|
items.append(ActionSheetButtonItem(title: presentationData.strings.Group_LeaveGroup, color: .destructive, action: {
|
||||||
|
dismissAction()
|
||||||
|
let _ = (removePeerChat(postbox: account.postbox, peerId: peerId, reportChatSpam: false)
|
||||||
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
popToRootImpl?()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: items),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
}
|
||||||
|
})
|
||||||
}, displayUsernameShareMenu: { text in
|
}, displayUsernameShareMenu: { text in
|
||||||
let shareController = ShareController(account: account, subject: .url(text))
|
let shareController = ShareController(account: account, subject: .url(text))
|
||||||
presentControllerImpl?(shareController, nil)
|
presentControllerImpl?(shareController, nil)
|
||||||
@ -1709,7 +1737,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
let previousChannelMembers = Atomic<[PeerId]?>(value: nil)
|
let previousChannelMembers = Atomic<[PeerId]?>(value: nil)
|
||||||
|
|
||||||
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
||||||
let signal = combineLatest(queue: .mainQueue(), (account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), account.viewTracker.peerView(peerId), account.postbox.combinedView(keys: [globalNotificationsKey]), channelMembersPromise.get())
|
let signal = combineLatest(queue: .mainQueue(), (account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peerView.get(), account.postbox.combinedView(keys: [globalNotificationsKey]), channelMembersPromise.get())
|
||||||
|> map { presentationData, state, view, combinedView, channelMembers -> (ItemListControllerState, (ItemListNodeState<GroupInfoEntry>, GroupInfoEntry.ItemGenerationArguments)) in
|
|> map { presentationData, state, view, combinedView, channelMembers -> (ItemListControllerState, (ItemListNodeState<GroupInfoEntry>, GroupInfoEntry.ItemGenerationArguments)) in
|
||||||
let peer = peerViewMainPeer(view)
|
let peer = peerViewMainPeer(view)
|
||||||
|
|
||||||
@ -1720,7 +1748,24 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rightNavigationButton: ItemListNavigationButton
|
var canEditGroupInfo = false
|
||||||
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
|
switch group.role {
|
||||||
|
case .admin, .creator:
|
||||||
|
canEditGroupInfo = true
|
||||||
|
case .member:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !group.hasBannedPermission(.banChangeInfo) {
|
||||||
|
canEditGroupInfo = true
|
||||||
|
}
|
||||||
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
||||||
|
if channel.hasPermission(.changeInfo) || !(channel.adminRights?.flags ?? []).isEmpty {
|
||||||
|
canEditGroupInfo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
var secondaryRightNavigationButton: ItemListNavigationButton?
|
var secondaryRightNavigationButton: ItemListNavigationButton?
|
||||||
if let editingState = state.editingState {
|
if let editingState = state.editingState {
|
||||||
var doneEnabled = true
|
var doneEnabled = true
|
||||||
@ -1779,7 +1824,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else if canEditGroupInfo {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
||||||
if let peer = peer as? TelegramGroup {
|
if let peer = peer as? TelegramGroup {
|
||||||
var text = ""
|
var text = ""
|
||||||
@ -1806,7 +1851,14 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if peer is TelegramChannel {
|
||||||
|
rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedSearchingMembers(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchItem: ItemListControllerSearch?
|
var searchItem: ItemListControllerSearch?
|
||||||
|
|||||||
@ -96,7 +96,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
entries.append(entry)
|
entries.append(entry)
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
prepareTransition(from: self.currentEntries ?? [], to: entries)
|
self.prepareTransition(from: self.currentEntries ?? [], to: entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
||||||
@ -132,7 +132,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func enqueueTransition(_ transition: HashtagChatInputContextPanelTransition, firstTime: Bool) {
|
private func enqueueTransition(_ transition: HashtagChatInputContextPanelTransition, firstTime: Bool) {
|
||||||
enqueuedTransitions.append((transition, firstTime))
|
self.enqueuedTransitions.append((transition, firstTime))
|
||||||
|
|
||||||
if self.validLayout != nil {
|
if self.validLayout != nil {
|
||||||
while !self.enqueuedTransitions.isEmpty {
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
@ -233,7 +233,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
|
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
|
||||||
|
|
||||||
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
|
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
|
||||||
prepareTransition(from: self.currentEntries, to: new)
|
self.prepareTransition(from: self.currentEntries, to: new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,15 +44,13 @@ final class HashtagSearchController: TelegramController {
|
|||||||
let interaction = ChatListNodeInteraction(activateSearch: {
|
let interaction = ChatListNodeInteraction(activateSearch: {
|
||||||
}, peerSelected: { peer in
|
}, peerSelected: { peer in
|
||||||
}, togglePeerSelected: { _ in
|
}, togglePeerSelected: { _ in
|
||||||
}, messageSelected: { [weak self] message, _ in
|
}, messageSelected: { [weak self] peer, message, _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let peer = message.peers[message.id.peerId] {
|
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in
|
||||||
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer) |> deliverOnMainQueue).start(completed: {
|
if let strongSelf = self {
|
||||||
if let strongSelf = self {
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(actualPeerId), messageId: message.id.peerId == actualPeerId ? message.id : nil))
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(message.id.peerId), messageId: message.id))
|
}
|
||||||
}
|
}))
|
||||||
}))
|
|
||||||
}
|
|
||||||
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)
|
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)
|
||||||
}
|
}
|
||||||
}, groupSelected: { _ in
|
}, groupSelected: { _ in
|
||||||
|
|||||||
@ -209,7 +209,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
|||||||
}
|
}
|
||||||
imageDimensions = content?.dimensions
|
imageDimensions = content?.dimensions
|
||||||
if type == "gif", let thumbnailResource = imageResource, let content = content, let dimensions = content.dimensions {
|
if type == "gif", let thumbnailResource = imageResource, let content = content, let dimensions = content.dimensions {
|
||||||
videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])
|
videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])
|
||||||
imageResource = nil
|
imageResource = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private var strings: PresentationStrings
|
private var strings: PresentationStrings
|
||||||
|
|
||||||
private let gridNode: GridNode
|
private let gridNode: GridNode
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat, ChatPresentationInterfaceState)?
|
private var validLayout: (CGSize, CGFloat, CGFloat, ChatPresentationInterfaceState)?
|
||||||
private var currentEntries: [StickerEntry] = []
|
private var currentEntries: [StickerEntry] = []
|
||||||
@ -72,6 +73,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.gridNode = GridNode()
|
self.gridNode = GridNode()
|
||||||
self.gridNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
self.gridNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.backgroundColor = theme.list.plainBackgroundColor
|
||||||
|
|
||||||
self.stickersInteraction = HorizontalStickersChatContextPanelInteraction()
|
self.stickersInteraction = HorizontalStickersChatContextPanelInteraction()
|
||||||
|
|
||||||
super.init(account: account, theme: theme, strings: strings)
|
super.init(account: account, theme: theme, strings: strings)
|
||||||
@ -80,6 +84,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
|
|
||||||
self.addSubnode(self.gridNode)
|
self.addSubnode(self.gridNode)
|
||||||
|
self.gridNode.addSubnode(self.backgroundNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -192,7 +197,13 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private func dequeueTransitions() {
|
private func dequeueTransitions() {
|
||||||
while !self.queuedTransitions.isEmpty {
|
while !self.queuedTransitions.isEmpty {
|
||||||
let transition = self.queuedTransitions.removeFirst()
|
let transition = self.queuedTransitions.removeFirst()
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in })
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in
|
||||||
|
|
||||||
|
// if let topItemOffset = topItemOffset {
|
||||||
|
// let position = strongSelf.listView.layer.position
|
||||||
|
// strongSelf.listView.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset)), to: position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
// }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,32 +222,12 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
|||||||
|
|
||||||
transition.updateFrame(node: self.gridNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
transition.updateFrame(node: self.gridNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||||
|
|
||||||
var duration: Double = 0.0
|
let updateSizeAndInsets = GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: insets, preloadSize: 100.0, type: .fixed(itemSize: CGSize(width: 66.0, height: 66.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition)
|
||||||
var curve: UInt = 0
|
|
||||||
switch transition {
|
|
||||||
case .immediate:
|
|
||||||
break
|
|
||||||
case let .animated(animationDuration, animationCurve):
|
|
||||||
duration = animationDuration
|
|
||||||
switch animationCurve {
|
|
||||||
case .easeInOut:
|
|
||||||
break
|
|
||||||
case .spring:
|
|
||||||
curve = 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let listViewCurve: ListViewAnimationCurve
|
|
||||||
if curve == 7 {
|
|
||||||
listViewCurve = .Spring(duration: duration)
|
|
||||||
} else {
|
|
||||||
listViewCurve = .Default(duration: duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
//let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: listViewCurve)
|
|
||||||
|
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: insets, preloadSize: 100.0, type: .fixed(itemSize: CGSize(width: 66.0, height: 66.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: updateSizeAndInsets, itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||||
|
|
||||||
|
self.backgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: 1000.0)
|
||||||
|
|
||||||
let dequeue = self.validLayout == nil
|
let dequeue = self.validLayout == nil
|
||||||
self.validLayout = (size, leftInset, rightInset, interfaceState)
|
self.validLayout = (size, leftInset, rightInset, interfaceState)
|
||||||
|
|
||||||
@ -260,7 +251,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
|||||||
let listViewFrame = self.gridNode.frame
|
let listViewFrame = self.gridNode.frame
|
||||||
return self.gridNode.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event)
|
return self.gridNode.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) {
|
private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) {
|
||||||
if self.stickersInteraction.previewedStickerItem != item {
|
if self.stickersInteraction.previewedStickerItem != item {
|
||||||
self.stickersInteraction.previewedStickerItem = item
|
self.stickersInteraction.previewedStickerItem = item
|
||||||
|
|||||||
@ -38,6 +38,20 @@ class IconSwitchNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public var positiveContentColor = UIColor(rgb: 0x00ff00) {
|
||||||
|
didSet {
|
||||||
|
if self.isNodeLoaded {
|
||||||
|
(self.view as! IconSwitchNodeView).setPositiveContentColor(self.positiveContentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public var negativeContentColor = UIColor(rgb: 0xff0000) {
|
||||||
|
didSet {
|
||||||
|
if self.isNodeLoaded {
|
||||||
|
(self.view as! IconSwitchNodeView).setNegativeContentColor(self.negativeContentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var _isOn: Bool = false
|
private var _isOn: Bool = false
|
||||||
public var isOn: Bool {
|
public var isOn: Bool {
|
||||||
@ -68,6 +82,8 @@ class IconSwitchNode: ASDisplayNode {
|
|||||||
(self.view as! UISwitch).tintColor = self.frameColor
|
(self.view as! UISwitch).tintColor = self.frameColor
|
||||||
//(self.view as! UISwitch).thumbTintColor = self.handleColor
|
//(self.view as! UISwitch).thumbTintColor = self.handleColor
|
||||||
(self.view as! UISwitch).onTintColor = self.contentColor
|
(self.view as! UISwitch).onTintColor = self.contentColor
|
||||||
|
(self.view as! IconSwitchNodeView).setNegativeContentColor(self.negativeContentColor)
|
||||||
|
(self.view as! IconSwitchNodeView).setPositiveContentColor(self.positiveContentColor)
|
||||||
|
|
||||||
(self.view as! UISwitch).setOn(self._isOn, animated: false)
|
(self.view as! UISwitch).setOn(self._isOn, animated: false)
|
||||||
|
|
||||||
|
|||||||
@ -75,10 +75,25 @@ private protocol ItemListSwitchNodeImpl {
|
|||||||
var frameColor: UIColor { get set }
|
var frameColor: UIColor { get set }
|
||||||
var contentColor: UIColor { get set }
|
var contentColor: UIColor { get set }
|
||||||
var handleColor: UIColor { get set }
|
var handleColor: UIColor { get set }
|
||||||
|
var positiveContentColor: UIColor { get set }
|
||||||
|
var negativeContentColor: UIColor { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SwitchNode: ItemListSwitchNodeImpl {
|
extension SwitchNode: ItemListSwitchNodeImpl {
|
||||||
|
var positiveContentColor: UIColor {
|
||||||
|
get {
|
||||||
|
return .white
|
||||||
|
} set(value) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var negativeContentColor: UIColor {
|
||||||
|
get {
|
||||||
|
return .white
|
||||||
|
} set(value) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IconSwitchNode: ItemListSwitchNodeImpl {
|
extension IconSwitchNode: ItemListSwitchNodeImpl {
|
||||||
@ -214,6 +229,8 @@ class ItemListSwitchItemNode: ListViewItemNode {
|
|||||||
strongSelf.switchNode.frameColor = item.theme.list.itemSwitchColors.frameColor
|
strongSelf.switchNode.frameColor = item.theme.list.itemSwitchColors.frameColor
|
||||||
strongSelf.switchNode.contentColor = item.theme.list.itemSwitchColors.contentColor
|
strongSelf.switchNode.contentColor = item.theme.list.itemSwitchColors.contentColor
|
||||||
strongSelf.switchNode.handleColor = item.theme.list.itemSwitchColors.handleColor
|
strongSelf.switchNode.handleColor = item.theme.list.itemSwitchColors.handleColor
|
||||||
|
strongSelf.switchNode.positiveContentColor = item.theme.list.itemSwitchColors.positiveColor
|
||||||
|
strongSelf.switchNode.negativeContentColor = item.theme.list.itemSwitchColors.negativeColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
|||||||
@ -21,7 +21,7 @@ func stickerFromLegacyDocument(_ documentAttachment: TGDocumentMediaAttachment)
|
|||||||
fileReference = data
|
fileReference = data
|
||||||
}
|
}
|
||||||
|
|
||||||
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: documentAttachment.documentId), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(documentAttachment.datacenterId), fileId: documentAttachment.documentId, accessHash: documentAttachment.accessHash, size: Int(documentAttachment.size), fileReference: fileReference, fileName: documentAttachment.fileName()), previewRepresentations: [], mimeType: documentAttachment.mimeType, size: Int(documentAttachment.size), attributes: attributes)
|
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: documentAttachment.documentId), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(documentAttachment.datacenterId), fileId: documentAttachment.documentId, accessHash: documentAttachment.accessHash, size: Int(documentAttachment.size), fileReference: fileReference, fileName: documentAttachment.fileName()), previewRepresentations: [], immediateThumbnailData: nil, mimeType: documentAttachment.mimeType, size: Int(documentAttachment.size), attributes: attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -184,7 +184,7 @@ final class LegacyStickerImageDataSource: TGImageDataSource {
|
|||||||
attributes.append(.Sticker(displayText: "", packReference: .id(id: stickerPackId, accessHash: stickerPackAccessHash), maskData: nil))
|
attributes.append(.Sticker(displayText: "", packReference: .id(id: stickerPackId, accessHash: stickerPackAccessHash), maskData: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
return LegacyStickerImageDataTask(account: account, file: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: documentId), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: datacenterId, fileId: documentId, accessHash: accessHash, size: size, fileReference: nil, fileName: fileNameFromFileAttributes(attributes)), previewRepresentations: [], mimeType: "image/webp", size: size, attributes: attributes), small: !highQuality, fitSize: fitSize, completion: { image in
|
return LegacyStickerImageDataTask(account: account, file: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: documentId), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: datacenterId, fileId: documentId, accessHash: accessHash, size: size, fileReference: nil, fileName: fileNameFromFileAttributes(attributes)), previewRepresentations: [], immediateThumbnailData: nil, mimeType: "image/webp", size: size, attributes: attributes), small: !highQuality, fitSize: fitSize, completion: { image in
|
||||||
if let image = image {
|
if let image = image {
|
||||||
sharedImageCache.setImage(image, forKey: uri, attributes: nil)
|
sharedImageCache.setImage(image, forKey: uri, attributes: nil)
|
||||||
completion?(TGDataResource(image: image, decoded: true))
|
completion?(TGDataResource(image: image, decoded: true))
|
||||||
|
|||||||
@ -158,7 +158,7 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: Int(finalDuration), size: finalDimensions, flags: [.instantRoundVideo])])
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: Int(finalDuration), size: finalDimensions, flags: [.instantRoundVideo])])
|
||||||
let attributes: [MessageAttribute] = []
|
let attributes: [MessageAttribute] = []
|
||||||
send(.message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil))
|
send(.message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -237,7 +237,7 @@ func legacyEnqueueGifMessage(account: Account, data: Data) -> Signal<EnqueueMess
|
|||||||
fileAttributes.append(.FileName(fileName: fileName))
|
fileAttributes.append(.FileName(fileName: fileName))
|
||||||
fileAttributes.append(.Animated)
|
fileAttributes.append(.Animated)
|
||||||
|
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, mimeType: "video/mp4", size: nil, attributes: fileAttributes)
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes)
|
||||||
subscriber.putNext(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil))
|
subscriber.putNext(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
@ -284,7 +284,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
|
|||||||
arc4random_buf(&randomId, 8)
|
arc4random_buf(&randomId, 8)
|
||||||
let _ = try? heicData.write(to: URL(fileURLWithPath: tempFilePath + ".heic"))
|
let _ = try? heicData.write(to: URL(fileURLWithPath: tempFilePath + ".heic"))
|
||||||
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath + ".heic", randomId: randomId)
|
let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath + ".heic", randomId: randomId)
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], mimeType: "image/heic", size: nil, attributes: [.FileName(fileName: "image.heic")])
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: "image/heic", size: nil, attributes: [.FileName(fileName: "image.heic")])
|
||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
if let timer = item.timer, timer > 0 && timer <= 60 {
|
if let timer = item.timer, timer > 0 && timer <= 60 {
|
||||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
|
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
|
||||||
@ -336,13 +336,13 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
|
|||||||
var randomId: Int64 = 0
|
var randomId: Int64 = 0
|
||||||
arc4random_buf(&randomId, 8)
|
arc4random_buf(&randomId, 8)
|
||||||
let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: randomId)
|
let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: randomId)
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)])
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)])
|
||||||
messages.append(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
|
messages.append(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
|
||||||
case let .asset(asset):
|
case let .asset(asset):
|
||||||
var randomId: Int64 = 0
|
var randomId: Int64 = 0
|
||||||
arc4random_buf(&randomId, 8)
|
arc4random_buf(&randomId, 8)
|
||||||
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: arc4random64())
|
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: arc4random64())
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)])
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)])
|
||||||
messages.append(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
|
messages.append(.message(text: caption ?? "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -424,7 +424,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, mimeType: "video/mp4", size: nil, attributes: fileAttributes)
|
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes)
|
||||||
var attributes: [MessageAttribute] = []
|
var attributes: [MessageAttribute] = []
|
||||||
if let timer = item.timer, timer > 0 && timer <= 60 {
|
if let timer = item.timer, timer > 0 && timer <= 60 {
|
||||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
|
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
|
||||||
|
|||||||
3
TelegramUI/MergedItemListItem.swift
Normal file
3
TelegramUI/MergedItemListItem.swift
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
@ -193,5 +193,28 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
|||||||
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
(navigationController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .wallpaper(slug):
|
||||||
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
|
||||||
|
present(controller, nil)
|
||||||
|
let _ = (getWallpaper(account: account, slug: slug)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak controller] wallpaper in
|
||||||
|
controller?.dismiss()
|
||||||
|
let wallpaperController = WallpaperListPreviewController(account: account, source: .wallpaper(wallpaper))
|
||||||
|
present(wallpaperController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
}, error: { [weak controller] error in
|
||||||
|
controller?.dismiss()
|
||||||
|
|
||||||
|
// let text: String
|
||||||
|
// switch error {
|
||||||
|
// case .limitExceeded:
|
||||||
|
// text = presentationData.strings.Login_CodeFloodError
|
||||||
|
// case .generic:
|
||||||
|
// text = presentationData.strings.Login_UnknownError
|
||||||
|
// }
|
||||||
|
// let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
// present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
})
|
||||||
|
dismissInput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -544,6 +544,22 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic
|
|||||||
convertedUrl = "https://t.me/confirmphone?phone=\(phone)&hash=\(hash)"
|
convertedUrl = "https://t.me/confirmphone?phone=\(phone)&hash=\(hash)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if parsedUrl.host == "bg" {
|
||||||
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
|
var slug: String?
|
||||||
|
if let queryItems = components.queryItems {
|
||||||
|
for queryItem in queryItems {
|
||||||
|
if let value = queryItem.value {
|
||||||
|
if queryItem.name == "slug" {
|
||||||
|
slug = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let slug = slug {
|
||||||
|
convertedUrl = "https://t.me/bg/\(slug)"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedUrl.host == "resolve" {
|
if parsedUrl.host == "resolve" {
|
||||||
|
|||||||
@ -130,6 +130,7 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe
|
|||||||
let fullSizeResource = fileReference.media.resource
|
let fullSizeResource = fileReference.media.resource
|
||||||
|
|
||||||
let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource, pathExtension: pathExtension)
|
let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource, pathExtension: pathExtension)
|
||||||
|
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
|
||||||
|
|
||||||
let signal = maybeFullSize
|
let signal = maybeFullSize
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -138,18 +139,26 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe
|
|||||||
return .single((nil, maybeData.path, true))
|
return .single((nil, maybeData.path, true))
|
||||||
} else {
|
} else {
|
||||||
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError>
|
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError>
|
||||||
if let thumbnailResource = thumbnailResource {
|
if !fetched, let _ = decodedThumbnailData {
|
||||||
|
fetchedThumbnail = .single(.local)
|
||||||
|
} else if let thumbnailResource = thumbnailResource {
|
||||||
fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource), statsCategory: statsCategoryForFileWithAttributes(fileReference.media.attributes))
|
fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource), statsCategory: statsCategoryForFileWithAttributes(fileReference.media.attributes))
|
||||||
} else {
|
} else {
|
||||||
fetchedThumbnail = .complete()
|
fetchedThumbnail = .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
let thumbnail: Signal<Data?, NoError>
|
let thumbnail: Signal<Data?, NoError>
|
||||||
if let thumbnailResource = thumbnailResource {
|
if !fetched, let decodedThumbnailData = decodedThumbnailData {
|
||||||
|
thumbnail = .single(decodedThumbnailData)
|
||||||
|
} else if let thumbnailResource = thumbnailResource {
|
||||||
thumbnail = Signal { subscriber in
|
thumbnail = Signal { subscriber in
|
||||||
let fetchedDisposable = fetchedThumbnail.start()
|
let fetchedDisposable = fetchedThumbnail.start()
|
||||||
let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in
|
let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in
|
||||||
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
if next.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) {
|
||||||
|
subscriber.putNext(data)
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(nil)
|
||||||
|
}
|
||||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||||
|
|
||||||
return ActionDisposable {
|
return ActionDisposable {
|
||||||
@ -186,14 +195,21 @@ private let thumbnailGenerationMimeTypes: Set<String> = Set([
|
|||||||
|
|
||||||
private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false) -> Signal<(Data?, String?, Bool), NoError> {
|
private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false) -> Signal<(Data?, String?, Bool), NoError> {
|
||||||
let thumbnailResource = smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource
|
let thumbnailResource = smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource
|
||||||
|
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
|
||||||
|
|
||||||
if !thumbnailGenerationMimeTypes.contains(fileReference.media.mimeType) {
|
if !thumbnailGenerationMimeTypes.contains(fileReference.media.mimeType) {
|
||||||
if let thumbnailResource = thumbnailResource {
|
if let decodedThumbnailData = decodedThumbnailData {
|
||||||
|
return .single((decodedThumbnailData, nil, false))
|
||||||
|
} else if let thumbnailResource = thumbnailResource {
|
||||||
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError> = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource))
|
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError> = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource))
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let fetchedDisposable = fetchedThumbnail.start()
|
let fetchedDisposable = fetchedThumbnail.start()
|
||||||
let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in
|
let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in
|
||||||
subscriber.putNext(((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])), nil, false))
|
if next.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) {
|
||||||
|
subscriber.putNext((data, nil, false))
|
||||||
|
} else {
|
||||||
|
subscriber.putNext((nil, nil, false))
|
||||||
|
}
|
||||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||||
|
|
||||||
return ActionDisposable {
|
return ActionDisposable {
|
||||||
@ -218,18 +234,26 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference:
|
|||||||
return .single((nil, maybeData.path, true))
|
return .single((nil, maybeData.path, true))
|
||||||
} else {
|
} else {
|
||||||
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError>
|
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError>
|
||||||
if let thumbnailResource = thumbnailResource {
|
if let _ = fileReference.media.immediateThumbnailData {
|
||||||
|
fetchedThumbnail = .complete()
|
||||||
|
} else if let thumbnailResource = thumbnailResource {
|
||||||
fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource))
|
fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource))
|
||||||
} else {
|
} else {
|
||||||
fetchedThumbnail = .complete()
|
fetchedThumbnail = .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
let thumbnail: Signal<Data?, NoError>
|
let thumbnail: Signal<Data?, NoError>
|
||||||
if let thumbnailResource = thumbnailResource {
|
if let decodedThumbnailData = decodedThumbnailData {
|
||||||
|
thumbnail = .single(decodedThumbnailData)
|
||||||
|
} else if let thumbnailResource = thumbnailResource {
|
||||||
thumbnail = Signal { subscriber in
|
thumbnail = Signal { subscriber in
|
||||||
let fetchedDisposable = fetchedThumbnail.start()
|
let fetchedDisposable = fetchedThumbnail.start()
|
||||||
let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in
|
let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in
|
||||||
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
if next.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) {
|
||||||
|
subscriber.putNext(data)
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(nil)
|
||||||
|
}
|
||||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||||
|
|
||||||
return ActionDisposable {
|
return ActionDisposable {
|
||||||
@ -2172,9 +2196,10 @@ func instantPageImageFile(account: Account, fileReference: FileMediaReference, f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func avatarGalleryPhotoDatas(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||||
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) {
|
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) {
|
||||||
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
|
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
|
||||||
|
let decodedThumbnailData = fileReference?.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
|
||||||
|
|
||||||
let signal = maybeFullSize
|
let signal = maybeFullSize
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -2183,18 +2208,29 @@ private func avatarGalleryPhotoDatas(account: Account, representations: [ImageRe
|
|||||||
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
|
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
|
||||||
return .single((nil, loadedData, true))
|
return .single((nil, loadedData, true))
|
||||||
} else {
|
} else {
|
||||||
let fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].reference)
|
let fetchedThumbnail: Signal<FetchResourceSourceType, NoError>
|
||||||
|
if let _ = decodedThumbnailData {
|
||||||
|
fetchedThumbnail = .complete()
|
||||||
|
} else {
|
||||||
|
fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].reference)
|
||||||
|
}
|
||||||
|
|
||||||
let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].reference)
|
let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].reference)
|
||||||
|
|
||||||
let thumbnail = Signal<Data?, NoError> { subscriber in
|
let thumbnail: Signal<Data?, NoError>
|
||||||
let fetchedDisposable = fetchedThumbnail.start()
|
if let decodedThumbnailData = decodedThumbnailData {
|
||||||
let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in
|
thumbnail = .single(decodedThumbnailData)
|
||||||
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
} else {
|
||||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
thumbnail = Signal<Data?, NoError> { subscriber in
|
||||||
|
let fetchedDisposable = fetchedThumbnail.start()
|
||||||
return ActionDisposable {
|
let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in
|
||||||
fetchedDisposable.dispose()
|
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
||||||
thumbnailDisposable.dispose()
|
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
fetchedDisposable.dispose()
|
||||||
|
thumbnailDisposable.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2219,7 +2255,6 @@ private func avatarGalleryPhotoDatas(account: Account, representations: [ImageRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return thumbnail |> mapToSignal { thumbnailData in
|
return thumbnail |> mapToSignal { thumbnailData in
|
||||||
return fullSizeData |> map { (fullSizeData, complete) in
|
return fullSizeData |> map { (fullSizeData, complete) in
|
||||||
return (thumbnailData, fullSizeData, complete)
|
return (thumbnailData, fullSizeData, complete)
|
||||||
@ -2234,8 +2269,8 @@ private func avatarGalleryPhotoDatas(account: Account, representations: [ImageRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
func chatAvatarGalleryPhoto(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize)
|
let signal = avatarGalleryPhotoDatas(account: account, fileReference: fileReference, representations: representations, autoFetchFullSize: autoFetchFullSize)
|
||||||
|
|
||||||
return signal
|
return signal
|
||||||
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
||||||
|
|||||||
@ -46,6 +46,7 @@ public final class PresentationData: Equatable {
|
|||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let chatWallpaper: TelegramWallpaper
|
public let chatWallpaper: TelegramWallpaper
|
||||||
|
public let chatWallpaperMode: PresentationWallpaperMode
|
||||||
public let volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons
|
public let volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons
|
||||||
public let fontSize: PresentationFontSize
|
public let fontSize: PresentationFontSize
|
||||||
public let dateTimeFormat: PresentationDateTimeFormat
|
public let dateTimeFormat: PresentationDateTimeFormat
|
||||||
@ -53,10 +54,11 @@ public final class PresentationData: Equatable {
|
|||||||
public let nameSortOrder: PresentationPersonNameOrder
|
public let nameSortOrder: PresentationPersonNameOrder
|
||||||
public let disableAnimations: Bool
|
public let disableAnimations: Bool
|
||||||
|
|
||||||
public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
public init(strings: PresentationStrings, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatWallpaperMode: PresentationWallpaperMode, volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, nameSortOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.chatWallpaper = chatWallpaper
|
self.chatWallpaper = chatWallpaper
|
||||||
|
self.chatWallpaperMode = chatWallpaperMode
|
||||||
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
|
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
|
||||||
self.fontSize = fontSize
|
self.fontSize = fontSize
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
@ -66,7 +68,7 @@ public final class PresentationData: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
|
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
|
||||||
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations
|
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatWallpaperMode == rhs.chatWallpaperMode && lhs.volumeControlStatusBarIcons == rhs.volumeControlStatusBarIcons && lhs.fontSize == rhs.fontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,6 +236,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
|
|||||||
|
|
||||||
let effectiveTheme: PresentationThemeReference
|
let effectiveTheme: PresentationThemeReference
|
||||||
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
|
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
|
||||||
|
var effectiveChatWallpaperMode: PresentationWallpaperMode = themeSettings.chatWallpaperMode
|
||||||
|
|
||||||
if automaticThemeShouldSwitchNow(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) {
|
if automaticThemeShouldSwitchNow(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) {
|
||||||
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
|
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
|
||||||
@ -242,8 +245,10 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
|
|||||||
switch themeSettings.automaticThemeSwitchSetting.theme {
|
switch themeSettings.automaticThemeSwitchSetting.theme {
|
||||||
case .nightAccent:
|
case .nightAccent:
|
||||||
effectiveChatWallpaper = .color(0x18222d)
|
effectiveChatWallpaper = .color(0x18222d)
|
||||||
|
effectiveChatWallpaperMode = .still
|
||||||
case .nightGrayscale:
|
case .nightGrayscale:
|
||||||
effectiveChatWallpaper = .color(0x000000)
|
effectiveChatWallpaper = .color(0x000000)
|
||||||
|
effectiveChatWallpaperMode = .still
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -276,7 +281,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
|
|||||||
let dateTimeFormat = currentDateTimeFormat()
|
let dateTimeFormat = currentDateTimeFormat()
|
||||||
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
||||||
let nameSortOrder = currentPersonNameSortOrder()
|
let nameSortOrder = currentPersonNameSortOrder()
|
||||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
|
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperMode: effectiveChatWallpaperMode, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,6 +375,8 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
let themeValue: PresentationTheme
|
let themeValue: PresentationTheme
|
||||||
let effectiveTheme: PresentationThemeReference
|
let effectiveTheme: PresentationThemeReference
|
||||||
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
|
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
|
||||||
|
var effectiveChatWallpaperMode: PresentationWallpaperMode = themeSettings.chatWallpaperMode
|
||||||
|
|
||||||
if shouldSwitch {
|
if shouldSwitch {
|
||||||
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
|
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
|
||||||
switch effectiveChatWallpaper {
|
switch effectiveChatWallpaper {
|
||||||
@ -377,8 +384,10 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
switch themeSettings.automaticThemeSwitchSetting.theme {
|
switch themeSettings.automaticThemeSwitchSetting.theme {
|
||||||
case .nightAccent:
|
case .nightAccent:
|
||||||
effectiveChatWallpaper = .color(0x18222d)
|
effectiveChatWallpaper = .color(0x18222d)
|
||||||
|
effectiveChatWallpaperMode = .still
|
||||||
case .nightGrayscale:
|
case .nightGrayscale:
|
||||||
effectiveChatWallpaper = .color(0x000000)
|
effectiveChatWallpaper = .color(0x000000)
|
||||||
|
effectiveChatWallpaperMode = .still
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -420,7 +429,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
|
|||||||
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
let nameDisplayOrder = contactSettings.nameDisplayOrder
|
||||||
let nameSortOrder = currentPersonNameSortOrder()
|
let nameSortOrder = currentPersonNameSortOrder()
|
||||||
|
|
||||||
return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, chatWallpaperMode: effectiveChatWallpaperMode, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
@ -436,5 +445,5 @@ public func defaultPresentationData() -> PresentationData {
|
|||||||
let nameSortOrder = currentPersonNameSortOrder()
|
let nameSortOrder = currentPersonNameSortOrder()
|
||||||
|
|
||||||
let themeSettings = PresentationThemeSettings.defaultSettings
|
let themeSettings = PresentationThemeSettings.defaultSettings
|
||||||
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, chatWallpaperMode: .still, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -219,7 +219,6 @@ enum PresentationResourceKey: Int32 {
|
|||||||
case groupInfoAdminsIcon
|
case groupInfoAdminsIcon
|
||||||
case groupInfoPermissionsIcon
|
case groupInfoPermissionsIcon
|
||||||
case groupInfoMembersIcon
|
case groupInfoMembersIcon
|
||||||
case groupInfoBannedIcon
|
|
||||||
|
|
||||||
case emptyChatListCheckIcon
|
case emptyChatListCheckIcon
|
||||||
}
|
}
|
||||||
|
|||||||
@ -953,12 +953,6 @@ struct PresentationResourcesChat {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static func groupInfoBannedIcon(_ theme: PresentationTheme) -> UIImage? {
|
|
||||||
return theme.image(PresentationResourceKey.groupInfoBannedIcon.rawValue, { _ in
|
|
||||||
return UIImage(bundleImageName: "Chat/Info/GroupBannedIcon")?.precomposed()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static func emptyChatListCheckIcon(_ theme: PresentationTheme) -> UIImage? {
|
static func emptyChatListCheckIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.emptyChatListCheckIcon.rawValue, { _ in
|
return theme.image(PresentationResourceKey.emptyChatListCheckIcon.rawValue, { _ in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/ListCheckIcon"), color: theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/ListCheckIcon"), color: theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -210,11 +210,15 @@ public final class PresentationThemeSwitch {
|
|||||||
public let frameColor: UIColor
|
public let frameColor: UIColor
|
||||||
public let handleColor: UIColor
|
public let handleColor: UIColor
|
||||||
public let contentColor: UIColor
|
public let contentColor: UIColor
|
||||||
|
public let positiveColor: UIColor
|
||||||
|
public let negativeColor: UIColor
|
||||||
|
|
||||||
public init(frameColor: UIColor, handleColor: UIColor, contentColor: UIColor) {
|
public init(frameColor: UIColor, handleColor: UIColor, contentColor: UIColor, positiveColor: UIColor, negativeColor: UIColor) {
|
||||||
self.frameColor = frameColor
|
self.frameColor = frameColor
|
||||||
self.handleColor = handleColor
|
self.handleColor = handleColor
|
||||||
self.contentColor = contentColor
|
self.contentColor = contentColor
|
||||||
|
self.positiveColor = positiveColor
|
||||||
|
self.negativeColor = negativeColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,12 @@ public enum PresentationBuiltinThemeReference: Int32 {
|
|||||||
case nightAccent = 3
|
case nightAccent = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PresentationWallpaperMode: Int32 {
|
||||||
|
case still
|
||||||
|
case perspective
|
||||||
|
case blurred
|
||||||
|
}
|
||||||
|
|
||||||
public enum PresentationThemeReference: PostboxCoding, Equatable {
|
public enum PresentationThemeReference: PostboxCoding, Equatable {
|
||||||
case builtin(PresentationBuiltinThemeReference)
|
case builtin(PresentationBuiltinThemeReference)
|
||||||
|
|
||||||
@ -141,6 +147,7 @@ public struct AutomaticThemeSwitchSetting: PostboxCoding, Equatable {
|
|||||||
|
|
||||||
public struct PresentationThemeSettings: PreferencesEntry {
|
public struct PresentationThemeSettings: PreferencesEntry {
|
||||||
public var chatWallpaper: TelegramWallpaper
|
public var chatWallpaper: TelegramWallpaper
|
||||||
|
public var chatWallpaperMode: PresentationWallpaperMode
|
||||||
public var theme: PresentationThemeReference
|
public var theme: PresentationThemeReference
|
||||||
public var themeAccentColor: Int32?
|
public var themeAccentColor: Int32?
|
||||||
public var fontSize: PresentationFontSize
|
public var fontSize: PresentationFontSize
|
||||||
@ -157,11 +164,12 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static var defaultSettings: PresentationThemeSettings {
|
public static var defaultSettings: PresentationThemeSettings {
|
||||||
return PresentationThemeSettings(chatWallpaper: .builtin, theme: .builtin(.dayClassic), themeAccentColor: nil, fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent), disableAnimations: true)
|
return PresentationThemeSettings(chatWallpaper: .builtin, chatWallpaperMode: .still, theme: .builtin(.dayClassic), themeAccentColor: nil, fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent), disableAnimations: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, themeAccentColor: Int32?, fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, disableAnimations: Bool) {
|
public init(chatWallpaper: TelegramWallpaper, chatWallpaperMode: PresentationWallpaperMode, theme: PresentationThemeReference, themeAccentColor: Int32?, fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, disableAnimations: Bool) {
|
||||||
self.chatWallpaper = chatWallpaper
|
self.chatWallpaper = chatWallpaper
|
||||||
|
self.chatWallpaperMode = chatWallpaperMode
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.themeAccentColor = themeAccentColor
|
self.themeAccentColor = themeAccentColor
|
||||||
self.fontSize = fontSize
|
self.fontSize = fontSize
|
||||||
@ -171,6 +179,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
|||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin
|
self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin
|
||||||
|
self.chatWallpaperMode = PresentationWallpaperMode(rawValue: decoder.decodeInt32ForKey("m", orElse: 0)) ?? .still
|
||||||
self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as! PresentationThemeReference
|
self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as! PresentationThemeReference
|
||||||
self.themeAccentColor = decoder.decodeOptionalInt32ForKey("themeAccentColor")
|
self.themeAccentColor = decoder.decodeOptionalInt32ForKey("themeAccentColor")
|
||||||
self.fontSize = PresentationFontSize(rawValue: decoder.decodeInt32ForKey("f", orElse: PresentationFontSize.regular.rawValue)) ?? .regular
|
self.fontSize = PresentationFontSize(rawValue: decoder.decodeInt32ForKey("f", orElse: PresentationFontSize.regular.rawValue)) ?? .regular
|
||||||
@ -180,6 +189,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
|||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeObject(self.chatWallpaper, forKey: "w")
|
encoder.encodeObject(self.chatWallpaper, forKey: "w")
|
||||||
|
encoder.encodeInt32(self.chatWallpaperMode.rawValue, forKey: "m")
|
||||||
encoder.encodeObject(self.theme, forKey: "t")
|
encoder.encodeObject(self.theme, forKey: "t")
|
||||||
if let themeAccentColor = self.themeAccentColor {
|
if let themeAccentColor = self.themeAccentColor {
|
||||||
encoder.encodeInt32(themeAccentColor, forKey: "themeAccentColor")
|
encoder.encodeInt32(themeAccentColor, forKey: "themeAccentColor")
|
||||||
@ -200,7 +210,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PresentationThemeSettings, rhs: PresentationThemeSettings) -> Bool {
|
public static func ==(lhs: PresentationThemeSettings, rhs: PresentationThemeSettings) -> Bool {
|
||||||
return lhs.chatWallpaper == rhs.chatWallpaper && lhs.theme == rhs.theme && lhs.themeAccentColor == rhs.themeAccentColor && lhs.fontSize == rhs.fontSize && lhs.automaticThemeSwitchSetting == rhs.automaticThemeSwitchSetting && lhs.disableAnimations == rhs.disableAnimations
|
return lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatWallpaperMode == rhs.chatWallpaperMode && lhs.theme == rhs.theme && lhs.themeAccentColor == rhs.themeAccentColor && lhs.fontSize == rhs.fontSize && lhs.automaticThemeSwitchSetting == rhs.automaticThemeSwitchSetting && lhs.disableAnimations == rhs.disableAnimations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import LegacyComponents
|
|
||||||
import SwiftSignalKit
|
|
||||||
|
|
||||||
enum RadialPlayPauseMode {
|
|
||||||
case play
|
|
||||||
case pause
|
|
||||||
}
|
|
||||||
|
|
||||||
class RadialPlayPauseContentNode: RadialStatusContentNode {
|
|
||||||
var color: UIColor {
|
|
||||||
didSet {
|
|
||||||
self.leftShape.fillColor = self.color.cgColor
|
|
||||||
self.rightShape.fillColor = self.color.cgColor
|
|
||||||
self.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var effectiveProgress: CGFloat = 1.0 {
|
|
||||||
didSet {
|
|
||||||
self.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var animationCompletionTimer: SwiftSignalKit.Timer?
|
|
||||||
|
|
||||||
private var isAnimatingProgress: Bool {
|
|
||||||
return self.pop_animation(forKey: "progress") != nil || self.animationCompletionTimer != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private var enqueuedReadyForTransition: (() -> Void)?
|
|
||||||
|
|
||||||
private let leftShape = CAShapeLayer()
|
|
||||||
private let rightShape = CAShapeLayer()
|
|
||||||
|
|
||||||
init(color: UIColor, mode: RadialPlayPauseMode) {
|
|
||||||
self.color = color
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.leftShape.fillColor = self.color.cgColor
|
|
||||||
self.rightShape.fillColor = self.color.cgColor
|
|
||||||
|
|
||||||
self.isLayerBacked = true
|
|
||||||
self.isOpaque = false
|
|
||||||
|
|
||||||
self.layer.addSublayer(self.leftShape)
|
|
||||||
self.layer.addSublayer(self.rightShape)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func enqueueReadyForTransition(_ f: @escaping () -> Void) {
|
|
||||||
if self.isAnimatingProgress {
|
|
||||||
self.enqueuedReadyForTransition = f
|
|
||||||
} else {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -183,8 +183,13 @@ public final class RadialStatusNode: ASControlNode {
|
|||||||
contentNode.animateIn(from: fromState)
|
contentNode.animateIn(from: fromState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: previousContentNode, animated: animated, completion: completion)
|
if backgroundColor != nil {
|
||||||
|
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: previousContentNode, animated: animated, completion: completion)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
if backgroundColor == nil {
|
||||||
|
strongSelf.transitionToBackgroundColor(backgroundColor, previousContentNode: previousContentNode, animated: animated, completion: completion)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
previousContentNode.removeFromSupernode()
|
previousContentNode.removeFromSupernode()
|
||||||
strongSelf.contentNode = strongSelf.nextContentNode
|
strongSelf.contentNode = strongSelf.nextContentNode
|
||||||
|
|||||||
Binary file not shown.
@ -65,9 +65,9 @@ class SearchBarPlaceholderNode: ASDisplayNode {
|
|||||||
self.iconNode.displayWithoutProcessing = true
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.isOpaque = true
|
self.labelNode.isOpaque = false
|
||||||
self.labelNode.isLayerBacked = true
|
self.labelNode.isLayerBacked = true
|
||||||
self.labelNode.backgroundColor = self.foregroundColor
|
//self.labelNode.backgroundColor = self.foregroundColor
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ class SearchBarPlaceholderNode: ASDisplayNode {
|
|||||||
let currentIconColor = self.iconColor
|
let currentIconColor = self.iconColor
|
||||||
|
|
||||||
return { placeholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
|
return { placeholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
|
||||||
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: foregroundColor, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
var updatedColor: UIColor?
|
var updatedColor: UIColor?
|
||||||
var updatedIconImage: UIImage?
|
var updatedIconImage: UIImage?
|
||||||
@ -111,7 +111,7 @@ class SearchBarPlaceholderNode: ASDisplayNode {
|
|||||||
strongSelf.backgroundNode.isUserInteractionEnabled = expansionProgress > 1.0 - CGFloat.ulpOfOne
|
strongSelf.backgroundNode.isUserInteractionEnabled = expansionProgress > 1.0 - CGFloat.ulpOfOne
|
||||||
|
|
||||||
if let updatedColor = updatedColor {
|
if let updatedColor = updatedColor {
|
||||||
strongSelf.labelNode.backgroundColor = updatedColor
|
//strongSelf.labelNode.backgroundColor = updatedColor
|
||||||
strongSelf.backgroundNode.backgroundColor = updatedColor
|
strongSelf.backgroundNode.backgroundColor = updatedColor
|
||||||
}
|
}
|
||||||
if let updatedIconImage = updatedIconImage {
|
if let updatedIconImage = updatedIconImage {
|
||||||
|
|||||||
@ -3,13 +3,17 @@ import Postbox
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
func storedMessageFromSearchPeer(account: Account, peer: Peer) -> Signal<Void, NoError> {
|
func storedMessageFromSearchPeer(account: Account, peer: Peer) -> Signal<PeerId, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Void in
|
return account.postbox.transaction { transaction -> PeerId in
|
||||||
if transaction.getPeer(peer.id) == nil {
|
if transaction.getPeer(peer.id) == nil {
|
||||||
updatePeers(transaction: transaction, peers: [peer], update: { previousPeer, updatedPeer in
|
updatePeers(transaction: transaction, peers: [peer], update: { previousPeer, updatedPeer in
|
||||||
return updatedPeer
|
return updatedPeer
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if let group = transaction.getPeer(peer.id) as? TelegramGroup, let migrationReference = group.migrationReference {
|
||||||
|
return migrationReference.peerId
|
||||||
|
}
|
||||||
|
return peer.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -83,7 +83,7 @@ public final class TelegramRootController: NavigationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(ThemeGridController(account: self.account, mode: .wallpapers))
|
//(controller.navigationController as? NavigationController)?.pushViewController(ThemeGridController(account: self.account, mode: .wallpapers))
|
||||||
|
|
||||||
// let wrapperNode = ASDisplayNode()
|
// let wrapperNode = ASDisplayNode()
|
||||||
// let bounds = controller.displayNode.bounds
|
// let bounds = controller.displayNode.bounds
|
||||||
|
|||||||
@ -220,7 +220,7 @@ class ThemeGalleryController: ViewController {
|
|||||||
wallpaper = value
|
wallpaper = value
|
||||||
}
|
}
|
||||||
let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in
|
let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperMode: .still, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
}) |> deliverOnMainQueue).start(completed: {
|
}) |> deliverOnMainQueue).start(completed: {
|
||||||
self?.dismiss(forceAway: true)
|
self?.dismiss(forceAway: true)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -58,7 +58,7 @@ final class ThemeGridController: ViewController {
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search, activate: { [weak self] in
|
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search, activate: { [weak self] in
|
||||||
self?.activateSearch()
|
//self?.activateSearch()
|
||||||
})
|
})
|
||||||
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
|
||||||
}
|
}
|
||||||
@ -147,12 +147,17 @@ final class ThemeGridController: ViewController {
|
|||||||
|
|
||||||
let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)])
|
let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)])
|
||||||
let _ = (updatePresentationThemeSettingsInteractively(postbox: self.account.postbox, { current in
|
let _ = (updatePresentationThemeSettingsInteractively(postbox: self.account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperMode: .still, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
}) |> deliverOnMainQueue).start(completed: { [weak self] in
|
}) |> deliverOnMainQueue).start()
|
||||||
let _ = (self?.navigationController as? NavigationController)?.popViewController(animated: true)
|
|
||||||
})
|
|
||||||
|
|
||||||
let _ = uploadWallpaper(account: self.account, resource: resource)
|
let account = self.account
|
||||||
|
let _ = uploadWallpaper(account: account, resource: resource).start(next: { status in
|
||||||
|
if case let .complete(wallpaper) = status {
|
||||||
|
let _ = (updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
||||||
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperMode: .still, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
|
})).start()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
@ -168,8 +168,9 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
let transition = combineLatest(telegramWallpapers(postbox: account.postbox, network: account.network), account.telegramApplicationContext.presentationData)
|
let transition = combineLatest(telegramWallpapers(postbox: account.postbox, network: account.network), account.telegramApplicationContext.presentationData)
|
||||||
|> map { wallpapers, presentationData -> (ThemeGridEntryTransition, Bool) in
|
|> map { wallpapers, presentationData -> (ThemeGridEntryTransition, Bool) in
|
||||||
var entries: [ThemeGridControllerEntry] = []
|
var entries: [ThemeGridControllerEntry] = []
|
||||||
var index = 0
|
var index = 1
|
||||||
|
|
||||||
|
var hasCurrent = false
|
||||||
switch presentationData.theme.name {
|
switch presentationData.theme.name {
|
||||||
case let .builtin(name):
|
case let .builtin(name):
|
||||||
switch name {
|
switch name {
|
||||||
@ -177,15 +178,21 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
break
|
break
|
||||||
case .day:
|
case .day:
|
||||||
let wallpaper = TelegramWallpaper.color(0xffffff)
|
let wallpaper = TelegramWallpaper.color(0xffffff)
|
||||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: presentationData.chatWallpaper == wallpaper))
|
let selected = presentationData.chatWallpaper == wallpaper
|
||||||
|
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: selected))
|
||||||
|
hasCurrent = hasCurrent || selected
|
||||||
index += 1
|
index += 1
|
||||||
case .nightGrayscale:
|
case .nightGrayscale:
|
||||||
let wallpaper = TelegramWallpaper.color(0xffffff)
|
let wallpaper = TelegramWallpaper.color(0xffffff)
|
||||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: presentationData.chatWallpaper == wallpaper))
|
let selected = presentationData.chatWallpaper == wallpaper
|
||||||
|
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: selected))
|
||||||
|
hasCurrent = hasCurrent || selected
|
||||||
index += 1
|
index += 1
|
||||||
case .nightAccent:
|
case .nightAccent:
|
||||||
let wallpaper = TelegramWallpaper.color(0xffffff)
|
let wallpaper = TelegramWallpaper.color(0xffffff)
|
||||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: presentationData.chatWallpaper == wallpaper))
|
let selected = presentationData.chatWallpaper == wallpaper
|
||||||
|
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: selected))
|
||||||
|
hasCurrent = hasCurrent || selected
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -193,9 +200,16 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for wallpaper in wallpapers {
|
for wallpaper in wallpapers {
|
||||||
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: presentationData.chatWallpaper == wallpaper))
|
let selected = presentationData.chatWallpaper == wallpaper
|
||||||
|
entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, selected: selected))
|
||||||
|
hasCurrent = hasCurrent || selected
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !hasCurrent {
|
||||||
|
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
let previous = previousEntries.swap(entries)
|
let previous = previousEntries.swap(entries)
|
||||||
return (preparedThemeGridEntryTransition(account: account, from: previous ?? [], to: entries, interaction: interaction), previous == nil)
|
return (preparedThemeGridEntryTransition(account: account, from: previous ?? [], to: entries, interaction: interaction), previous == nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -268,26 +268,25 @@ public func themeSettingsController(account: Account) -> ViewController {
|
|||||||
wallpaper = .color(0x18222D)
|
wallpaper = .color(0x18222D)
|
||||||
theme = .builtin(.nightAccent)
|
theme = .builtin(.nightAccent)
|
||||||
}
|
}
|
||||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperMode: .still, theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
}).start()
|
}).start()
|
||||||
}, selectFontSize: { size in
|
}, selectFontSize: { size in
|
||||||
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, chatWallpaperMode: current.chatWallpaperMode, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
}).start()
|
}).start()
|
||||||
}, openWallpaperSettings: {
|
}, openWallpaperSettings: {
|
||||||
pushControllerImpl?(ThemeGridController(account: account, mode: .wallpapers))
|
pushControllerImpl?(ThemeGridController(account: account, mode: .wallpapers))
|
||||||
}, openAccentColor: { color in
|
}, openAccentColor: { color in
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
|
||||||
presentControllerImpl?(ThemeAccentColorActionSheet(account: account, currentValue: color, applyValue: { color in
|
presentControllerImpl?(ThemeAccentColorActionSheet(account: account, currentValue: color, applyValue: { color in
|
||||||
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeAccentColor: color, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, chatWallpaperMode: current.chatWallpaperMode, theme: current.theme, themeAccentColor: color, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
}).start()
|
}).start()
|
||||||
}))
|
}))
|
||||||
}, openAutoNightTheme: {
|
}, openAutoNightTheme: {
|
||||||
pushControllerImpl?(themeAutoNightSettingsController(account: account))
|
pushControllerImpl?(themeAutoNightSettingsController(account: account))
|
||||||
}, disableAnimations: { disabled in
|
}, disableAnimations: { disabled in
|
||||||
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: disabled)
|
return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, chatWallpaperMode: current.chatWallpaperMode, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: disabled)
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ enum ParsedInternalUrl {
|
|||||||
case confirmationCode(Int)
|
case confirmationCode(Int)
|
||||||
case cancelAccountReset(phone: String, hash: String)
|
case cancelAccountReset(phone: String, hash: String)
|
||||||
case share(url: String?, text: String?, to: String?)
|
case share(url: String?, text: String?, to: String?)
|
||||||
|
case wallpaper(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ParsedUrl {
|
private enum ParsedUrl {
|
||||||
@ -41,6 +42,7 @@ enum ResolvedUrl {
|
|||||||
case confirmationCode(Int)
|
case confirmationCode(Int)
|
||||||
case cancelAccountReset(phone: String, hash: String)
|
case cancelAccountReset(phone: String, hash: String)
|
||||||
case share(url: String?, text: String?, to: String?)
|
case share(url: String?, text: String?, to: String?)
|
||||||
|
case wallpaper(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
||||||
@ -167,6 +169,8 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
} else if pathComponents[0] == "bg" {
|
||||||
|
return .wallpaper(pathComponents[1])
|
||||||
} else if let value = Int(pathComponents[1]) {
|
} else if let value = Int(pathComponents[1]) {
|
||||||
return .peerName(peerName, .channelMessage(Int32(value)))
|
return .peerName(peerName, .channelMessage(Int32(value)))
|
||||||
} else {
|
} else {
|
||||||
@ -233,6 +237,8 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
|
|||||||
return .single(.cancelAccountReset(phone: phone, hash: hash))
|
return .single(.cancelAccountReset(phone: phone, hash: hash))
|
||||||
case let .share(url, text, to):
|
case let .share(url, text, to):
|
||||||
return .single(.share(url: url, text: text, to: to))
|
return .single(.share(url: url, text: text, to: to))
|
||||||
|
case let .wallpaper(slug):
|
||||||
|
return .single(.wallpaper(slug))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -665,6 +665,8 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
|
|||||||
} else {
|
} else {
|
||||||
notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
|
notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
|
||||||
}
|
}
|
||||||
|
} else if case .default = notificationSettings.messageSound {
|
||||||
|
notificationsLabel = presentationData.strings.UserInfo_NotificationsEnabled
|
||||||
} else {
|
} else {
|
||||||
notificationsLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
notificationsLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,9 @@ final class WallpaperListPreviewController: ViewController {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
private var wallpaper: TelegramWallpaper?
|
||||||
|
private var wallpaperDisposable: Disposable?
|
||||||
|
|
||||||
private var didPlayPresentationAnimation = false
|
private var didPlayPresentationAnimation = false
|
||||||
|
|
||||||
init(account: Account, source: WallpaperListPreviewSource) {
|
init(account: Account, source: WallpaperListPreviewSource) {
|
||||||
@ -62,12 +65,12 @@ final class WallpaperListPreviewController: ViewController {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.presentationDataDisposable?.dispose()
|
self.presentationDataDisposable?.dispose()
|
||||||
|
self.wallpaperDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateThemeAndStrings() {
|
private func updateThemeAndStrings() {
|
||||||
self.title = self.presentationData.strings.BackgroundPreview_Title
|
self.title = self.presentationData.strings.BackgroundPreview_Title
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: self.presentationData.theme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.sharePressed))
|
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
@ -96,13 +99,13 @@ final class WallpaperListPreviewController: ViewController {
|
|||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = WallpaperListPreviewControllerNode(account: self.account, presentationData: self.presentationData, source: self.source, dismiss: { [weak self] in
|
self.displayNode = WallpaperListPreviewControllerNode(account: self.account, presentationData: self.presentationData, source: self.source, dismiss: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
}, apply: { [weak self] wallpaper in
|
}, apply: { [weak self] wallpaper, mode in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in
|
let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in
|
||||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperMode: mode, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||||
})
|
})
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
@ -110,6 +113,20 @@ final class WallpaperListPreviewController: ViewController {
|
|||||||
})
|
})
|
||||||
self._ready.set(self.controllerNode.ready.get())
|
self._ready.set(self.controllerNode.ready.get())
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.wallpaperDisposable = (self.controllerNode.currentWallpaper
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] wallpaper in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .file(_, _, _, _, slug, _, _) = wallpaper, let wallpaperSlug = slug, !wallpaperSlug.isEmpty {
|
||||||
|
strongSelf.wallpaper = wallpaper
|
||||||
|
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: strongSelf.presentationData.theme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(strongSelf.sharePressed))
|
||||||
|
} else {
|
||||||
|
strongSelf.navigationItem.rightBarButtonItem = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
@ -119,7 +136,9 @@ final class WallpaperListPreviewController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func sharePressed() {
|
@objc func sharePressed() {
|
||||||
let shareController = ShareController(account: account, subject: .url("link"))
|
if let wallpaper = self.wallpaper, case let .file(_, _, _, _, slug, _, _) = wallpaper, let wallpaperSlug = slug, !wallpaperSlug.isEmpty {
|
||||||
self.present(shareController, in: .window(.root), blockInteraction: true)
|
let shareController = ShareController(account: account, subject: .url("https://t.me/bg/\(wallpaperSlug)"))
|
||||||
|
self.present(shareController, in: .window(.root), blockInteraction: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,29 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
|
private enum WallpaperSegmentedControlStyle {
|
||||||
|
case dark
|
||||||
|
case light
|
||||||
|
|
||||||
|
var color: UIColor {
|
||||||
|
switch self {
|
||||||
|
case .dark:
|
||||||
|
return UIColor(rgb: 0x484848)
|
||||||
|
case .light:
|
||||||
|
return .white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class WallpaperBackgroundNode: ASDisplayNode {
|
private final class WallpaperBackgroundNode: ASDisplayNode {
|
||||||
let wallpaper: TelegramWallpaper
|
let wallpaper: TelegramWallpaper
|
||||||
private var fetchDisposable: Disposable?
|
private var fetchDisposable: Disposable?
|
||||||
private var statusDisposable: Disposable?
|
private var statusDisposable: Disposable?
|
||||||
private let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
private let statusNode: RadialStatusNode
|
private let statusNode: RadialStatusNode
|
||||||
|
|
||||||
|
let segmentedControlColor = Promise<UIColor>(.white)
|
||||||
|
|
||||||
init(account: Account, wallpaper: TelegramWallpaper) {
|
init(account: Account, wallpaper: TelegramWallpaper) {
|
||||||
self.wallpaper = wallpaper
|
self.wallpaper = wallpaper
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
@ -55,7 +71,7 @@ private final class WallpaperBackgroundNode: ASDisplayNode {
|
|||||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .standalone(resource: representation.resource)))
|
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: .standalone(resource: representation.resource)))
|
||||||
}
|
}
|
||||||
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .standalone(resource: file.file.resource)))
|
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource), reference: .standalone(resource: file.file.resource)))
|
||||||
signal = chatAvatarGalleryPhoto(account: account, representations: convertedRepresentations)
|
signal = chatMessageImageFile(account: account, fileReference: .standalone(media: file.file), thumbnail: false)
|
||||||
fetchSignal = fetchedMediaResource(postbox: account.postbox, reference: convertedRepresentations[convertedRepresentations.count - 1].reference)
|
fetchSignal = fetchedMediaResource(postbox: account.postbox, reference: convertedRepresentations[convertedRepresentations.count - 1].reference)
|
||||||
statusSignal = account.postbox.mediaBox.resourceStatus(file.file.resource)
|
statusSignal = account.postbox.mediaBox.resourceStatus(file.file.resource)
|
||||||
case let .image(representations):
|
case let .image(representations):
|
||||||
@ -101,6 +117,8 @@ private final class WallpaperBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.imageNode.contentMode = .scaleAspectFill
|
self.imageNode.contentMode = .scaleAspectFill
|
||||||
|
|
||||||
|
self.segmentedControlColor.set(.single(.white) |> then(chatBackgroundContrastColor(wallpaper: wallpaper, postbox: account.postbox)))
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -120,7 +138,7 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
private let account: Account
|
private let account: Account
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let dismiss: () -> Void
|
private let dismiss: () -> Void
|
||||||
private let apply: (TelegramWallpaper) -> Void
|
private let apply: (TelegramWallpaper, PresentationWallpaperMode) -> Void
|
||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
@ -133,6 +151,8 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
private let toolbarButtonApplyBackground: ASDisplayNode
|
private let toolbarButtonApplyBackground: ASDisplayNode
|
||||||
|
|
||||||
private let segmentedControl: UISegmentedControl
|
private let segmentedControl: UISegmentedControl
|
||||||
|
private var segmentedControlColor = Promise<UIColor>(.white)
|
||||||
|
private var segmentedControlColorDisposable: Disposable?
|
||||||
|
|
||||||
private var wallpapersDisposable: Disposable?
|
private var wallpapersDisposable: Disposable?
|
||||||
private var wallpapers: [TelegramWallpaper]?
|
private var wallpapers: [TelegramWallpaper]?
|
||||||
@ -142,9 +162,14 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var visibleBackgroundNodes: [WallpaperBackgroundNode] = []
|
private var visibleBackgroundNodes: [WallpaperBackgroundNode] = []
|
||||||
private var centralWallpaper: TelegramWallpaper?
|
private var centralWallpaper: TelegramWallpaper?
|
||||||
|
|
||||||
|
private let currentWallpaperPromise = Promise<TelegramWallpaper>()
|
||||||
|
var currentWallpaper: Signal<TelegramWallpaper, NoError> {
|
||||||
|
return self.currentWallpaperPromise.get()
|
||||||
|
}
|
||||||
private var visibleBackgroundNodesOffset: CGFloat = 0.0
|
private var visibleBackgroundNodesOffset: CGFloat = 0.0
|
||||||
|
|
||||||
init(account: Account, presentationData: PresentationData, source: WallpaperListPreviewSource, dismiss: @escaping () -> Void, apply: @escaping (TelegramWallpaper) -> Void) {
|
init(account: Account, presentationData: PresentationData, source: WallpaperListPreviewSource, dismiss: @escaping () -> Void, apply: @escaping (TelegramWallpaper, PresentationWallpaperMode) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
@ -223,6 +248,14 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged)
|
||||||
|
self.segmentedControlColorDisposable = (self.segmentedControlColor.get()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] color in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.segmentedControl.tintColor = color
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
switch source {
|
switch source {
|
||||||
case let .list(wallpapers, central):
|
case let .list(wallpapers, central):
|
||||||
self.wallpapers = wallpapers
|
self.wallpapers = wallpapers
|
||||||
@ -239,6 +272,9 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
self.ready.set(true)
|
self.ready.set(true)
|
||||||
}
|
}
|
||||||
|
if let wallpaper = self.centralWallpaper {
|
||||||
|
self.currentWallpaperPromise.set(.single(wallpaper))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -405,6 +441,11 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
} else {
|
} else {
|
||||||
itemNodeTransition = transition
|
itemNodeTransition = transition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if j == i {
|
||||||
|
self.segmentedControlColor.set(itemNode.segmentedControlColor.get())
|
||||||
|
}
|
||||||
|
|
||||||
itemNodeTransition.updateFrame(node: itemNode, frame: itemFrame)
|
itemNodeTransition.updateFrame(node: itemNode, frame: itemFrame)
|
||||||
itemNode.updateLayout(layout, navigationHeight: navigationBarHeight, transition: itemNodeTransition)
|
itemNode.updateLayout(layout, navigationHeight: navigationBarHeight, transition: itemNodeTransition)
|
||||||
visibleBackgroundNodes.append(itemNode)
|
visibleBackgroundNodes.append(itemNode)
|
||||||
@ -451,13 +492,64 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
|
|||||||
return super.hitTest(point, with: event)
|
return super.hitTest(point, with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func addParallaxToView(_ view: UIView) {
|
||||||
|
let amount = 16.0
|
||||||
|
|
||||||
|
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
|
||||||
|
horizontal.minimumRelativeValue = -amount
|
||||||
|
horizontal.maximumRelativeValue = amount
|
||||||
|
|
||||||
|
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
|
||||||
|
vertical.minimumRelativeValue = -amount
|
||||||
|
vertical.maximumRelativeValue = amount
|
||||||
|
|
||||||
|
let group = UIMotionEffectGroup()
|
||||||
|
group.motionEffects = [horizontal, vertical]
|
||||||
|
view.addMotionEffect(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeParallaxFromView(_ view: UIView) {
|
||||||
|
for effect in view.motionEffects {
|
||||||
|
view.removeMotionEffect(effect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func indexChanged() {
|
||||||
|
guard let mode = PresentationWallpaperMode(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == .perspective {
|
||||||
|
for node in self.visibleBackgroundNodes {
|
||||||
|
if node.wallpaper == self.centralWallpaper {
|
||||||
|
self.addParallaxToView(node.imageNode.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for node in self.visibleBackgroundNodes {
|
||||||
|
if node.wallpaper == self.centralWallpaper {
|
||||||
|
self.removeParallaxFromView(node.imageNode.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func cancelPressed() {
|
@objc private func cancelPressed() {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func applyPressed() {
|
@objc private func applyPressed() {
|
||||||
if let wallpaper = self.centralWallpaper {
|
if let wallpaper = self.centralWallpaper {
|
||||||
self.apply(wallpaper)
|
let mode: PresentationWallpaperMode
|
||||||
|
switch self.segmentedControl.selectedSegmentIndex {
|
||||||
|
case 1:
|
||||||
|
mode = .perspective
|
||||||
|
case 2:
|
||||||
|
mode = .blurred
|
||||||
|
default:
|
||||||
|
mode = .still
|
||||||
|
}
|
||||||
|
self.apply(wallpaper, mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ struct WebSearchGalleryEntry: Equatable {
|
|||||||
switch self.result {
|
switch self.result {
|
||||||
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
|
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
|
||||||
if let content = content, type == "gif", let thumbnailResource = thumbnail?.resource, let dimensions = content.dimensions {
|
if let content = content, type == "gif", let thumbnailResource = thumbnail?.resource, let dimensions = content.dimensions {
|
||||||
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
|
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
|
||||||
return WebSearchVideoGalleryItem(account: account, presentationData: presentationData, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction)
|
return WebSearchVideoGalleryItem(account: account, presentationData: presentationData, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), controllerInteraction: controllerInteraction)
|
||||||
}
|
}
|
||||||
case let .internalReference(_, _, _, _, _, _, file, _):
|
case let .internalReference(_, _, _, _, _, _, file, _):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user