Merge branch 'master' of gitlab.com:peter-iakovlev/TelegramUI

This commit is contained in:
Ilya Laktyushin 2019-01-08 22:41:07 +04:00
commit bdd47b456f
78 changed files with 2812 additions and 2292 deletions

View File

@ -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" : {

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

View File

@ -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" : {

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

View File

@ -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 */,

View File

@ -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

View File

@ -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

View File

@ -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?()
} }

View File

@ -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

View File

@ -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

View File

@ -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 = ""

View File

@ -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)

View File

@ -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))
})) }))

View File

@ -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 {

View File

@ -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)

View File

@ -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, _):

View 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)])
} }
} }

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)

View File

@ -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)
} }

View File

@ -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

View File

@ -770,6 +770,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, dismissInput: { }, dismissInput: {
self?.view.endEditing(true) self?.view.endEditing(true)
}) })
case let .wallpaper(slug):
break
} }
} }
})) }))

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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
} }

View File

@ -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())

View File

@ -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()

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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?

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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))

View File

@ -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))
} }

View File

@ -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))

View File

@ -0,0 +1,3 @@
import Foundation

View File

@ -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()
} }
} }

View File

@ -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" {

View File

@ -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

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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
} }
} }

View File

@ -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
} }
} }

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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
} }
} }

View File

@ -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

View File

@ -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)
}) })

View File

@ -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) {

View File

@ -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)
} }

View File

@ -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()
}) })

View File

@ -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))
} }
} }

View File

@ -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)
} }

View File

@ -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)
}
} }
} }

View File

@ -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)
} }
} }
} }

View File

@ -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, _):