mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '2e9e017b570d713506a2b36526b52cd72a50833f'
This commit is contained in:
commit
3aec94b4b2
@ -9071,3 +9071,13 @@ Sorry for the inconvenience.";
|
|||||||
"DataUsage.SettingsHelpWifi" = "You can change your auto-download settings for media to reduce data usage when on wifi.";
|
"DataUsage.SettingsHelpWifi" = "You can change your auto-download settings for media to reduce data usage when on wifi.";
|
||||||
|
|
||||||
"DataUsage.Reset" = "Reset Statistics";
|
"DataUsage.Reset" = "Reset Statistics";
|
||||||
|
|
||||||
|
"Conversation.SendWhenOnlineTooltip" = "Long tap to send the message later.";
|
||||||
|
|
||||||
|
"Bot.Active" = "bot is active now";
|
||||||
|
"Bot.Inactive" = "bot is not active";
|
||||||
|
"Bot.Slow" = "bot replies slowly";
|
||||||
|
"Bot.TapToUse" = "Tap here to use this bot";
|
||||||
|
|
||||||
|
"CreateGroup.PeersTitleDelimeter" = ", ";
|
||||||
|
"CreateGroup.PeersTitleLastDelimeter" = " and ";
|
||||||
|
@ -320,7 +320,8 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
|||||||
let bold = MarkdownAttributeSet(font: Font.semibold(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
let bold = MarkdownAttributeSet(font: Font.semibold(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||||
if let _ = resetPendingDate {
|
if let _ = resetPendingDate {
|
||||||
self.actionDisposable.set(
|
self.actionDisposable.set(
|
||||||
resetLoginEmail(account: self.account, phoneNumber: number, phoneCodeHash: phoneCodeHash).start(error: { [weak self] error in
|
(resetLoginEmail(account: self.account, phoneNumber: number, phoneCodeHash: phoneCodeHash)
|
||||||
|
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||||
if let self, case .alreadyInProgress = error {
|
if let self, case .alreadyInProgress = error {
|
||||||
let formattedNumber = formatPhoneNumber(number)
|
let formattedNumber = formatPhoneNumber(number)
|
||||||
let title = NSAttributedString(string: self.presentationData.strings.Login_Email_PremiumRequiredTitle, font: Font.semibold(self.presentationData.listsFontSize.baseDisplaySize), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
let title = NSAttributedString(string: self.presentationData.strings.Login_Email_PremiumRequiredTitle, font: Font.semibold(self.presentationData.listsFontSize.baseDisplaySize), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||||
|
@ -103,9 +103,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
return context.engine.data.get(
|
return context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.IsContact(id: peer.id),
|
TelegramEngine.EngineData.Item.Peer.IsContact(id: peer.id),
|
||||||
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id),
|
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id),
|
||||||
|
TelegramEngine.EngineData.Item.NotificationSettings.Global(),
|
||||||
TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: peer.id)
|
TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: peer.id)
|
||||||
)
|
)
|
||||||
|> map { [weak chatListController] isContact, notificationSettings, readCounters -> [ContextMenuItem] in
|
|> map { [weak chatListController] isContact, notificationSettings, globalNotificationSettings, readCounters -> [ContextMenuItem] in
|
||||||
if promoInfo != nil {
|
if promoInfo != nil {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -164,8 +165,21 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isMuted = false
|
var isMuted = false
|
||||||
if case .muted = notificationSettings.muteState {
|
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||||
isMuted = true
|
isMuted = true
|
||||||
|
} else if case .default = notificationSettings.muteState {
|
||||||
|
if case .user = peer {
|
||||||
|
isMuted = !globalNotificationSettings.privateChats.enabled
|
||||||
|
} else if case .legacyGroup = peer {
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
switch channel.info {
|
||||||
|
case .group:
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
case .broadcast:
|
||||||
|
isMuted = !globalNotificationSettings.channels.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isUnread = false
|
var isUnread = false
|
||||||
@ -406,8 +420,21 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
|
|
||||||
if !isSavedMessages {
|
if !isSavedMessages {
|
||||||
var isMuted = false
|
var isMuted = false
|
||||||
if case .muted = notificationSettings.muteState {
|
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||||
isMuted = true
|
isMuted = true
|
||||||
|
} else if case .default = notificationSettings.muteState {
|
||||||
|
if case .user = peer {
|
||||||
|
isMuted = !globalNotificationSettings.privateChats.enabled
|
||||||
|
} else if case .legacyGroup = peer {
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
switch channel.info {
|
||||||
|
case .group:
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
case .broadcast:
|
||||||
|
isMuted = !globalNotificationSettings.channels.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||||
let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil)
|
let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil)
|
||||||
@ -506,9 +533,10 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
|
|||||||
return context.engine.data.get(
|
return context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
||||||
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
|
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
|
||||||
TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId)
|
TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId),
|
||||||
|
TelegramEngine.EngineData.Item.NotificationSettings.Global()
|
||||||
)
|
)
|
||||||
|> mapToSignal { peer, peerNotificationSettings, threadData -> Signal<[ContextMenuItem], NoError> in
|
|> mapToSignal { peer, peerNotificationSettings, threadData, globalNotificationSettings -> Signal<[ContextMenuItem], NoError> in
|
||||||
guard case let .channel(channel) = peer else {
|
guard case let .channel(channel) = peer else {
|
||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
@ -560,9 +588,15 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
|
|||||||
case .unmuted:
|
case .unmuted:
|
||||||
isMuted = false
|
isMuted = false
|
||||||
case .default:
|
case .default:
|
||||||
if case .muted = peerNotificationSettings.muteState {
|
var peerIsMuted = false
|
||||||
isMuted = true
|
if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||||
|
peerIsMuted = true
|
||||||
|
} else if case .default = peerNotificationSettings.muteState {
|
||||||
|
if case let .channel(channel) = peer, case .group = channel.info {
|
||||||
|
peerIsMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
isMuted = peerIsMuted
|
||||||
}
|
}
|
||||||
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] c, f in
|
items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] c, f in
|
||||||
if isMuted {
|
if isMuted {
|
||||||
|
@ -38,13 +38,13 @@ private enum ChatListRecentEntryStableId: Hashable {
|
|||||||
|
|
||||||
private enum ChatListRecentEntry: Comparable, Identifiable {
|
private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||||
case topPeers([EnginePeer], PresentationTheme, PresentationStrings)
|
case topPeers([EnginePeer], PresentationTheme, PresentationStrings)
|
||||||
case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder)
|
case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, EngineGlobalNotificationSettings)
|
||||||
|
|
||||||
var stableId: ChatListRecentEntryStableId {
|
var stableId: ChatListRecentEntryStableId {
|
||||||
switch self {
|
switch self {
|
||||||
case .topPeers:
|
case .topPeers:
|
||||||
return .topPeers
|
return .topPeers
|
||||||
case let .peer(_, peer, _, _, _, _, _):
|
case let .peer(_, peer, _, _, _, _, _, _):
|
||||||
return .peerId(peer.peer.peerId)
|
return .peerId(peer.peer.peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,8 +66,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder):
|
case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsGlobalNotificationsSettings):
|
||||||
if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder {
|
if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsGlobalNotificationsSettings) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsGlobalNotificationsSettings == rhsGlobalNotificationsSettings {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -79,11 +79,11 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
switch lhs {
|
switch lhs {
|
||||||
case .topPeers:
|
case .topPeers:
|
||||||
return true
|
return true
|
||||||
case let .peer(lhsIndex, _, _, _, _, _, _):
|
case let .peer(lhsIndex, _, _, _, _, _, _, _):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .topPeers:
|
case .topPeers:
|
||||||
return false
|
return false
|
||||||
case let .peer(rhsIndex, _, _, _, _, _, _):
|
case let .peer(rhsIndex, _, _, _, _, _, _, _):
|
||||||
return lhsIndex <= rhsIndex
|
return lhsIndex <= rhsIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder):
|
case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, globalNotificationSettings):
|
||||||
let primaryPeer: EnginePeer
|
let primaryPeer: EnginePeer
|
||||||
var chatPeer: EnginePeer?
|
var chatPeer: EnginePeer?
|
||||||
let maybeChatPeer = EnginePeer(peer.peer.peers[peer.peer.peerId]!)
|
let maybeChatPeer = EnginePeer(peer.peer.peers[peer.peer.peerId]!)
|
||||||
@ -185,11 +185,27 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
status = .none
|
status = .none
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMuted = false
|
var isMuted = false
|
||||||
if let notificationSettings = peer.notificationSettings {
|
if let notificationSettings = peer.notificationSettings {
|
||||||
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
|
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||||
|
isMuted = true
|
||||||
|
} else if case .default = notificationSettings.muteState {
|
||||||
|
if case .user = primaryPeer {
|
||||||
|
isMuted = !globalNotificationSettings.privateChats.enabled
|
||||||
|
} else if case .legacyGroup = primaryPeer {
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
} else if case let .channel(channel) = primaryPeer {
|
||||||
|
switch channel.info {
|
||||||
|
case .group:
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
case .broadcast:
|
||||||
|
isMuted = !globalNotificationSettings.channels.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var badge: ContactsPeerItemBadge?
|
var badge: ContactsPeerItemBadge?
|
||||||
if peer.unreadCount > 0 {
|
if peer.unreadCount > 0 {
|
||||||
badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active)
|
badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active)
|
||||||
@ -1374,7 +1390,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
context.engine.contacts.searchLocalPeers(query: query.lowercased()),
|
context.engine.contacts.searchLocalPeers(query: query.lowercased()),
|
||||||
fixedOrRemovedRecentlySearchedPeers
|
fixedOrRemovedRecentlySearchedPeers
|
||||||
)
|
)
|
||||||
|> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer], Set<EnginePeer.Id>), NoError> in
|
|> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer], Set<EnginePeer.Id>, EngineGlobalNotificationSettings), NoError> in
|
||||||
let recentlySearched = allRecentlySearched.filter { peer in
|
let recentlySearched = allRecentlySearched.filter { peer in
|
||||||
guard let peer = peer.peer.peer else {
|
guard let peer = peer.peer.peer else {
|
||||||
return false
|
return false
|
||||||
@ -1408,25 +1424,37 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in
|
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in
|
||||||
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId)
|
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId)
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
|
TelegramEngine.EngineData.Item.NotificationSettings.Global()
|
||||||
)
|
)
|
||||||
|> map { notificationSettings, unreadCounts in
|
|> map { notificationSettings, unreadCounts, globalNotificationSettings in
|
||||||
return (notificationSettings, unreadCounts, peers, Set(recentlySearched.map(\.peer.peerId)))
|
return (notificationSettings, unreadCounts, peers, Set(recentlySearched.map(\.peer.peerId)), globalNotificationSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> map { notificationSettings, unreadCounts, peers, recentlySearchedPeerIds -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>) in
|
|> map { notificationSettings, unreadCounts, peers, recentlySearchedPeerIds, globalNotificationSettings -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>) in
|
||||||
var unread: [EnginePeer.Id: (Int32, Bool)] = [:]
|
var unread: [EnginePeer.Id: (Int32, Bool)] = [:]
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
var isMuted: Bool = false
|
var isMuted = false
|
||||||
if let nofiticationSettings = notificationSettings[peer.peerId] {
|
if let peerNotificationSettings = notificationSettings[peer.peerId], let peerNotificationSettings {
|
||||||
switch nofiticationSettings?.muteState {
|
if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||||
case .muted:
|
|
||||||
isMuted = true
|
isMuted = true
|
||||||
default:
|
} else if case .default = peerNotificationSettings.muteState {
|
||||||
break
|
if let peer = peer.peer {
|
||||||
|
if case .user = peer {
|
||||||
|
isMuted = !globalNotificationSettings.privateChats.enabled
|
||||||
|
} else if case .legacyGroup = peer {
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
switch channel.info {
|
||||||
|
case .group:
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
case .broadcast:
|
||||||
|
isMuted = !globalNotificationSettings.channels.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let unreadCount = unreadCounts[peer.peerId]
|
let unreadCount = unreadCounts[peer.peerId]
|
||||||
if let unreadCount = unreadCount, unreadCount > 0 {
|
if let unreadCount = unreadCount, unreadCount > 0 {
|
||||||
unread[peer.peerId] = (Int32(unreadCount), isMuted)
|
unread[peer.peerId] = (Int32(unreadCount), isMuted)
|
||||||
@ -2399,8 +2427,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
var recentItems = combineLatest(hasRecentPeers, fixedRecentlySearchedPeers, presentationDataPromise.get())
|
var recentItems = combineLatest(
|
||||||
|> mapToSignal { hasRecentPeers, peers, presentationData -> Signal<[ChatListRecentEntry], NoError> in
|
hasRecentPeers,
|
||||||
|
fixedRecentlySearchedPeers,
|
||||||
|
presentationDataPromise.get(),
|
||||||
|
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global())
|
||||||
|
)
|
||||||
|
|> mapToSignal { hasRecentPeers, peers, presentationData, globalNotificationSettings -> Signal<[ChatListRecentEntry], NoError> in
|
||||||
var entries: [ChatListRecentEntry] = []
|
var entries: [ChatListRecentEntry] = []
|
||||||
if !peersFilter.contains(.onlyGroups) {
|
if !peersFilter.contains(.onlyGroups) {
|
||||||
if hasRecentPeers {
|
if hasRecentPeers {
|
||||||
@ -2419,7 +2452,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
peerIds.insert(peer.id)
|
peerIds.insert(peer.id)
|
||||||
|
|
||||||
entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
|
entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalNotificationSettings))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,11 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL
|
|||||||
for groupId in additionalGroupIds {
|
for groupId in additionalGroupIds {
|
||||||
unreadCountItems.append(.totalInGroup(groupId))
|
unreadCountItems.append(.totalInGroup(groupId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.globalNotifications]))
|
||||||
let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems)
|
let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems)
|
||||||
var keys: [PostboxViewKey] = []
|
var keys: [PostboxViewKey] = []
|
||||||
|
keys.append(globalNotificationsKey)
|
||||||
keys.append(unreadKey)
|
keys.append(unreadKey)
|
||||||
for peerId in additionalPeerIds {
|
for peerId in additionalPeerIds {
|
||||||
keys.append(.basicPeer(peerId))
|
keys.append(.basicPeer(peerId))
|
||||||
@ -45,6 +48,13 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL
|
|||||||
return (0, [])
|
return (0, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var globalNotificationSettings: GlobalNotificationSettingsSet
|
||||||
|
if let settingsView = view.views[globalNotificationsKey] as? PreferencesView, let settings = settingsView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) {
|
||||||
|
globalNotificationSettings = settings.effective
|
||||||
|
} else {
|
||||||
|
globalNotificationSettings = GlobalNotificationSettings.defaultSettings.effective
|
||||||
|
}
|
||||||
|
|
||||||
var result: [(ChatListFilter, Int, Bool)] = []
|
var result: [(ChatListFilter, Int, Bool)] = []
|
||||||
|
|
||||||
var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool, PeerGroupId?, Bool)] = [:]
|
var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool, PeerGroupId?, Bool)] = [:]
|
||||||
@ -66,7 +76,28 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL
|
|||||||
peerCount = max(1, peerCount)
|
peerCount = max(1, peerCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, case .muted = notificationSettings.muteState {
|
var isMuted = false
|
||||||
|
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
||||||
|
if case .muted = notificationSettings.muteState {
|
||||||
|
isMuted = true
|
||||||
|
} else if case .default = notificationSettings.muteState {
|
||||||
|
if let peer = peerView.peer {
|
||||||
|
if peer is TelegramUser {
|
||||||
|
isMuted = !globalNotificationSettings.privateChats.enabled
|
||||||
|
} else if peer is TelegramGroup {
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
switch channel.info {
|
||||||
|
case .group:
|
||||||
|
isMuted = !globalNotificationSettings.groupChats.enabled
|
||||||
|
case .broadcast:
|
||||||
|
isMuted = !globalNotificationSettings.channels.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isMuted {
|
||||||
peerTagAndCount[peerId] = (tag, peerCount, false, peerView.groupId, true)
|
peerTagAndCount[peerId] = (tag, peerCount, false, peerView.groupId, true)
|
||||||
} else {
|
} else {
|
||||||
peerTagAndCount[peerId] = (tag, peerCount, true, peerView.groupId, false)
|
peerTagAndCount[peerId] = (tag, peerCount, true, peerView.groupId, false)
|
||||||
|
@ -507,6 +507,10 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
|||||||
self.textNode.becomeFirstResponder()
|
self.textNode.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func selectAll() {
|
||||||
|
self.textNode.textView.selectAll(nil)
|
||||||
|
}
|
||||||
|
|
||||||
override func isReorderable(at point: CGPoint) -> Bool {
|
override func isReorderable(at point: CGPoint) -> Bool {
|
||||||
if self.reorderControlNode.frame.contains(point), !self.reorderControlNode.isHidden, !self.isDisplayingRevealedOptions {
|
if self.reorderControlNode.frame.contains(point), !self.reorderControlNode.isHidden, !self.isDisplayingRevealedOptions {
|
||||||
return true
|
return true
|
||||||
|
@ -617,6 +617,10 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func selectAll() {
|
||||||
|
self.textNode.textView.selectAll(nil)
|
||||||
|
}
|
||||||
|
|
||||||
public func animateError() {
|
public func animateError() {
|
||||||
self.textNode.layer.addShakeAnimation()
|
self.textNode.layer.addShakeAnimation()
|
||||||
}
|
}
|
||||||
|
@ -1328,16 +1328,19 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
} else if let file = m as? TelegramMediaFile {
|
} else if let file = m as? TelegramMediaFile {
|
||||||
subject = .media(.message(message: MessageReference(messages[0]._asMessage()), media: file))
|
subject = .media(.message(message: MessageReference(messages[0]._asMessage()), media: file))
|
||||||
if file.isAnimated {
|
if file.isAnimated {
|
||||||
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Preview_SaveGif, action: { [weak self] in
|
if messages[0].id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
if let strongSelf = self {
|
preferredAction = .default
|
||||||
let message = messages[0]
|
} else {
|
||||||
|
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Preview_SaveGif, action: { [weak self] in
|
||||||
let context = strongSelf.context
|
if let strongSelf = self {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let message = messages[0]
|
||||||
let controllerInteraction = strongSelf.controllerInteraction
|
|
||||||
let _ = (toggleGifSaved(account: context.account, fileReference: .message(message: MessageReference(message._asMessage()), media: file), saved: true)
|
let context = strongSelf.context
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
switch result {
|
let controllerInteraction = strongSelf.controllerInteraction
|
||||||
|
let _ = (toggleGifSaved(account: context.account, fileReference: .message(message: MessageReference(message._asMessage()), media: file), saved: true)
|
||||||
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
|
switch result {
|
||||||
case .generic:
|
case .generic:
|
||||||
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
case let .limitExceeded(limit, premiumLimit):
|
case let .limitExceeded(limit, premiumLimit):
|
||||||
@ -1356,10 +1359,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}), nil)
|
}), nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
} else if file.mimeType.hasPrefix("image/") {
|
} else if file.mimeType.hasPrefix("image/") {
|
||||||
preferredAction = .saveToCameraRoll
|
preferredAction = .saveToCameraRoll
|
||||||
actionCompletionText = strongSelf.presentationData.strings.Gallery_ImageSaved
|
actionCompletionText = strongSelf.presentationData.strings.Gallery_ImageSaved
|
||||||
|
@ -1118,6 +1118,10 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||||||
self.inputFirstField?.becomeFirstResponder()
|
self.inputFirstField?.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func selectAll() {
|
||||||
|
self.inputFirstField?.selectAll(nil)
|
||||||
|
}
|
||||||
|
|
||||||
override public func longTapped() {
|
override public func longTapped() {
|
||||||
self.item?.longTapAction?()
|
self.item?.longTapAction?()
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ public protocol ItemListItemNode {
|
|||||||
|
|
||||||
public protocol ItemListItemFocusableNode {
|
public protocol ItemListItemFocusableNode {
|
||||||
func focus()
|
func focus()
|
||||||
|
func selectAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ItemListInsetWithOtherSection {
|
public enum ItemListInsetWithOtherSection {
|
||||||
|
@ -472,6 +472,10 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func selectAll() {
|
||||||
|
self.textNode.textView.selectAll(nil)
|
||||||
|
}
|
||||||
|
|
||||||
public func animateError() {
|
public func animateError() {
|
||||||
self.textNode.layer.addShakeAnimation()
|
self.textNode.layer.addShakeAnimation()
|
||||||
}
|
}
|
||||||
|
@ -473,6 +473,10 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func selectAll() {
|
||||||
|
self.textNode.textField.selectAll(nil)
|
||||||
|
}
|
||||||
|
|
||||||
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||||
|
@ -1195,7 +1195,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
strongSelf.currentIconImage = iconImage
|
strongSelf.currentIconImage = iconImage
|
||||||
|
|
||||||
if let updateIconImageSignal, let iconImage, case .albumArt = iconImage {
|
if let updateIconImageSignal, let iconImage, case .albumArt = iconImage {
|
||||||
strongSelf.iconStatusNode.setBackgroundImage(updateIconImageSignal)
|
strongSelf.iconStatusNode.setBackgroundImage(updateIconImageSignal, size: CGSize(width: 40.0, height: 40.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let iconImageApply = iconImageApply {
|
if let iconImageApply = iconImageApply {
|
||||||
|
@ -234,7 +234,7 @@ public final class MediaGroupsScreen: ViewController {
|
|||||||
albums.append(collection)
|
albums.append(collection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.albums.enumerateObjects { collection, _, _ in
|
state.albums.enumerateObjects(options: [.reverse]) { collection, _, _ in
|
||||||
albums.append(collection)
|
albums.append(collection)
|
||||||
}
|
}
|
||||||
entries.append(.albums(self.presentationData.theme, albums))
|
entries.append(.albums(self.presentationData.theme, albums))
|
||||||
|
@ -347,4 +347,8 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN
|
|||||||
func focus() {
|
func focus() {
|
||||||
self.phoneNode.numberField?.becomeFirstResponder()
|
self.phoneNode.numberField?.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func selectAll() {
|
||||||
|
self.phoneNode.numberField?.textField.selectAll(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -864,16 +864,19 @@ public final class SemanticStatusNode: ASControlNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setBackgroundImage(_ image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>) {
|
public func setBackgroundImage(_ image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, size: CGSize) {
|
||||||
let start = CACurrentMediaTime()
|
let start = CACurrentMediaTime()
|
||||||
self.disposable = combineLatest(queue: Queue.mainQueue(), image, self.hasLayoutPromise.get()).start(next: { [weak self] transform, ready in
|
let imageSignal: Signal<UIImage?, NoError> = image
|
||||||
|
|> map { transform -> UIImage? in
|
||||||
|
let context = transform(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||||
|
return context?.generateImage()
|
||||||
|
}
|
||||||
|
self.disposable = combineLatest(queue: Queue.mainQueue(), imageSignal, self.hasLayoutPromise.get()).start(next: { [weak self] image, ready in
|
||||||
guard let strongSelf = self, ready else {
|
guard let strongSelf = self, ready else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
let previousAppearanceContext = strongSelf.appearanceContext
|
let previousAppearanceContext = strongSelf.appearanceContext
|
||||||
strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(context?.generateImage())
|
strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(image)
|
||||||
|
|
||||||
if CACurrentMediaTime() - start > 0.3 {
|
if CACurrentMediaTime() - start > 0.3 {
|
||||||
strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {})
|
strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {})
|
||||||
@ -915,7 +918,7 @@ public final class SemanticStatusNode: ASControlNode {
|
|||||||
self.displaysAsynchronously = true
|
self.displaysAsynchronously = true
|
||||||
|
|
||||||
if let image {
|
if let image {
|
||||||
self.setBackgroundImage(image)
|
self.setBackgroundImage(image, size: CGSize(width: 44.0, height: 44.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +220,40 @@ public extension TelegramEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func subscribe<
|
||||||
|
T0: TelegramEngineDataItem,
|
||||||
|
T1: TelegramEngineDataItem,
|
||||||
|
T2: TelegramEngineDataItem,
|
||||||
|
T3: TelegramEngineDataItem
|
||||||
|
>(
|
||||||
|
_ t0: T0,
|
||||||
|
_ t1: T1,
|
||||||
|
_ t2: T2,
|
||||||
|
_ t3: T3
|
||||||
|
) -> Signal<
|
||||||
|
(
|
||||||
|
T0.Result,
|
||||||
|
T1.Result,
|
||||||
|
T2.Result,
|
||||||
|
T3.Result
|
||||||
|
),
|
||||||
|
NoError> {
|
||||||
|
return self._subscribe(items: [
|
||||||
|
t0 as! AnyPostboxViewDataItem,
|
||||||
|
t1 as! AnyPostboxViewDataItem,
|
||||||
|
t2 as! AnyPostboxViewDataItem,
|
||||||
|
t3 as! AnyPostboxViewDataItem
|
||||||
|
])
|
||||||
|
|> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result) in
|
||||||
|
return (
|
||||||
|
results[0] as! T0.Result,
|
||||||
|
results[1] as! T1.Result,
|
||||||
|
results[2] as! T2.Result,
|
||||||
|
results[3] as! T3.Result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public func get<
|
public func get<
|
||||||
T0: TelegramEngineDataItem,
|
T0: TelegramEngineDataItem,
|
||||||
@ -253,5 +287,26 @@ public extension TelegramEngine {
|
|||||||
NoError> {
|
NoError> {
|
||||||
return self.subscribe(t0, t1, t2) |> take(1)
|
return self.subscribe(t0, t1, t2) |> take(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func get<
|
||||||
|
T0: TelegramEngineDataItem,
|
||||||
|
T1: TelegramEngineDataItem,
|
||||||
|
T2: TelegramEngineDataItem,
|
||||||
|
T3: TelegramEngineDataItem
|
||||||
|
>(
|
||||||
|
_ t0: T0,
|
||||||
|
_ t1: T1,
|
||||||
|
_ t2: T2,
|
||||||
|
_ t3: T3
|
||||||
|
) -> Signal<
|
||||||
|
(
|
||||||
|
T0.Result,
|
||||||
|
T1.Result,
|
||||||
|
T2.Result,
|
||||||
|
T3.Result
|
||||||
|
),
|
||||||
|
NoError> {
|
||||||
|
return self.subscribe(t0, t1, t2, t3) |> take(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||||||
case dismissedTrendingEmojiPacks = 35
|
case dismissedTrendingEmojiPacks = 35
|
||||||
case audioRateOptionsTip = 36
|
case audioRateOptionsTip = 36
|
||||||
case translationSuggestion = 37
|
case translationSuggestion = 37
|
||||||
|
case sendWhenOnlineTip = 38
|
||||||
|
|
||||||
var key: ValueBoxKey {
|
var key: ValueBoxKey {
|
||||||
let v = ValueBoxKey(length: 4)
|
let v = ValueBoxKey(length: 4)
|
||||||
@ -374,6 +375,10 @@ private struct ApplicationSpecificNoticeKeys {
|
|||||||
static func audioRateOptionsTip() -> NoticeEntryKey {
|
static func audioRateOptionsTip() -> NoticeEntryKey {
|
||||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioRateOptionsTip.key)
|
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioRateOptionsTip.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func sendWhenOnlineTip() -> NoticeEntryKey {
|
||||||
|
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ApplicationSpecificNotice {
|
public struct ApplicationSpecificNotice {
|
||||||
@ -1327,6 +1332,30 @@ public struct ApplicationSpecificNotice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func getSendWhenOnlineTip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||||
|
return accountManager.transaction { transaction -> Int32 in
|
||||||
|
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) {
|
||||||
|
return value.value
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func incrementSendWhenOnlineTip(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int32 = 1) -> Signal<Void, NoError> {
|
||||||
|
return accountManager.transaction { transaction -> Void in
|
||||||
|
var currentValue: Int32 = 0
|
||||||
|
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) {
|
||||||
|
currentValue = value.value
|
||||||
|
}
|
||||||
|
currentValue += count
|
||||||
|
|
||||||
|
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
|
||||||
|
transaction.setNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip(), entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
|
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
|
||||||
return accountManager.transaction { transaction -> Void in
|
return accountManager.transaction { transaction -> Void in
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,14 @@ public func stringForMessageTimestamp(timestamp: Int32, dateTimeFormat: Presenta
|
|||||||
return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat)
|
return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getDateTimeComponents(timestamp: Int32) -> (day: Int32, month: Int32, year: Int32, hour: Int32, minutes: Int32) {
|
||||||
|
var t: time_t = Int(timestamp)
|
||||||
|
var timeinfo = tm()
|
||||||
|
localtime_r(&t, &timeinfo);
|
||||||
|
|
||||||
|
return (timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year, timeinfo.tm_hour, timeinfo.tm_min)
|
||||||
|
}
|
||||||
|
|
||||||
public func stringForMediumDate(timestamp: Int32, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> String {
|
public func stringForMediumDate(timestamp: Int32, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> String {
|
||||||
var t: time_t = Int(timestamp)
|
var t: time_t = Int(timestamp)
|
||||||
var timeinfo = tm()
|
var timeinfo = tm()
|
||||||
|
@ -368,15 +368,12 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
var inputActivitiesAllowed = true
|
var inputActivitiesAllowed = true
|
||||||
if let titleContent = self.titleContent {
|
if let titleContent = self.titleContent {
|
||||||
switch titleContent {
|
switch titleContent {
|
||||||
case let .peer(peerView, _, _, isScheduledMessages, _, customMessageCount, _):
|
case let .peer(peerView, _, _, isScheduledMessages, _, _, _):
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
if let peer = peerViewMainPeer(peerView) {
|
||||||
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies {
|
if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies {
|
||||||
inputActivitiesAllowed = false
|
inputActivitiesAllowed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if customMessageCount != nil {
|
|
||||||
inputActivitiesAllowed = false
|
|
||||||
}
|
|
||||||
case .replyThread:
|
case .replyThread:
|
||||||
inputActivitiesAllowed = true
|
inputActivitiesAllowed = true
|
||||||
default:
|
default:
|
||||||
|
@ -361,7 +361,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private var recentlyUsedInlineBotsDisposable: Disposable?
|
private var recentlyUsedInlineBotsDisposable: Disposable?
|
||||||
|
|
||||||
private var unpinMessageDisposable: MetaDisposable?
|
private var unpinMessageDisposable: MetaDisposable?
|
||||||
|
|
||||||
private let typingActivityPromise = Promise<Bool>(false)
|
private let typingActivityPromise = Promise<Bool>(false)
|
||||||
private var inputActivityDisposable: Disposable?
|
private var inputActivityDisposable: Disposable?
|
||||||
private var recordingActivityValue: ChatRecordingActivity = .none
|
private var recordingActivityValue: ChatRecordingActivity = .none
|
||||||
@ -431,6 +431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private weak var messageTooltipController: TooltipController?
|
private weak var messageTooltipController: TooltipController?
|
||||||
private weak var videoUnmuteTooltipController: TooltipController?
|
private weak var videoUnmuteTooltipController: TooltipController?
|
||||||
private var didDisplayVideoUnmuteTooltip = false
|
private var didDisplayVideoUnmuteTooltip = false
|
||||||
|
private var didDisplaySendWhenOnlineTip = false
|
||||||
private weak var silentPostTooltipController: TooltipController?
|
private weak var silentPostTooltipController: TooltipController?
|
||||||
private weak var mediaRecordingModeTooltipController: TooltipController?
|
private weak var mediaRecordingModeTooltipController: TooltipController?
|
||||||
private weak var mediaRestrictedTooltipController: TooltipController?
|
private weak var mediaRestrictedTooltipController: TooltipController?
|
||||||
@ -3875,16 +3876,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
|
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id))
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id))
|
||||||
|> mapToSignal { message -> Signal<(EngineMessage.Id, Int32?)?, NoError> in
|
|> mapToSignal { message -> Signal<(EngineMessage.Id, Int32?)?, NoError> in
|
||||||
if let message = message, let sourceMessageId = message.forwardInfo?.sourceMessageId {
|
if let message {
|
||||||
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: sourceMessageId.peerId))
|
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: message.id.peerId))
|
||||||
|> map { statsDatacenterId -> (EngineMessage.Id, Int32?)? in
|
|> map { statsDatacenterId -> (EngineMessage.Id, Int32?)? in
|
||||||
return (sourceMessageId, statsDatacenterId)
|
return (message.id, statsDatacenterId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: id.peerId))
|
return .complete()
|
||||||
|> map { statsDatacenterId -> (EngineMessage.Id, Int32?)? in
|
|
||||||
return (id, statsDatacenterId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] messageIdAndStatsDatacenterId in
|
|> deliverOnMainQueue).start(next: { [weak self] messageIdAndStatsDatacenterId in
|
||||||
@ -7572,6 +7570,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
Signal<Bool, NoError>.single(false)
|
Signal<Bool, NoError>.single(false)
|
||||||
|> delay(4.0, queue: Queue.mainQueue())
|
|> delay(4.0, queue: Queue.mainQueue())
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if !strongSelf.didDisplaySendWhenOnlineTip {
|
||||||
|
strongSelf.didDisplaySendWhenOnlineTip = true
|
||||||
|
Queue.mainQueue().after(2.0) {
|
||||||
|
strongSelf.displaySendWhenOnlineTooltip()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.typingActivityPromise.set(.single(false))
|
strongSelf.typingActivityPromise.set(.single(false))
|
||||||
}
|
}
|
||||||
@ -9878,6 +9883,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
sendWhenOnlineAvailable = false
|
sendWhenOnlineAvailable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sendWhenOnlineAvailable {
|
||||||
|
let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: strongSelf.context.sharedContext.accountManager, count: 4).start()
|
||||||
|
}
|
||||||
|
|
||||||
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak self] in
|
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.supportedOrientations = previousSupportedOrientations
|
strongSelf.supportedOrientations = previousSupportedOrientations
|
||||||
@ -17507,6 +17516,60 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func displaySendWhenOnlineTooltip() {
|
||||||
|
guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self, let peerId = self.chatLocation.peerId else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string
|
||||||
|
guard !inputText.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sendingOptionsTooltipController?.dismiss()
|
||||||
|
|
||||||
|
let _ = (ApplicationSpecificNotice.getSendWhenOnlineTip(accountManager: self.context.sharedContext.accountManager)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] counter in
|
||||||
|
if let strongSelf = self, counter < 3 {
|
||||||
|
let _ = (strongSelf.context.account.viewTracker.peerView(peerId)
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
||||||
|
guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var sendWhenOnlineAvailable = false
|
||||||
|
if peer.id != strongSelf.context.account.peerId, let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status, until != .max {
|
||||||
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
let (_, _, _, hours, _) = getDateTimeComponents(timestamp: currentTime)
|
||||||
|
if currentTime > until + 60 * 30 && hours >= 0 && hours <= 8 {
|
||||||
|
sendWhenOnlineAvailable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
|
||||||
|
sendWhenOnlineAvailable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sendWhenOnlineAvailable {
|
||||||
|
let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: strongSelf.context.sharedContext.accountManager).start()
|
||||||
|
|
||||||
|
let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Conversation_SendWhenOnlineTooltip), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0)
|
||||||
|
strongSelf.sendingOptionsTooltipController = tooltipController
|
||||||
|
tooltipController.dismissed = { [weak self, weak tooltipController] _ in
|
||||||
|
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.sendingOptionsTooltipController === tooltipController {
|
||||||
|
strongSelf.sendingOptionsTooltipController = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
return (strongSelf.chatDisplayNode, rect)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func displaySendingOptionsTooltip() {
|
private func displaySendingOptionsTooltip() {
|
||||||
guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self else {
|
guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self else {
|
||||||
return
|
return
|
||||||
|
@ -1514,7 +1514,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let file = media as? TelegramMediaFile, !isCopyProtected {
|
if message.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
|
||||||
|
} else if let file = media as? TelegramMediaFile, !isCopyProtected {
|
||||||
if file.isVideo {
|
if file.isVideo {
|
||||||
if file.isAnimated && !file.isVideoSticker {
|
if file.isAnimated && !file.isVideoSticker {
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveGif, icon: { theme in
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveGif, icon: { theme in
|
||||||
|
@ -3540,9 +3540,33 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
var mediaMessage: Message?
|
var mediaMessage: Message?
|
||||||
var forceOpen = false
|
var forceOpen = false
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
for media in item.message.media {
|
if case .group = item.content {
|
||||||
if let file = media as? TelegramMediaFile, file.duration != nil {
|
var message: Message? = item.content.firstMessage
|
||||||
mediaMessage = item.message
|
loop: for contentNode in self.contentNodes {
|
||||||
|
if !(contentNode is ChatMessageTextBubbleContentNode) {
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
let convertedNodeFrame = contentNode.view.convert(contentNode.bounds, to: self.view).insetBy(dx: 0.0, dy: -10.0)
|
||||||
|
if !convertedNodeFrame.contains(location) {
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
if contentNode is ChatMessageEventLogPreviousMessageContentNode {
|
||||||
|
} else {
|
||||||
|
message = contentNode.item?.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let message {
|
||||||
|
for media in message.media {
|
||||||
|
if let file = media as? TelegramMediaFile, file.duration != nil {
|
||||||
|
mediaMessage = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for media in item.message.media {
|
||||||
|
if let file = media as? TelegramMediaFile, file.duration != nil {
|
||||||
|
mediaMessage = item.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mediaMessage == nil {
|
if mediaMessage == nil {
|
||||||
|
@ -553,6 +553,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
var endEditingImpl: (() -> Void)?
|
var endEditingImpl: (() -> Void)?
|
||||||
var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)?
|
var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)?
|
||||||
var findAutoremoveReferenceNode: (() -> ItemListDisclosureItemNode?)?
|
var findAutoremoveReferenceNode: (() -> ItemListDisclosureItemNode?)?
|
||||||
|
var selectTitleImpl: (() -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
@ -564,6 +565,50 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
||||||
var uploadedVideoAvatar: (Promise<UploadedPeerPhotoData?>, Double?)? = nil
|
var uploadedVideoAvatar: (Promise<UploadedPeerPhotoData?>, Double?)? = nil
|
||||||
|
|
||||||
|
if initialTitle == nil && peerIds.count > 0 && peerIds.count < 5 {
|
||||||
|
let _ = (context.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||||
|
EngineDataList(
|
||||||
|
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { accountPeer, peers in
|
||||||
|
var allNames: [String] = []
|
||||||
|
if case let .user(user) = accountPeer, let firstName = user.firstName, !firstName.isEmpty {
|
||||||
|
allNames.append(firstName)
|
||||||
|
}
|
||||||
|
for peer in peers {
|
||||||
|
if case let .user(user) = peer, let firstName = user.firstName, !firstName.isEmpty {
|
||||||
|
allNames.append(firstName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allNames.count > 1 {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
var title: String = ""
|
||||||
|
for i in 0 ..< allNames.count {
|
||||||
|
if i == 0 {
|
||||||
|
} else if i < allNames.count - 1 {
|
||||||
|
title.append(presentationData.strings.CreateGroup_PeersTitleDelimeter)
|
||||||
|
} else {
|
||||||
|
title.append(presentationData.strings.CreateGroup_PeersTitleLastDelimeter)
|
||||||
|
}
|
||||||
|
title.append(allNames[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState { current in
|
||||||
|
var current = current
|
||||||
|
current.editingName = .title(title: title, type: .group)
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.3) {
|
||||||
|
selectTitleImpl?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let addressPromise = Promise<String?>(nil)
|
let addressPromise = Promise<String?>(nil)
|
||||||
let venuesPromise = Promise<[TelegramMediaMap]?>(nil)
|
let venuesPromise = Promise<[TelegramMediaMap]?>(nil)
|
||||||
if case let .locatedGroup(latitude, longitude, address) = mode {
|
if case let .locatedGroup(latitude, longitude, address) = mode {
|
||||||
@ -1341,6 +1386,14 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectTitleImpl = { [weak controller] in
|
||||||
|
controller?.forEachItemNode({ itemNode in
|
||||||
|
if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode {
|
||||||
|
itemNode.selectAll()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4908,15 +4908,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: user, chatPeer: user, cachedData: strongSelf.data?.cachedData)
|
let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: user, chatPeer: chatPeer, cachedData: strongSelf.data?.cachedData)
|
||||||
if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil {
|
if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil {
|
||||||
if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser {
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in
|
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor)
|
||||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor)
|
}, action: { c, _ in
|
||||||
}, action: { c, _ in
|
self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user)
|
||||||
self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user)
|
})))
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) {
|
if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) {
|
||||||
@ -5560,8 +5558,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = clearPeerHistory.canClearForMyself {
|
if let canClearForMyself = clearPeerHistory.canClearForMyself {
|
||||||
let text: String = self.presentationData.strings.Conversation_DeleteMessagesForMe
|
let text: String
|
||||||
|
switch canClearForMyself {
|
||||||
|
case .secretChat:
|
||||||
|
text = self.presentationData.strings.Conversation_DeleteMessagesFor(EnginePeer(chatPeer).compactDisplayTitle).string
|
||||||
|
default:
|
||||||
|
text = self.presentationData.strings.Conversation_DeleteMessagesForMe
|
||||||
|
}
|
||||||
|
|
||||||
subItems.append(.action(ContextMenuActionItem(text: text, textColor: .destructive, icon: { _ in
|
subItems.append(.action(ContextMenuActionItem(text: text, textColor: .destructive, icon: { _ in
|
||||||
return nil
|
return nil
|
||||||
@ -6012,7 +6016,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openChatWithClearedHistory(type: InteractiveHistoryClearingType) {
|
private func openChatWithClearedHistory(type: InteractiveHistoryClearingType) {
|
||||||
guard let peer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController else {
|
guard let peer = self.data?.chatPeer, let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user