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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_addoption@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_addoption@3x.png",
"scale" : "3x"
"filename" : "add.pdf"
}
],
"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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_deleteotion@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_deleteotion@3x.png",
"scale" : "3x"
"filename" : "delete.pdf"
}
],
"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 */; };
099529B221D24F5800805E13 /* RadialDownloadContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099529B121D24F5800805E13 /* RadialDownloadContentNode.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 */; };
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.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 */; };
D00580A021DCF0A200CB7CD3 /* WallpaperListPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005809F21DCF0A200CB7CD3 /* WallpaperListPreviewController.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 */; };
D007019C2029E8F2006B9E34 /* LegqacyICloudFileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007019B2029E8F2006B9E34 /* LegqacyICloudFileController.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>"; };
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>"; };
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>"; };
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>"; };
@ -1230,6 +1229,7 @@
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>"; };
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>"; };
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>"; };
@ -2549,7 +2549,6 @@
D01776B91F1D704F0044446D /* RadialStatusIconContentNode.swift */,
D0380DAA204EA72F000414AB /* RadialStatusSecretTimeoutContentNode.swift */,
099529B121D24F5800805E13 /* RadialDownloadContentNode.swift */,
099529F221D8FE8300805E13 /* RadialPlayPauseContentNode.swift */,
);
name = "Radial Status";
sourceTree = "<group>";
@ -3921,6 +3920,7 @@
D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */,
D09AEFD31E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift */,
D0BFAE5A20AB35D200793CF2 /* IconSwitchNode.swift */,
D00580B021E3DEF200CB7CD3 /* MergedItemListItem.swift */,
);
name = Items;
sourceTree = "<group>";
@ -5480,6 +5480,7 @@
9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */,
D0EC6D891EB9F58800EBF1C3 /* ChatSecretAutoremoveTimerActionSheet.swift in Sources */,
D05D8B782195E0050064586F /* SetupTwoStepVerificationContentNode.swift in Sources */,
D00580B121E3DEF200CB7CD3 /* MergedItemListItem.swift in Sources */,
D0EC6D8A1EB9F58800EBF1C3 /* ChatInfo.swift in Sources */,
D0EC6D8B1EB9F58800EBF1C3 /* ChatHistoryNavigationStack.swift in Sources */,
D0EC6D8C1EB9F58800EBF1C3 /* NavigateToChatController.swift in Sources */,
@ -5733,7 +5734,6 @@
D0EC6E031EB9F58900EBF1C3 /* GalleryFooterContentNode.swift in Sources */,
D0E9BA0A1F0457DD00F079A4 /* BotCheckoutWebInteractionController.swift in Sources */,
D0EC6E041EB9F58900EBF1C3 /* SecretMediaPreviewController.swift in Sources */,
099529F321D8FE8300805E13 /* RadialPlayPauseContentNode.swift in Sources */,
D0C26D571FDF2388004ABF18 /* OpenChatMessage.swift in Sources */,
D0FA08BE20481EA300DD23FC /* Locale.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.contentColor = theme.list.itemSwitchColors.contentColor
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

View File

@ -336,7 +336,7 @@ private func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: I
let id = arc4random64()
let name = "\(callId.id)_\(callId.accessHash).log"
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)
return rateSignal

View File

@ -677,38 +677,50 @@ public func channelAdminController(account: Account, peerId: PeerId, adminId: Pe
updateFlags = defaultFlags
}
if let updateFlags = updateFlags, updateFlags != defaultFlags {
let signal = convertGroupToSupergroup(account: account, peerId: peerId)
|> map(Optional.init)
|> `catch` { _ -> Signal<PeerId?, NoError> in
return .single(nil)
}
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
guard let upgradedPeerId = upgradedPeerId else {
if let updateFlags = updateFlags {
if initialParticipant?.adminInfo == nil && updateFlags == defaultFlags {
updateState { current in
return current.withUpdatedUpdating(true)
}
updateRightsDisposable.set((addGroupAdmin(account: account, peerId: peerId, adminId: adminId)
|> deliverOnMainQueue).start(completed: {
dismissImpl?()
}))
} else if updateFlags != defaultFlags {
let signal = convertGroupToSupergroup(account: account, peerId: peerId)
|> map(Optional.init)
|> `catch` { _ -> Signal<PeerId?, NoError> in
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()
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
guard let upgradedPeerId = upgradedPeerId else {
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
updateState { current in
return current.withUpdatedUpdating(true)
}
updateRightsDisposable.set(signal.start(next: { upgradedPeerId in
if let upgradedPeerId = upgradedPeerId {
upgradedToSupergroup(upgradedPeerId, {
dismissImpl?()
})
}
}, error: { _ in
|> deliverOnMainQueue
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 {
dismissImpl?()
}

View File

@ -490,6 +490,9 @@ public func channelAdminsController(account: Account, peerId: PeerId, loadComple
upgradedToSupergroupImpl?(upgradedPeerId, f)
}
let peerView = Promise<PeerView>()
peerView.set(account.viewTracker.peerView(peerId))
let arguments = ChannelAdminsControllerArguments(account: account, openRecentActions: {
let _ = (account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
@ -526,21 +529,12 @@ public func channelAdminsController(account: Account, peerId: PeerId, loadComple
}))
}
}, addAdmin: {
updateState { current in
var dismissController: (() -> Void)?
let controller = ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in
if peerId.namespace == Namespaces.Peer.CloudGroup {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let progress = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: nil))
presentControllerImpl?(progress, nil)
addAdminDisposable.set((addGroupAdmin(account: account, peerId: peerId, adminId: peer.id)
|> deliverOnMainQueue).start(completed: {
[weak progress] in
dismissController?()
progress?.dismiss()
}))
} else {
let _ = (peerView.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerView in
updateState { current in
var dismissController: (() -> Void)?
let controller = ChannelMembersSearchController(account: account, peerId: peerId, mode: .promote, filters: [], openPeer: { peer, participant in
dismissController?()
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
if peer.id == account.peerId {
@ -551,32 +545,39 @@ public func channelAdminsController(account: Account, peerId: PeerId, loadComple
case .creator:
return
case let .member(_, _, _, banInfo):
if let banInfo = banInfo, banInfo.restrictedBy != account.peerId {
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
if let banInfo = banInfo {
var canUnban = false
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
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
dismissController = { [weak controller] in
controller?.dismiss()
}
})
dismissController = { [weak controller] in
controller?.dismiss()
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return current
}
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
return current
}
})
}, openAdmin: { participant in
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in
}, upgradedToSupergroup: upgradedToSupergroup), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
let peerView = Promise<PeerView>()
peerView.set(account.viewTracker.peerView(peerId))
let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
if peerId.namespace == Namespaces.Peer.CloudChannel {
var didReportLoadCompleted = false

View File

@ -510,7 +510,7 @@ public func channelBannedMemberController(account: Account, peerId: PeerId, memb
if state.updating {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} 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()
|> take(1)
|> deliverOnMainQueue).start(next: { view in

View File

@ -301,15 +301,15 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
})
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()
})
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()
})
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()
})
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 state.editingState != nil && canEditMembers {
if canEditMembers {
if peer.adminRights != nil || peer.flags.contains(.isCreator) {
let adminCount = cachedChannelData.participantsSummary.adminCount ?? 0
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 {
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
}
} else if case .default = notificationSettings.messageSound {
notificationsText = presentationData.strings.UserInfo_NotificationsEnabled
} else {
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 rightNavigationButton: ItemListNavigationButton?
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: {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
var text = ""

View File

@ -4,7 +4,7 @@ import Postbox
import SwiftSignalKit
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 requestBatchSize: Int32 = 64
@ -83,7 +83,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
var listStateValue: ChannelMemberListState {
didSet {
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 previousIds: Set<PeerId> = Set(oldValue.list.map { $0.peer.id })
if ids != previousIds {
@ -288,8 +288,8 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
}
}
switch self.category {
case .admins:
if let updated = updated, let _ = updated.participant.adminInfo {
case let .admins(query):
if let updated = updated, let _ = updated.participant.adminInfo, (query == nil || updated.peer.indexName.matchesByTokens(query!)) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
@ -512,14 +512,16 @@ struct PeerChannelMemberCategoryControl {
private final class PeerChannelMemberContextWithSubscribers {
let context: ChannelMemberCategoryListContext
private let emptyTimeout: Double
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
private let disposable = MetaDisposable()
private let becameEmpty: () -> Void
private var emptyTimer: SwiftSignalKit.Timer?
init(context: ChannelMemberCategoryListContext, becameEmpty: @escaping () -> Void) {
init(context: ChannelMemberCategoryListContext, emptyTimeout: Double, becameEmpty: @escaping () -> Void) {
self.context = context
self.emptyTimeout = emptyTimeout
self.becameEmpty = becameEmpty
self.disposable.set((context.listState
|> deliverOnMainQueue).start(next: { [weak self] value in
@ -539,7 +541,7 @@ private final class PeerChannelMemberContextWithSubscribers {
private func resetAndBeginEmptyTimer() {
self.context.reset(false)
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 strongSelf.subscribers.isEmpty {
strongSelf.becameEmpty()
@ -605,6 +607,13 @@ final class PeerChannelMemberCategoriesContext {
return (current.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
}
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 {
case .recent, .recentSearch, .admins:
let mappedCategory: ChannelMemberListCategory
@ -626,7 +635,7 @@ final class PeerChannelMemberCategoriesContext {
case let .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())
if let strongSelf = self {
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 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
|> deliverOnMainQueue
|> mapToSignal { [weak contactsController] contacts -> Signal<Never, NoError> in
|> introduceError(AddChannelMemberError.self)
|> mapToSignal { [weak contactsController] contacts -> Signal<Never, AddChannelMemberError> in
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 {
case let .peer(contactId):
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMember(account: account, peerId: peerId, memberId: contactId)
|> ignoreValues
case .deviceContact:
return contactId
default:
return nil
}
})
}))
return combineLatest(signals)
return signal
|> ignoreValues
|> deliverOnMainQueue
|> afterCompleted {
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))
}))

View File

@ -260,6 +260,19 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
}
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))
|> afterDisposed {
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)
|> deliverOnMainQueue
|> 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
if case .ready = state.loadingState {
subscriber.putNext(state.list)
subscriber.putCompletion()
}
})
return disposable
} |> runOn(Queue.mainQueue())
} |> runOn(Queue.mainQueue())
foundMembers = .single([])
case .searchBanned:
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: .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 {

View File

@ -14,8 +14,9 @@ private final class ChannelPermissionsControllerArguments {
let openPeer: (ChannelParticipant) -> Void
let openPeerInfo: (Peer) -> 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.updatePermission = updatePermission
self.addPeer = addPeer
@ -24,6 +25,7 @@ private final class ChannelPermissionsControllerArguments {
self.openPeer = openPeer
self.openPeerInfo = openPeerInfo
self.openKicked = openKicked
self.presentRestrictedPublicGroupPermissionsAlert = presentRestrictedPublicGroupPermissionsAlert
}
}
@ -40,7 +42,7 @@ private enum ChannelPermissionsEntryStableId: Hashable {
private enum ChannelPermissionsEntry: ItemListNodeEntry {
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 exceptionsHeader(PresentationTheme, String)
case add(PresentationTheme, String)
@ -173,8 +175,12 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
case let .permissionsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
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
arguments.updatePermission(rights, value)
return ItemListSwitchItem(theme: theme, title: title, value: value, type: .icon, enableInteractiveChanges: enabled != nil, enabled: enabled ?? true, sectionId: self.section, style: .blocks, updated: { value in
if let _ = enabled {
arguments.updatePermission(rights, value)
} else {
arguments.presentRestrictedPublicGroupPermissionsAlert()
}
})
case let .kicked(theme, text, value):
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))
var rightIndex: Int = 0
for rights in allGroupPermissionList {
var enabled = true
if channel.addressName != nil {
enabled = !publicGroupRestrictedPermissions.contains(rights)
var enabled: Bool? = true
if channel.addressName != nil && publicGroupRestrictedPermissions.contains(rights) {
enabled = nil
}
entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights), !effectiveRightsFlags.contains(rights), rights, enabled))
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))
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
if let modifiedRightsFlags = state.modifiedRightsFlags {
effectiveRightsFlags = modifiedRightsFlags
@ -436,7 +442,7 @@ public func channelPermissionsController(account: Account, peerId: PeerId, loadC
let _ = (peerView.get()
|> take(1)
|> 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
var state = state
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))
|> 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
var state = state
var effectiveRightsFlags: TelegramChatBannedRightsFlags
@ -566,6 +572,9 @@ public func channelPermissionsController(account: Account, peerId: PeerId, loadC
}
}, openKicked: {
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)

View File

@ -158,7 +158,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
imageDimensions = content?.dimensions
if let content = content, type == "gif", let thumbnailResource = imageResource
, 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
}
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 mediaRecordingModeTooltipController: TooltipController?
private weak var mediaRestrictedTooltipController: TooltipController?
private var mediaRestrictedTooltipControllerMode = true
private var screenCaptureEventsDisposable: Disposable?
private let chatAdditionalDataDisposable = MetaDisposable()
@ -2578,8 +2580,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
let banDescription: String
switch subject {
case .stickers:
if personal {
banDescription = strongSelf.presentationInterfaceState.strings.Group_ErrorSendRestrictedStickers
if untilDate != 0 && untilDate != Int32.max {
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 {
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedStickers
}
@ -2600,6 +2604,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
strongSelf.recordingModeFeedback?.error()
let rect: CGRect?
let isStickers: Bool = subject == .stickers
switch subject {
case .stickers:
rect = strongSelf.chatDisplayNode.frameForStickersButton()
@ -2607,14 +2612,16 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
}
if let tooltipController = strongSelf.mediaRecordingModeTooltipController {
if let tooltipController = strongSelf.mediaRestrictedTooltipController, strongSelf.mediaRestrictedTooltipControllerMode == isStickers {
tooltipController.text = banDescription
} else if let rect = rect {
strongSelf.mediaRestrictedTooltipController?.dismiss()
let tooltipController = TooltipController(text: banDescription)
strongSelf.mediaRecordingModeTooltipController = tooltipController
strongSelf.mediaRestrictedTooltipController = tooltipController
strongSelf.mediaRestrictedTooltipControllerMode = isStickers
tooltipController.dismissed = { [weak tooltipController] in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController {
strongSelf.mediaRecordingModeTooltipController = nil
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
strongSelf.mediaRestrictedTooltipController = nil
}
}
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
@ -3739,10 +3746,21 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
strongSelf.chatDisplayNode.dismissInput()
var bannedSendMedia: (Int32, Bool)?
if let channel = peer as? TelegramChannel, let value = channel.hasBannedPermission(.banSendMedia) {
bannedSendMedia = value
} else if let group = peer as? TelegramGroup, group.hasBannedPermission(.banSendMedia) {
bannedSendMedia = (Int32.max, false)
var canSendPolls = true
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendMedia) {
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 {
@ -3756,17 +3774,23 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: banDescription),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Location, color: .accent, action: { [weak actionSheet] in
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: banDescription))
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()
self?.presentMapPicker(editingMessage: false)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
self?.presentContactPicker()
})
]), ActionSheetItemGroup(items: [
self?.presentPollCreation()
}))
}
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_Contact, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
self?.presentContactPicker()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
@ -3885,7 +3909,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
for item in results {
if let item = item {
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)
messages.append(message)
}
@ -4275,7 +4299,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
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 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 = 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
}
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> {
if wallpaper == serviceBackgroundColorForWallpaper?.0, let color = serviceBackgroundColorForWallpaper?.1 {
return .single(color)
@ -51,32 +80,7 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox)
if let largest = largestImageRepresentation(representations) {
return Signal<UIColor, NoError> { subscriber in
let fetch = postbox.mediaBox.fetchedResource(largest.resource, parameters: nil).start()
let data = (postbox.mediaBox.resourceData(largest.resource)
|> 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
let data = serviceColor(for: postbox.mediaBox.resourceData(largest.resource)).start(next: { next in
subscriber.putNext(next)
}, completed: {
subscriber.putCompletion()
@ -95,32 +99,7 @@ func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, postbox: Postbox)
case let .file(file):
return Signal<UIColor, NoError> { subscriber in
let fetch = postbox.mediaBox.fetchedResource(file.file.resource, parameters: nil).start()
let data = (postbox.mediaBox.resourceData(file.file.resource)
|> 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
let data = serviceColor(for: postbox.mediaBox.resourceData(file.file.resource)).start(next: { next in
subscriber.putNext(next)
}, completed: {
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)] = []
for entry in view.entries {
loop: for entry in view.entries {
switch entry {
case let .HoleEntry(hole, _):
if !groupBucket.isEmpty {
@ -28,6 +28,19 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
entries.append(.HoleEntry(hole, presentationData))
}
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
if let author = message.author {
isAdmin = adminIds.contains(author.id)
@ -100,7 +113,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
entries.insert(.ChatInfoEntry(botInfo.description, presentationData), at: 0)
}
var isEmpty = true
if entries.count <= 2 {
if entries.count <= 3 {
loop: for entry in view.entries {
switch entry {
case let .MessageEntry(entry):
@ -108,7 +121,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
for media in entry.0.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case .groupCreated, .photoUpdated:
case .groupCreated, .photoUpdated, .channelMigratedFromGroup, .groupMigratedToChannel:
isEmptyMedia = true
default:
break

View File

@ -121,27 +121,48 @@ struct ChatHistoryListViewTransition {
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?
for i in (indexRange.0 ... indexRange.1).reversed() {
if case let .MessageEntry(message, _, _, _, _, _) = entries[i] {
if overall == nil {
overall = MessageIndex(message)
var nextLowestIndex: MessageIndex?
if indexRange.0 >= 0 && indexRange.0 < view.filteredEntries.count {
if indexRange.0 > 0 {
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 overall == nil {
}
if case let .MessageEntry(messageEntry) = view.originalView.entries[i] {
if overall == nil || overall! < index {
overall = index
}
if messages[messages.count - 1].0.flags.contains(.Incoming) {
return (index, overall)
if messageEntry.0.flags.contains(.Incoming) {
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] {
@ -654,7 +675,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
if readIndexRange.0 <= readIndexRange.1 {
let (maxIncomingIndex, maxOverallIndex) = maxMessageIndexForEntries(historyView.filteredEntries, indexRange: readIndexRange)
let (maxIncomingIndex, maxOverallIndex) = maxMessageIndexForEntries(historyView, indexRange: readIndexRange)
if let maxIncomingIndex = maxIncomingIndex {
strongSelf.updateMaxVisibleReadIncomingMessageIndex(maxIncomingIndex)
@ -980,7 +1001,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
visibleFirstIndex += 1
}*/
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 {
strongSelf.updateMaxVisibleReadIncomingMessageIndex(messageIndex)
}

View File

@ -585,12 +585,6 @@ private func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId
return true
}
if let peer = message.peers[message.id.peerId] as? TelegramChannel {
if peer.hasPermission(.pinMessages) {
return true
}
}
return false
}

View File

@ -442,10 +442,11 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
self.chatListDisplayNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
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 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)
})
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
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 {

View File

@ -100,8 +100,10 @@ class ChatListItem: ListViewItem {
func selected(listView: ListView) {
switch self.content {
case let .peer(message, peer, _, _, _, _, _, isAd, _):
if let message = message {
self.interaction.messageSelected(message, isAd)
if let message = message, let peer = peer.peer {
self.interaction.messageSelected(peer, message, isAd)
} else if let peer = peer.peer {
self.interaction.peerSelected(peer)
} else if let peer = peer.peers[peer.peerId] {
self.interaction.peerSelected(peer)
}

View File

@ -60,7 +60,7 @@ final class ChatListNodeInteraction {
let activateSearch: () -> Void
let peerSelected: (Peer) -> Void
let togglePeerSelected: (PeerId) -> Void
let messageSelected: (Message, Bool) -> Void
let messageSelected: (Peer, Message, Bool) -> Void
let groupSelected: (PeerGroupId) -> Void
let addContact: (String) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
@ -72,7 +72,7 @@ final class ChatListNodeInteraction {
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.peerSelected = peerSelected
self.togglePeerSelected = togglePeerSelected
@ -386,9 +386,9 @@ final class ChatListNode: ListView {
}
return state
}
}, messageSelected: { [weak self] message, isAd in
}, messageSelected: { [weak self] peer, message, isAd in
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(message.id.peerId, true, isAd)
peerSelected(peer.id, true, isAd)
}
}, groupSelected: { [weak self] groupId in
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()
self?.listNode.clearHighlightAnimated(true)
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] message, _ in
}, messageSelected: { [weak self] peer, message, _ in
if let peer = message.peers[message.id.peerId] {
openMessage(peer, message.id)
}

View File

@ -232,7 +232,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st
case .joinedByLink:
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .channelMigratedFromGroup, .groupMigratedToChannel:
attributedString = NSAttributedString(string: strings.Notification_ChannelMigratedFrom, font: titleFont, textColor: primaryTextColor)
attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor)
case let .messageAutoremoveTimeoutUpdated(timeout):
if timeout > 0 {
let timeValue = timeIntervalString(strings: strings, value: timeout)

View File

@ -195,10 +195,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
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 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)
}

View File

@ -354,7 +354,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
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)
@ -419,7 +419,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
let actionButtonsNode = actionButtonsSizeAndApply.1(animated)
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
if actionButtonsNode !== strongSelf.actionButtonsNode {
strongSelf.actionButtonsNode = actionButtonsNode

View File

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

View File

@ -767,8 +767,6 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
var text: String = ""
var entities: [MessageTextEntity] = []
let newFlags = new.flags
var addedRights = new.flags
var removedRights: TelegramChatBannedRightsFlags = []
addedRights = addedRights.subtracting(prev.flags)
@ -777,8 +775,6 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
text += self.presentationData.strings.Channel_AdminLog_DefaultRestrictionsUpdated
text += "\n"
let prevFlags = prev.flags
let order: [(TelegramChatBannedRightsFlags, String)] = [
(.banReadMessages, self.presentationData.strings.Channel_AdminLog_BanReadMessages),
(.banSendMessages, self.presentationData.strings.Channel_AdminLog_BanSendMessages),
@ -786,6 +782,10 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
(.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickers),
(.banSendGifs, self.presentationData.strings.Channel_AdminLog_BanSendGifs),
(.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 {

View File

@ -20,11 +20,17 @@ private let searchLayoutProgressImage = generateImage(CGSize(width: 22.0, height
private let accessoryButtonFont = Font.medium(14.0)
private final class AccessoryItemIconButton: HighlightableButton {
private final class AccessoryItemIconButton: HighlightTrackingButton {
private let item: ChatTextInputAccessoryItem
private var width: CGFloat
private let imageNode: ASImageNode
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
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())
self.addSubnode(self.imageNode)
if let text = text {
self.titleLabel?.font = accessoryButtonFont
self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: [])
self.setTitle(text, for: [])
}
self.setImage(image, for: [])
self.imageNode.image = image
self.imageNode.alpha = alpha
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) {
@ -57,9 +77,9 @@ private final class AccessoryItemIconButton: HighlightableButton {
self.setTitle("", for: [])
}
self.setImage(image, for: [])
self.imageNode.image = image
self.imageEdgeInsets = insets
self.imageView?.alpha = alpha
self.imageNode.alpha = alpha
}
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 {
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)
for (_, button) in self.accessoryItemButtons.reversed() {
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)
if button.superview == nil {
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 {
@ -639,6 +644,7 @@ private struct ContactsListNodeTransition {
let indexSections: [String]
let firstTime: Bool
let isEmpty: Bool
let scrollToItem: ListViewScrollToItem?
let animation: ContactListAnimation
}
@ -727,6 +733,9 @@ final class ContactListNode: ASDisplayNode {
self.selectionStatePromise.set(.single(self.selectionStateValue))
}
}
var selectionState: ContactListNodeGroupSelectionState? {
return self.selectionStateValue
}
private var enableUpdatesValue = false
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.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 !strongSelf.didSetReady {
strongSelf.didSetReady = true

View File

@ -131,11 +131,12 @@ class ContactMultiselectionController: ViewController {
switch self.mode {
case .groupCreation:
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))
self.rightNavigationButton = rightNavigationButton
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
rightNavigationButton.isEnabled = false
rightNavigationButton.isEnabled = count != 0
case .channelCreation:
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))

View File

@ -5,6 +5,25 @@ import Postbox
import SwiftSignalKit
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 {
private let account: Account
@ -229,7 +248,7 @@ public class ContactsController: ViewController {
self.contactsNode.contactListNode.contentScrollingEnded = { [weak self] listView in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
return fixNavigationSearchableListNodeScrolling(listView, searchNode: searchContentNode)
return fixListNodeScrolling(listView, searchNode: searchContentNode)
} else {
return false
}

View File

@ -105,10 +105,31 @@ public func debugAccountsController(account: Account, accountManager: AccountMan
transaction.setCurrentId(id)
}).start()
}, loginNewAccount: {
let _ = accountManager.transaction({ transaction -> Void in
let id = transaction.createRecord([])
transaction.setCurrentId(id)
}).start()
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationTheme: presentationData.theme)
let dismissAction: () -> Void = { [weak controller] in
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())

View File

@ -118,7 +118,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
let messages = logs.map { (name, path) -> EnqueueMessage in
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)
}
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 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)
}
let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start()

View File

@ -57,7 +57,9 @@ private let rootController = PresentationThemeRootController(
private let switchColors = PresentationThemeSwitch(
frameColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
handleColor: UIColor(rgb: 0x121212),
contentColor: accentColor
contentColor: accentColor,
positiveColor: accentColor,
negativeColor: destructiveColor
)
private let list = PresentationThemeList(

View File

@ -57,7 +57,9 @@ private let rootController = PresentationThemeRootController(
private let switchColors = PresentationThemeSwitch(
frameColor: UIColor(rgb: 0x545454),
handleColor: UIColor(rgb: 0x121212),
contentColor: UIColor(rgb: 0xb2b2b2)
contentColor: UIColor(rgb: 0xb2b2b2),
positiveColor: UIColor(rgb: 0x000000),
negativeColor: destructiveColor
)
private let list = PresentationThemeList(

View File

@ -57,7 +57,9 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
let switchColors = PresentationThemeSwitch(
frameColor: UIColor(rgb: 0xe0e0e0),
handleColor: UIColor(rgb: 0xffffff),
contentColor: UIColor(rgb: 0x42d451)
contentColor: UIColor(rgb: 0x42d451),
positiveColor: UIColor(rgb: 0x00B12C),
negativeColor: destructiveColor
)
let list = PresentationThemeList(

View File

@ -28,7 +28,6 @@ private final class GroupInfoArguments {
let promotePeer: (RenderedChannelParticipant) -> Void
let restrictPeer: (RenderedChannelParticipant) -> Void
let removePeer: (PeerId) -> Void
let convertToSupergroup: () -> Void
let leave: () -> Void
let displayUsernameShareMenu: (String) -> Void
let displayUsernameContextMenu: (String) -> Void
@ -37,7 +36,7 @@ private final class GroupInfoArguments {
let openStickerPackSetup: () -> 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.peerId = peerId
self.avatarAndNameInfoContext = avatarAndNameInfoContext
@ -57,7 +56,6 @@ private final class GroupInfoArguments {
self.promotePeer = promotePeer
self.restrictPeer = restrictPeer
self.removePeer = removePeer
self.convertToSupergroup = convertToSupergroup
self.leave = leave
self.displayUsernameShareMenu = displayUsernameShareMenu
self.displayUsernameContextMenu = displayUsernameContextMenu
@ -147,7 +145,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
case permissions(PresentationTheme, String, String)
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 convertToSupergroup(PresentationTheme, String)
case leave(PresentationTheme, String)
var section: ItemListSectionId {
@ -164,7 +161,7 @@ private enum GroupInfoEntry: ItemListNodeEntry {
return GroupInfoSection.memberManagement.rawValue
case .addMember, .member:
return GroupInfoSection.members.rawValue
case .convertToSupergroup, .leave:
case .leave:
return GroupInfoSection.leave.rawValue
}
}
@ -230,12 +227,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
} else {
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):
if case let .aboutHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -407,8 +398,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
return 17
case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _):
return 20 + index
case .convertToSupergroup:
return 100000
case .leave:
return 100000 + 1
}
@ -516,10 +505,6 @@ private enum GroupInfoEntry: ItemListNodeEntry {
}, removePeer: { peerId in
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):
return ItemListActionItem(theme: theme, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.leave()
@ -698,9 +683,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
var canAddMembers = false
var isPublic = false
var isCreator = false
var isGroup = false
if let group = view.peers[view.peerId] as? TelegramGroup {
isGroup = true
if case .creator = group.role {
isCreator = true
}
@ -713,10 +696,13 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
case .member:
break
}
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
if case .group = channel.info {
isGroup = true
if !group.hasBannedPermission(.banChangeInfo) {
canEditGroupInfo = true
}
if !group.hasBannedPermission(.banAddMembers) {
canAddMembers = true
}
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
highlightAdmins = true
isPublic = channel.username != nil
isCreator = channel.flags.contains(.isCreator)
@ -745,6 +731,8 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
} else {
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
}
} else if case .default = peerNotificationSettings.messageSound {
notificationsText = presentationData.strings.UserInfo_NotificationsEnabled
} else {
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 canRestrict: Bool
switch participant.participant {
case .creator:
canPromote = false
canRestrict = false
case let .member(_, _, adminRights, bannedRights):
if channel.hasPermission(.addAdmins) {
canPromote = true
} else {
if participant.peer.id == account.peerId {
canPromote = false
canRestrict = false
} else {
switch participant.participant {
case .creator:
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
case let .member(_, _, adminRights, bannedRights):
if channel.hasPermission(.addAdmins) {
canPromote = true
} else {
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 let adminRights = adminRights {
if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) {
canRestrict = false
if canRestrict {
if let adminRights = adminRights {
if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) {
canRestrict = false
}
}
}
}
}
}
var peerActions: [ParticipantRevealAction] = []
@ -1068,14 +1061,21 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
if let group = view.peers[view.peerId] as? TelegramGroup {
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))
}
} 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 {
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
if case .member = channel.participationStatus {
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)?
let peerView = Promise<PeerView>()
peerView.set(account.viewTracker.peerView(peerId))
let arguments = GroupInfoArguments(account: account, peerId: peerId, avatarAndNameInfoContext: avatarAndNameInfoContext, tapAvatarAction: {
let _ = (account.postbox.loadedPeerWithId(peerId)
|> 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.postbox.peerView(id: memberId)
return peerView.get()
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { view -> Signal<Void, NoError> in
@ -1645,31 +1648,56 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
}
}
removeMemberDisposable.set(signal.start())
}, convertToSupergroup: {
pushControllerImpl?(convertToSupergroupController(account: account, peerId: peerId))
}, leave: {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
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))
let _ = (peerView.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerView in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
if let channel = peerView.peers[peerId] as? TelegramChannel, channel.flags.contains(.isCreator), stateValue.with({ $0 }).editingState != nil {
let controller = ActionSheetController(presentationTheme: presentationData.theme)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: presentationData.strings.ChannelInfo_DeleteGroupConfirmation))
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteGroup, color: .destructive, action: {
dismissAction()
let _ = (removePeerChat(postbox: account.postbox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: true)
|> deliverOnMainQueue).start(completed: {
popToRootImpl?()
})
}))
controller.setItemGroups([
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
let shareController = ShareController(account: account, subject: .url(text))
presentControllerImpl?(shareController, nil)
@ -1709,7 +1737,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
let previousChannelMembers = Atomic<[PeerId]?>(value: nil)
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
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?
if let editingState = state.editingState {
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: {
if let peer = peer as? TelegramGroup {
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?

View File

@ -96,7 +96,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
entries.append(entry)
index += 1
}
prepareTransition(from: self.currentEntries ?? [], to: entries)
self.prepareTransition(from: self.currentEntries ?? [], to: entries)
}
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
@ -132,7 +132,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
}
private func enqueueTransition(_ transition: HashtagChatInputContextPanelTransition, firstTime: Bool) {
enqueuedTransitions.append((transition, firstTime))
self.enqueuedTransitions.append((transition, firstTime))
if self.validLayout != nil {
while !self.enqueuedTransitions.isEmpty {
@ -233,7 +233,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
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: {
}, peerSelected: { peer in
}, togglePeerSelected: { _ in
}, messageSelected: { [weak self] message, _ in
}, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self {
if let peer = message.peers[message.id.peerId] {
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer) |> deliverOnMainQueue).start(completed: {
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(message.id.peerId), messageId: message.id))
}
}))
}
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in
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.controllerNode.listNode.clearHighlightAnimated(true)
}
}, groupSelected: { _ in

View File

@ -209,7 +209,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}
imageDimensions = 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
}

View File

@ -56,6 +56,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
private var strings: PresentationStrings
private let gridNode: GridNode
private let backgroundNode: ASDisplayNode
private var validLayout: (CGSize, CGFloat, CGFloat, ChatPresentationInterfaceState)?
private var currentEntries: [StickerEntry] = []
@ -72,6 +73,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
self.gridNode = GridNode()
self.gridNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.list.plainBackgroundColor
self.stickersInteraction = HorizontalStickersChatContextPanelInteraction()
super.init(account: account, theme: theme, strings: strings)
@ -80,6 +84,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
self.clipsToBounds = true
self.addSubnode(self.gridNode)
self.gridNode.addSubnode(self.backgroundNode)
}
override func didLoad() {
@ -192,7 +197,13 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
private func dequeueTransitions() {
while !self.queuedTransitions.isEmpty {
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))
var duration: Double = 0.0
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)
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)
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
self.validLayout = (size, leftInset, rightInset, interfaceState)
@ -260,7 +251,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
let listViewFrame = self.gridNode.frame
return self.gridNode.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event)
}
private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) {
if 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
public var isOn: Bool {
@ -68,6 +82,8 @@ class IconSwitchNode: ASDisplayNode {
(self.view as! UISwitch).tintColor = self.frameColor
//(self.view as! UISwitch).thumbTintColor = self.handleColor
(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)

View File

@ -75,10 +75,25 @@ private protocol ItemListSwitchNodeImpl {
var frameColor: UIColor { get set }
var contentColor: UIColor { get set }
var handleColor: UIColor { get set }
var positiveContentColor: UIColor { get set }
var negativeContentColor: UIColor { get set }
}
extension SwitchNode: ItemListSwitchNodeImpl {
var positiveContentColor: UIColor {
get {
return .white
} set(value) {
}
}
var negativeContentColor: UIColor {
get {
return .white
} set(value) {
}
}
}
extension IconSwitchNode: ItemListSwitchNodeImpl {
@ -214,6 +229,8 @@ class ItemListSwitchItemNode: ListViewItemNode {
strongSelf.switchNode.frameColor = item.theme.list.itemSwitchColors.frameColor
strongSelf.switchNode.contentColor = item.theme.list.itemSwitchColors.contentColor
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()

View File

@ -21,7 +21,7 @@ func stickerFromLegacyDocument(_ documentAttachment: TGDocumentMediaAttachment)
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
@ -184,7 +184,7 @@ final class LegacyStickerImageDataSource: TGImageDataSource {
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 {
sharedImageCache.setImage(image, forKey: uri, attributes: nil)
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] = []
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(.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.putCompletion()
} else {
@ -284,7 +284,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
arc4random_buf(&randomId, 8)
let _ = try? heicData.write(to: URL(fileURLWithPath: tempFilePath + ".heic"))
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] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
@ -336,13 +336,13 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
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))
case let .asset(asset):
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
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))
default:
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] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
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))
}
}
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)"
}
}
} 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" {

View File

@ -130,6 +130,7 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe
let fullSizeResource = fileReference.media.resource
let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource, pathExtension: pathExtension)
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
let signal = maybeFullSize
|> take(1)
@ -138,18 +139,26 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe
return .single((nil, maybeData.path, true))
} else {
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))
} else {
fetchedThumbnail = .complete()
}
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
let fetchedDisposable = fetchedThumbnail.start()
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)
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> {
let thumbnailResource = smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource
let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
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))
return Signal { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
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)
return ActionDisposable {
@ -218,18 +234,26 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference:
return .single((nil, maybeData.path, true))
} else {
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))
} else {
fetchedThumbnail = .complete()
}
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
let fetchedDisposable = fetchedThumbnail.start()
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)
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 }) {
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
let decodedThumbnailData = fileReference?.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
let signal = maybeFullSize
|> take(1)
@ -2183,18 +2208,29 @@ private func avatarGalleryPhotoDatas(account: Account, representations: [ImageRe
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((nil, loadedData, true))
} 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 thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, error: subscriber.putError, completed: subscriber.putCompletion)
return ActionDisposable {
fetchedDisposable.dispose()
thumbnailDisposable.dispose()
let thumbnail: Signal<Data?, NoError>
if let decodedThumbnailData = decodedThumbnailData {
thumbnail = .single(decodedThumbnailData)
} else {
thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, 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 fullSizeData |> map { (fullSizeData, complete) in
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> {
let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize)
func chatAvatarGalleryPhoto(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryPhotoDatas(account: account, fileReference: fileReference, representations: representations, autoFetchFullSize: autoFetchFullSize)
return signal
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in

View File

@ -46,6 +46,7 @@ public final class PresentationData: Equatable {
public let strings: PresentationStrings
public let theme: PresentationTheme
public let chatWallpaper: TelegramWallpaper
public let chatWallpaperMode: PresentationWallpaperMode
public let volumeControlStatusBarIcons: PresentationVolumeControlStatusBarIcons
public let fontSize: PresentationFontSize
public let dateTimeFormat: PresentationDateTimeFormat
@ -53,10 +54,11 @@ public final class PresentationData: Equatable {
public let nameSortOrder: PresentationPersonNameOrder
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.theme = theme
self.chatWallpaper = chatWallpaper
self.chatWallpaperMode = chatWallpaperMode
self.volumeControlStatusBarIcons = volumeControlStatusBarIcons
self.fontSize = fontSize
self.dateTimeFormat = dateTimeFormat
@ -66,7 +68,7 @@ public final class PresentationData: Equatable {
}
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
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
var effectiveChatWallpaperMode: PresentationWallpaperMode = themeSettings.chatWallpaperMode
if automaticThemeShouldSwitchNow(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) {
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
@ -242,8 +245,10 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
switch themeSettings.automaticThemeSwitchSetting.theme {
case .nightAccent:
effectiveChatWallpaper = .color(0x18222d)
effectiveChatWallpaperMode = .still
case .nightGrayscale:
effectiveChatWallpaper = .color(0x000000)
effectiveChatWallpaperMode = .still
default:
break
}
@ -276,7 +281,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal<Initi
let dateTimeFormat = currentDateTimeFormat()
let nameDisplayOrder = contactSettings.nameDisplayOrder
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 effectiveTheme: PresentationThemeReference
var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper
var effectiveChatWallpaperMode: PresentationWallpaperMode = themeSettings.chatWallpaperMode
if shouldSwitch {
effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme)
switch effectiveChatWallpaper {
@ -377,8 +384,10 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
switch themeSettings.automaticThemeSwitchSetting.theme {
case .nightAccent:
effectiveChatWallpaper = .color(0x18222d)
effectiveChatWallpaperMode = .still
case .nightGrayscale:
effectiveChatWallpaper = .color(0x000000)
effectiveChatWallpaperMode = .still
default:
break
}
@ -420,7 +429,7 @@ public func updatedPresentationData(postbox: Postbox, applicationBindings: Teleg
let nameDisplayOrder = contactSettings.nameDisplayOrder
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 {
return .complete()
@ -436,5 +445,5 @@ public func defaultPresentationData() -> PresentationData {
let nameSortOrder = currentPersonNameSortOrder()
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 groupInfoPermissionsIcon
case groupInfoMembersIcon
case groupInfoBannedIcon
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? {
return theme.image(PresentationResourceKey.emptyChatListCheckIcon.rawValue, { _ in
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 handleColor: 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.handleColor = handleColor
self.contentColor = contentColor
self.positiveColor = positiveColor
self.negativeColor = negativeColor
}
}

View File

@ -10,6 +10,12 @@ public enum PresentationBuiltinThemeReference: Int32 {
case nightAccent = 3
}
public enum PresentationWallpaperMode: Int32 {
case still
case perspective
case blurred
}
public enum PresentationThemeReference: PostboxCoding, Equatable {
case builtin(PresentationBuiltinThemeReference)
@ -141,6 +147,7 @@ public struct AutomaticThemeSwitchSetting: PostboxCoding, Equatable {
public struct PresentationThemeSettings: PreferencesEntry {
public var chatWallpaper: TelegramWallpaper
public var chatWallpaperMode: PresentationWallpaperMode
public var theme: PresentationThemeReference
public var themeAccentColor: Int32?
public var fontSize: PresentationFontSize
@ -157,11 +164,12 @@ public struct PresentationThemeSettings: PreferencesEntry {
}
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.chatWallpaperMode = chatWallpaperMode
self.theme = theme
self.themeAccentColor = themeAccentColor
self.fontSize = fontSize
@ -171,6 +179,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
public init(decoder: PostboxDecoder) {
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.themeAccentColor = decoder.decodeOptionalInt32ForKey("themeAccentColor")
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) {
encoder.encodeObject(self.chatWallpaper, forKey: "w")
encoder.encodeInt32(self.chatWallpaperMode.rawValue, forKey: "m")
encoder.encodeObject(self.theme, forKey: "t")
if let themeAccentColor = self.themeAccentColor {
encoder.encodeInt32(themeAccentColor, forKey: "themeAccentColor")
@ -200,7 +210,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
}
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)
}
}
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 {
previousContentNode.removeFromSupernode()
strongSelf.contentNode = strongSelf.nextContentNode

View File

@ -65,9 +65,9 @@ class SearchBarPlaceholderNode: ASDisplayNode {
self.iconNode.displayWithoutProcessing = true
self.labelNode = TextNode()
self.labelNode.isOpaque = true
self.labelNode.isOpaque = false
self.labelNode.isLayerBacked = true
self.labelNode.backgroundColor = self.foregroundColor
//self.labelNode.backgroundColor = self.foregroundColor
super.init()
@ -90,7 +90,7 @@ class SearchBarPlaceholderNode: ASDisplayNode {
let currentIconColor = self.iconColor
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 updatedIconImage: UIImage?
@ -111,7 +111,7 @@ class SearchBarPlaceholderNode: ASDisplayNode {
strongSelf.backgroundNode.isUserInteractionEnabled = expansionProgress > 1.0 - CGFloat.ulpOfOne
if let updatedColor = updatedColor {
strongSelf.labelNode.backgroundColor = updatedColor
//strongSelf.labelNode.backgroundColor = updatedColor
strongSelf.backgroundNode.backgroundColor = updatedColor
}
if let updatedIconImage = updatedIconImage {

View File

@ -3,13 +3,17 @@ import Postbox
import TelegramCore
import SwiftSignalKit
func storedMessageFromSearchPeer(account: Account, peer: Peer) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
func storedMessageFromSearchPeer(account: Account, peer: Peer) -> Signal<PeerId, NoError> {
return account.postbox.transaction { transaction -> PeerId in
if transaction.getPeer(peer.id) == nil {
updatePeers(transaction: transaction, peers: [peer], update: { previousPeer, updatedPeer in
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) {
(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 bounds = controller.displayNode.bounds

View File

@ -220,7 +220,7 @@ class ThemeGalleryController: ViewController {
wallpaper = value
}
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: {
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?.activateSearch()
//self?.activateSearch()
})
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 _ = (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)
}) |> deliverOnMainQueue).start(completed: { [weak self] in
let _ = (self?.navigationController as? NavigationController)?.popViewController(animated: true)
})
return PresentationThemeSettings(chatWallpaper: wallpaper, chatWallpaperMode: .still, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
}) |> deliverOnMainQueue).start()
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) {

View File

@ -168,8 +168,9 @@ final class ThemeGridControllerNode: ASDisplayNode {
let transition = combineLatest(telegramWallpapers(postbox: account.postbox, network: account.network), account.telegramApplicationContext.presentationData)
|> map { wallpapers, presentationData -> (ThemeGridEntryTransition, Bool) in
var entries: [ThemeGridControllerEntry] = []
var index = 0
var index = 1
var hasCurrent = false
switch presentationData.theme.name {
case let .builtin(name):
switch name {
@ -177,15 +178,21 @@ final class ThemeGridControllerNode: ASDisplayNode {
break
case .day:
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
case .nightGrayscale:
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
case .nightAccent:
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
}
default:
@ -193,9 +200,16 @@ final class ThemeGridControllerNode: ASDisplayNode {
}
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
}
if !hasCurrent {
entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, selected: true), at: 0)
}
let previous = previousEntries.swap(entries)
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)
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()
}, selectFontSize: { size 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()
}, openWallpaperSettings: {
pushControllerImpl?(ThemeGridController(account: account, mode: .wallpapers))
}, openAccentColor: { color in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
presentControllerImpl?(ThemeAccentColorActionSheet(account: account, currentValue: color, applyValue: { color 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()
}))
}, openAutoNightTheme: {
pushControllerImpl?(themeAutoNightSettingsController(account: account))
}, disableAnimations: { disabled 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()
})

View File

@ -20,6 +20,7 @@ enum ParsedInternalUrl {
case confirmationCode(Int)
case cancelAccountReset(phone: String, hash: String)
case share(url: String?, text: String?, to: String?)
case wallpaper(String)
}
private enum ParsedUrl {
@ -41,6 +42,7 @@ enum ResolvedUrl {
case confirmationCode(Int)
case cancelAccountReset(phone: String, hash: String)
case share(url: String?, text: String?, to: String?)
case wallpaper(String)
}
func parseInternalUrl(query: String) -> ParsedInternalUrl? {
@ -167,6 +169,8 @@ func parseInternalUrl(query: String) -> ParsedInternalUrl? {
}
}
return nil
} else if pathComponents[0] == "bg" {
return .wallpaper(pathComponents[1])
} else if let value = Int(pathComponents[1]) {
return .peerName(peerName, .channelMessage(Int32(value)))
} else {
@ -233,6 +237,8 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
return .single(.cancelAccountReset(phone: phone, hash: hash))
case let .share(url, text, 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 {
notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
}
} else if case .default = notificationSettings.messageSound {
notificationsLabel = presentationData.strings.UserInfo_NotificationsEnabled
} else {
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 presentationDataDisposable: Disposable?
private var wallpaper: TelegramWallpaper?
private var wallpaperDisposable: Disposable?
private var didPlayPresentationAnimation = false
init(account: Account, source: WallpaperListPreviewSource) {
@ -62,12 +65,12 @@ final class WallpaperListPreviewController: ViewController {
deinit {
self.presentationDataDisposable?.dispose()
self.wallpaperDisposable?.dispose()
}
private func updateThemeAndStrings() {
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.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.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
@ -96,13 +99,13 @@ final class WallpaperListPreviewController: ViewController {
override public func loadDisplayNode() {
self.displayNode = WallpaperListPreviewControllerNode(account: self.account, presentationData: self.presentationData, source: self.source, dismiss: { [weak self] in
self?.dismiss()
}, apply: { [weak self] wallpaper in
}, apply: { [weak self] wallpaper, mode in
guard let strongSelf = self else {
return
}
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: {
self?.dismiss()
@ -110,6 +113,20 @@ final class WallpaperListPreviewController: ViewController {
})
self._ready.set(self.controllerNode.ready.get())
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) {
@ -119,7 +136,9 @@ final class WallpaperListPreviewController: ViewController {
}
@objc func sharePressed() {
let shareController = ShareController(account: account, subject: .url("link"))
self.present(shareController, in: .window(.root), blockInteraction: true)
if let wallpaper = self.wallpaper, case let .file(_, _, _, _, slug, _, _) = wallpaper, let wallpaperSlug = slug, !wallpaperSlug.isEmpty {
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 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 {
let wallpaper: TelegramWallpaper
private var fetchDisposable: Disposable?
private var statusDisposable: Disposable?
private let imageNode: TransformImageNode
let imageNode: TransformImageNode
private let statusNode: RadialStatusNode
let segmentedControlColor = Promise<UIColor>(.white)
init(account: Account, wallpaper: TelegramWallpaper) {
self.wallpaper = wallpaper
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: .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)
statusSignal = account.postbox.mediaBox.resourceStatus(file.file.resource)
case let .image(representations):
@ -101,6 +117,8 @@ private final class WallpaperBackgroundNode: ASDisplayNode {
}
})
self.imageNode.contentMode = .scaleAspectFill
self.segmentedControlColor.set(.single(.white) |> then(chatBackgroundContrastColor(wallpaper: wallpaper, postbox: account.postbox)))
}
deinit {
@ -120,7 +138,7 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
private let account: Account
private var presentationData: PresentationData
private let dismiss: () -> Void
private let apply: (TelegramWallpaper) -> Void
private let apply: (TelegramWallpaper, PresentationWallpaperMode) -> Void
private var validLayout: (ContainerViewLayout, CGFloat)?
@ -133,6 +151,8 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
private let toolbarButtonApplyBackground: ASDisplayNode
private let segmentedControl: UISegmentedControl
private var segmentedControlColor = Promise<UIColor>(.white)
private var segmentedControlColorDisposable: Disposable?
private var wallpapersDisposable: Disposable?
private var wallpapers: [TelegramWallpaper]?
@ -142,9 +162,14 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
private var visibleBackgroundNodes: [WallpaperBackgroundNode] = []
private var centralWallpaper: TelegramWallpaper?
private let currentWallpaperPromise = Promise<TelegramWallpaper>()
var currentWallpaper: Signal<TelegramWallpaper, NoError> {
return self.currentWallpaperPromise.get()
}
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.presentationData = presentationData
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 {
case let .list(wallpapers, central):
self.wallpapers = wallpapers
@ -239,6 +272,9 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
}
self.ready.set(true)
}
if let wallpaper = self.centralWallpaper {
self.currentWallpaperPromise.set(.single(wallpaper))
}
}
deinit {
@ -405,6 +441,11 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
} else {
itemNodeTransition = transition
}
if j == i {
self.segmentedControlColor.set(itemNode.segmentedControlColor.get())
}
itemNodeTransition.updateFrame(node: itemNode, frame: itemFrame)
itemNode.updateLayout(layout, navigationHeight: navigationBarHeight, transition: itemNodeTransition)
visibleBackgroundNodes.append(itemNode)
@ -451,13 +492,64 @@ final class WallpaperListPreviewControllerNode: ViewControllerTracingNode {
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() {
self.dismiss()
}
@objc private func applyPressed() {
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 {
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
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)
}
case let .internalReference(_, _, _, _, _, _, file, _):