mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
no message
This commit is contained in:
22
Images.xcassets/Chat List/LiveLocationPanelIcon.imageset/Contents.json
vendored
Normal file
22
Images.xcassets/Chat List/LiveLocationPanelIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LiveLocationTitlePin@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LiveLocationTitlePin@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Images.xcassets/Chat List/LiveLocationPanelIcon.imageset/LiveLocationTitlePin@2x.png
vendored
Normal file
BIN
Images.xcassets/Chat List/LiveLocationPanelIcon.imageset/LiveLocationTitlePin@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 465 B |
BIN
Images.xcassets/Chat List/LiveLocationPanelIcon.imageset/LiveLocationTitlePin@3x.png
vendored
Normal file
BIN
Images.xcassets/Chat List/LiveLocationPanelIcon.imageset/LiveLocationTitlePin@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 404 B |
@@ -86,6 +86,7 @@
|
|||||||
D048EA8B1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA8A1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift */; };
|
D048EA8B1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA8A1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift */; };
|
||||||
D048EA8D1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA8C1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift */; };
|
D048EA8D1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA8C1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift */; };
|
||||||
D048EA8F1F4F2A9C00188713 /* InstantPageSettingsItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA8E1F4F2A9C00188713 /* InstantPageSettingsItemNode.swift */; };
|
D048EA8F1F4F2A9C00188713 /* InstantPageSettingsItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA8E1F4F2A9C00188713 /* InstantPageSettingsItemNode.swift */; };
|
||||||
|
D04B26EC20082EB50053A58C /* LocationBroadcastPanelWavesNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B26EB20082EB50053A58C /* LocationBroadcastPanelWavesNode.swift */; };
|
||||||
D04B4D111EEA04D400711AF6 /* MapResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B4D101EEA04D400711AF6 /* MapResources.swift */; };
|
D04B4D111EEA04D400711AF6 /* MapResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B4D101EEA04D400711AF6 /* MapResources.swift */; };
|
||||||
D04B4D131EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B4D121EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift */; };
|
D04B4D131EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B4D121EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift */; };
|
||||||
D04B4D661EEA993A00711AF6 /* LegacyLocationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B4D651EEA993A00711AF6 /* LegacyLocationController.swift */; };
|
D04B4D661EEA993A00711AF6 /* LegacyLocationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B4D651EEA993A00711AF6 /* LegacyLocationController.swift */; };
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
D091C7A61F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D091C7A51F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift */; };
|
D091C7A61F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D091C7A51F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift */; };
|
||||||
D09250041FE5363D003F693F /* ExperimentalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09250031FE5363D003F693F /* ExperimentalSettings.swift */; };
|
D09250041FE5363D003F693F /* ExperimentalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09250031FE5363D003F693F /* ExperimentalSettings.swift */; };
|
||||||
D09250061FE5371D003F693F /* GlobalExperimentalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09250051FE5371D003F693F /* GlobalExperimentalSettings.swift */; };
|
D09250061FE5371D003F693F /* GlobalExperimentalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09250051FE5371D003F693F /* GlobalExperimentalSettings.swift */; };
|
||||||
|
D09394132007F5BB00997F31 /* LocationBroadcastNavigationAccessoryPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09394122007F5BB00997F31 /* LocationBroadcastNavigationAccessoryPanel.swift */; };
|
||||||
D0943AF61FDAAE7E001522CC /* MultipleAvatarsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */; };
|
D0943AF61FDAAE7E001522CC /* MultipleAvatarsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */; };
|
||||||
D0943AFE1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */; };
|
D0943AFE1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */; };
|
||||||
D0943B001FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */; };
|
D0943B001FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */; };
|
||||||
@@ -1209,6 +1211,7 @@
|
|||||||
D049EAE51E44AD5600A2CD3A /* ChatMediaInputMetaSectionItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputMetaSectionItemNode.swift; sourceTree = "<group>"; };
|
D049EAE51E44AD5600A2CD3A /* ChatMediaInputMetaSectionItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputMetaSectionItemNode.swift; sourceTree = "<group>"; };
|
||||||
D049EAED1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListRecentPeersListItem.swift; sourceTree = "<group>"; };
|
D049EAED1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListRecentPeersListItem.swift; sourceTree = "<group>"; };
|
||||||
D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceController.swift; sourceTree = "<group>"; };
|
D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceController.swift; sourceTree = "<group>"; };
|
||||||
|
D04B26EB20082EB50053A58C /* LocationBroadcastPanelWavesNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationBroadcastPanelWavesNode.swift; sourceTree = "<group>"; };
|
||||||
D04B4D101EEA04D400711AF6 /* MapResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapResources.swift; sourceTree = "<group>"; };
|
D04B4D101EEA04D400711AF6 /* MapResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapResources.swift; sourceTree = "<group>"; };
|
||||||
D04B4D121EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageMapBubbleContentNode.swift; sourceTree = "<group>"; };
|
D04B4D121EEA0A6500711AF6 /* ChatMessageMapBubbleContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageMapBubbleContentNode.swift; sourceTree = "<group>"; };
|
||||||
D04B4D651EEA993A00711AF6 /* LegacyLocationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyLocationController.swift; sourceTree = "<group>"; };
|
D04B4D651EEA993A00711AF6 /* LegacyLocationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyLocationController.swift; sourceTree = "<group>"; };
|
||||||
@@ -1423,6 +1426,7 @@
|
|||||||
D091C7A51F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsThemeWallpaperNode.swift; sourceTree = "<group>"; };
|
D091C7A51F8ECEA300D7DE13 /* SettingsThemeWallpaperNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsThemeWallpaperNode.swift; sourceTree = "<group>"; };
|
||||||
D09250031FE5363D003F693F /* ExperimentalSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentalSettings.swift; sourceTree = "<group>"; };
|
D09250031FE5363D003F693F /* ExperimentalSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentalSettings.swift; sourceTree = "<group>"; };
|
||||||
D09250051FE5371D003F693F /* GlobalExperimentalSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalExperimentalSettings.swift; sourceTree = "<group>"; };
|
D09250051FE5371D003F693F /* GlobalExperimentalSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalExperimentalSettings.swift; sourceTree = "<group>"; };
|
||||||
|
D09394122007F5BB00997F31 /* LocationBroadcastNavigationAccessoryPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationBroadcastNavigationAccessoryPanel.swift; sourceTree = "<group>"; };
|
||||||
D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAvatarsNode.swift; sourceTree = "<group>"; };
|
D0943AF51FDAAE7E001522CC /* MultipleAvatarsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAvatarsNode.swift; sourceTree = "<group>"; };
|
||||||
D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMultipleAvatarsNavigationNode.swift; sourceTree = "<group>"; };
|
D0943AFD1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMultipleAvatarsNavigationNode.swift; sourceTree = "<group>"; };
|
||||||
D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatFeedNavigationInputPanelNode.swift; sourceTree = "<group>"; };
|
D0943AFF1FDAE852001522CC /* ChatFeedNavigationInputPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatFeedNavigationInputPanelNode.swift; sourceTree = "<group>"; };
|
||||||
@@ -2764,6 +2768,8 @@
|
|||||||
D0736F2B1DF4DC2400F2C02A /* MediaNavigationAccessoryContainerNode.swift */,
|
D0736F2B1DF4DC2400F2C02A /* MediaNavigationAccessoryContainerNode.swift */,
|
||||||
D0736F2D1DF4E54A00F2C02A /* MediaNavigationAccessoryHeaderNode.swift */,
|
D0736F2D1DF4E54A00F2C02A /* MediaNavigationAccessoryHeaderNode.swift */,
|
||||||
D0177B811DFAEA5400A5083A /* MediaNavigationAccessoryItemListNode.swift */,
|
D0177B811DFAEA5400A5083A /* MediaNavigationAccessoryItemListNode.swift */,
|
||||||
|
D09394122007F5BB00997F31 /* LocationBroadcastNavigationAccessoryPanel.swift */,
|
||||||
|
D04B26EB20082EB50053A58C /* LocationBroadcastPanelWavesNode.swift */,
|
||||||
);
|
);
|
||||||
name = "Telegram Controller";
|
name = "Telegram Controller";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -5114,6 +5120,7 @@
|
|||||||
D0EC6CCF1EB9F58800EBF1C3 /* GeoLocation.swift in Sources */,
|
D0EC6CCF1EB9F58800EBF1C3 /* GeoLocation.swift in Sources */,
|
||||||
D0EC6EA71EBA0FB000EBF1C3 /* BlockingQueue.cpp in Sources */,
|
D0EC6EA71EBA0FB000EBF1C3 /* BlockingQueue.cpp in Sources */,
|
||||||
D0EC6CD01EB9F58800EBF1C3 /* PerformanceSpinner.swift in Sources */,
|
D0EC6CD01EB9F58800EBF1C3 /* PerformanceSpinner.swift in Sources */,
|
||||||
|
D09394132007F5BB00997F31 /* LocationBroadcastNavigationAccessoryPanel.swift in Sources */,
|
||||||
D0471B5C1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift in Sources */,
|
D0471B5C1EFEB4F30074D609 /* BotPaymentFieldItemNode.swift in Sources */,
|
||||||
D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */,
|
D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */,
|
||||||
D0EC6CD11EB9F58800EBF1C3 /* UrlHandling.swift in Sources */,
|
D0EC6CD11EB9F58800EBF1C3 /* UrlHandling.swift in Sources */,
|
||||||
@@ -5167,6 +5174,7 @@
|
|||||||
D0EC6FEF1EBA18E800EBF1C3 /* VoIPController.cpp in Sources */,
|
D0EC6FEF1EBA18E800EBF1C3 /* VoIPController.cpp in Sources */,
|
||||||
D0EC6CED1EB9F58800EBF1C3 /* StringPluralization.swift in Sources */,
|
D0EC6CED1EB9F58800EBF1C3 /* StringPluralization.swift in Sources */,
|
||||||
D020A9DC1FEAE6E7008C66F7 /* OverlayPlayerControllerNode.swift in Sources */,
|
D020A9DC1FEAE6E7008C66F7 /* OverlayPlayerControllerNode.swift in Sources */,
|
||||||
|
D04B26EC20082EB50053A58C /* LocationBroadcastPanelWavesNode.swift in Sources */,
|
||||||
D0EC6FC61EBA135100EBF1C3 /* complex_fft.c in Sources */,
|
D0EC6FC61EBA135100EBF1C3 /* complex_fft.c in Sources */,
|
||||||
D0EC6CEE1EB9F58800EBF1C3 /* InAppNotificationSettings.swift in Sources */,
|
D0EC6CEE1EB9F58800EBF1C3 /* InAppNotificationSettings.swift in Sources */,
|
||||||
D0EC6CEF1EB9F58800EBF1C3 /* PresentationPasscodeSettings.swift in Sources */,
|
D0EC6CEF1EB9F58800EBF1C3 /* PresentationPasscodeSettings.swift in Sources */,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<key>TelegramUI.xcscheme</key>
|
<key>TelegramUI.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>13</integer>
|
<integer>3</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|||||||
@@ -149,10 +149,14 @@ public final class ChatController: TelegramController {
|
|||||||
self.messageId = messageId
|
self.messageId = messageId
|
||||||
self.botStart = botStart
|
self.botStart = botStart
|
||||||
|
|
||||||
|
let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||||
|
|
||||||
switch chatLocation {
|
switch chatLocation {
|
||||||
case .peer:
|
case let .peer(peerId):
|
||||||
|
locationBroadcastPanelSource = .peer(peerId)
|
||||||
self.chatLocationInfoData = .peer(Promise())
|
self.chatLocationInfoData = .peer(Promise())
|
||||||
case .group:
|
case .group:
|
||||||
|
locationBroadcastPanelSource = .none
|
||||||
self.chatLocationInfoData = .group(Promise())
|
self.chatLocationInfoData = .group(Promise())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +170,7 @@ public final class ChatController: TelegramController {
|
|||||||
if case .overlay = mode {
|
if case .overlay = mode {
|
||||||
enableMediaAccessoryPanel = false
|
enableMediaAccessoryPanel = false
|
||||||
}
|
}
|
||||||
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme), enableMediaAccessoryPanel: enableMediaAccessoryPanel)
|
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme), enableMediaAccessoryPanel: enableMediaAccessoryPanel, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
||||||
|
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
|
|||||||
|
|
||||||
self.titleView = NetworkStatusTitleView(theme: self.presentationData.theme)
|
self.titleView = NetworkStatusTitleView(theme: self.presentationData.theme)
|
||||||
|
|
||||||
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme), enableMediaAccessoryPanel: true)
|
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme), enableMediaAccessoryPanel: true, locationBroadcastPanelSource: .summary)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ final class HashtagSearchController: TelegramController {
|
|||||||
|
|
||||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme), enableMediaAccessoryPanel: true)
|
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme), enableMediaAccessoryPanel: true, locationBroadcastPanelSource: .none)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||||
|
|
||||||
|
|||||||
@@ -213,4 +213,13 @@ public final class LiveLocationManager {
|
|||||||
self.pollingOnceValue = true
|
self.pollingOnceValue = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func internalMessageForPeerId(_ peerId: PeerId) -> MessageId? {
|
||||||
|
for id in self.broadcastToMessageIds {
|
||||||
|
if id.peerId == peerId {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ private final class LiveLocationPeerSummaryContext {
|
|||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
private let becameEmpty: () -> Void
|
private let becameEmpty: () -> Void
|
||||||
|
|
||||||
private var peers: [Peer] = [] {
|
private var peers: [Peer]? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ private final class LiveLocationPeerSummaryContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var subscribers = Bag<([Peer]) -> Void>()
|
private var subscribers = Bag<([Peer]?) -> Void>()
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
return !self.isActive && self.subscribers.isEmpty
|
return !self.isActive && self.subscribers.isEmpty
|
||||||
@@ -126,7 +126,7 @@ private final class LiveLocationPeerSummaryContext {
|
|||||||
self.peerDisposable.dispose()
|
self.peerDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe(_ f: @escaping ([Peer]) -> Void) -> Disposable {
|
func subscribe(_ f: @escaping ([Peer]?) -> Void) -> Disposable {
|
||||||
let index = self.subscribers.add({ next in
|
let index = self.subscribers.add({ next in
|
||||||
f(next)
|
f(next)
|
||||||
})
|
})
|
||||||
@@ -165,7 +165,7 @@ private final class LiveLocationPeerSummaryContext {
|
|||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
self.peerDisposable.set(nil)
|
self.peerDisposable.set(nil)
|
||||||
self.peers = []
|
self.peers = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,21 +195,6 @@ final class LiveLocationSummaryManager {
|
|||||||
peerIds.insert(id.peerId)
|
peerIds.insert(id.peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
var removedPeerIds: [PeerId] = []
|
|
||||||
for peerId in self.peerContexts.keys {
|
|
||||||
if !peerIds.contains(peerId) {
|
|
||||||
removedPeerIds.append(peerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for peerId in removedPeerIds {
|
|
||||||
if let _ = self.peerContexts[peerId] {
|
|
||||||
self.peerContexts.removeValue(forKey: peerId)
|
|
||||||
} else {
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for peerId in peerIds {
|
for peerId in peerIds {
|
||||||
if self.peerContexts[peerId] == nil {
|
if self.peerContexts[peerId] == nil {
|
||||||
let context = LiveLocationPeerSummaryContext(queue: self.queue, accountPeerId: self.accountPeerId, viewTracker: self.viewTracker, peerId: peerId, becameEmpty: { [weak self] in
|
let context = LiveLocationPeerSummaryContext(queue: self.queue, accountPeerId: self.accountPeerId, viewTracker: self.viewTracker, peerId: peerId, becameEmpty: { [weak self] in
|
||||||
@@ -232,7 +217,7 @@ final class LiveLocationSummaryManager {
|
|||||||
return self.globalContext.subscribe()
|
return self.globalContext.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
func peersBroadcastingTo(peerId: PeerId) -> Signal<[Peer], NoError> {
|
func peersBroadcastingTo(peerId: PeerId) -> Signal<[Peer]?, NoError> {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
return Signal { [weak self] subscriber in
|
return Signal { [weak self] subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
@@ -247,6 +232,7 @@ final class LiveLocationSummaryManager {
|
|||||||
strongSelf.peerContexts.removeValue(forKey: peerId)
|
strongSelf.peerContexts.removeValue(forKey: peerId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
strongSelf.peerContexts[peerId] = context
|
||||||
}
|
}
|
||||||
|
|
||||||
disposable.set(context.subscribe({ next in
|
disposable.set(context.subscribe({ next in
|
||||||
|
|||||||
184
TelegramUI/LocationBroadcastNavigationAccessoryPanel.swift
Normal file
184
TelegramUI/LocationBroadcastNavigationAccessoryPanel.swift
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import Foundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
|
||||||
|
private let titleFont = Font.regular(12.0)
|
||||||
|
private let subtitleFont = Font.regular(10.0)
|
||||||
|
|
||||||
|
enum LocationBroadcastNavigationAccessoryPanelMode {
|
||||||
|
case summary
|
||||||
|
case peer
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
|
||||||
|
private var theme: PresentationTheme
|
||||||
|
private var strings: PresentationStrings
|
||||||
|
|
||||||
|
private let tapAction: () -> Void
|
||||||
|
private let close: () -> Void
|
||||||
|
|
||||||
|
private let contentNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let iconNode: ASImageNode
|
||||||
|
private let wavesNode: LocationBroadcastPanelWavesNode
|
||||||
|
private let titleNode: TextNode
|
||||||
|
private let subtitleNode: TextNode
|
||||||
|
private let closeButton: HighlightableButtonNode
|
||||||
|
private let separatorNode: ASDisplayNode
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
private var peersAndMode: ([Peer], LocationBroadcastNavigationAccessoryPanelMode)?
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, strings: PresentationStrings, tapAction: @escaping () -> Void, close: @escaping () -> Void) {
|
||||||
|
self.theme = theme
|
||||||
|
self.strings = strings
|
||||||
|
|
||||||
|
self.tapAction = tapAction
|
||||||
|
self.close = close
|
||||||
|
|
||||||
|
self.contentNode = ASDisplayNode()
|
||||||
|
self.contentNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
|
||||||
|
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode.isLayerBacked = true
|
||||||
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
self.iconNode.displaysAsynchronously = false
|
||||||
|
self.iconNode.image = PresentationResourcesRootController.navigationLiveLocationIcon(self.theme)
|
||||||
|
|
||||||
|
self.wavesNode = LocationBroadcastPanelWavesNode(color: self.theme.rootController.navigationBar.accentTextColor)
|
||||||
|
|
||||||
|
self.titleNode = TextNode()
|
||||||
|
self.titleNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.subtitleNode = TextNode()
|
||||||
|
self.subtitleNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.closeButton = HighlightableButtonNode()
|
||||||
|
self.closeButton.setImage(PresentationResourcesRootController.navigationPlayerCloseButton(self.theme), for: [])
|
||||||
|
self.closeButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -8.0, -8.0, -8.0)
|
||||||
|
self.closeButton.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.separatorNode = ASDisplayNode()
|
||||||
|
self.separatorNode.isLayerBacked = true
|
||||||
|
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.contentNode)
|
||||||
|
|
||||||
|
self.contentNode.addSubnode(self.iconNode)
|
||||||
|
self.contentNode.addSubnode(self.wavesNode)
|
||||||
|
self.contentNode.addSubnode(self.titleNode)
|
||||||
|
self.contentNode.addSubnode(self.subtitleNode)
|
||||||
|
self.contentNode.addSubnode(self.closeButton)
|
||||||
|
self.contentNode.addSubnode(self.separatorNode)
|
||||||
|
|
||||||
|
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||||
|
self.view.addGestureRecognizer(tapRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let titleString = NSAttributedString(string: self.strings.Conversation_LiveLocation, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
||||||
|
var subtitleString: NSAttributedString?
|
||||||
|
if let (peers, mode) = self.peersAndMode {
|
||||||
|
switch mode {
|
||||||
|
case .summary:
|
||||||
|
let text: String
|
||||||
|
if peers.count == 1 {
|
||||||
|
text = self.strings.DialogList_LiveLocationSharingTo(peers[0].displayTitle).0
|
||||||
|
} else {
|
||||||
|
text = self.strings.DialogList_LiveLocationChatsCount(Int32(peers.count))
|
||||||
|
}
|
||||||
|
subtitleString = NSAttributedString(string: text, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
case .peer:
|
||||||
|
if peers.count == 0 {
|
||||||
|
subtitleString = NSAttributedString(string: self.strings.Conversation_LiveLocationYou, font: subtitleFont, textColor: self.theme.rootController.navigationBar.accentTextColor)
|
||||||
|
} else {
|
||||||
|
let otherString: String
|
||||||
|
if peers.count == 1 {
|
||||||
|
otherString = peers[0].compactDisplayTitle
|
||||||
|
} else {
|
||||||
|
otherString = self.strings.Conversation_LiveLocationMembersCount(Int32(peers.count))
|
||||||
|
}
|
||||||
|
let rawText = self.strings.Conversation_LiveLocationYouAnd(otherString).0.replacingOccurrences(of: "*", with: "**")
|
||||||
|
let body = MarkdownAttributeSet(font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
let accent = MarkdownAttributeSet(font: subtitleFont, textColor: self.theme.rootController.navigationBar.accentTextColor)
|
||||||
|
subtitleString = parseMarkdownIntoAttributedString(rawText, attributes: MarkdownAttributes(body: body, bold: accent, link: body, linkAttribute: { _ in nil }))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let _ = titleApply()
|
||||||
|
let _ = subtitleApply()
|
||||||
|
|
||||||
|
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
|
||||||
|
|
||||||
|
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size)
|
||||||
|
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size)
|
||||||
|
|
||||||
|
if let image = self.iconNode.image {
|
||||||
|
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: 7.0, y: 8.0), size: image.size))
|
||||||
|
transition.updateFrame(node: self.wavesNode, frame: CGRect(origin: CGPoint(x: -2.0, y: -4.0), size: CGSize(width: 48.0, height: 48.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame)
|
||||||
|
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)
|
||||||
|
|
||||||
|
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||||
|
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width, y: minimizedTitleFrame.minY + 8.0), size: closeButtonSize))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(peers: [Peer], mode: LocationBroadcastNavigationAccessoryPanelMode) {
|
||||||
|
self.peersAndMode = (peers, mode)
|
||||||
|
if let size = validLayout {
|
||||||
|
self.updateLayout(size: size, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn(_ transition: ContainedViewLayoutTransition) {
|
||||||
|
self.clipsToBounds = true
|
||||||
|
let contentPosition = self.contentNode.layer.position
|
||||||
|
transition.animatePosition(node: self.contentNode, from: CGPoint(x: contentPosition.x, y: contentPosition.y - 37.0), completion: { [weak self] _ in
|
||||||
|
self?.clipsToBounds = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||||
|
self.clipsToBounds = true
|
||||||
|
let contentPosition = self.contentNode.layer.position
|
||||||
|
transition.animatePosition(node: self.contentNode, to: CGPoint(x: contentPosition.x, y: contentPosition.y - 37.0), removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
self?.clipsToBounds = false
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func closePressed() {
|
||||||
|
self.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
self.tapAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
TelegramUI/LocationBroadcastPanelWavesNode.swift
Normal file
129
TelegramUI/LocationBroadcastPanelWavesNode.swift
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
|
import LegacyComponents
|
||||||
|
|
||||||
|
private final class LocationBroadcastPanelWavesNodeParams: NSObject {
|
||||||
|
let color: UIColor
|
||||||
|
let progress: CGFloat
|
||||||
|
|
||||||
|
init(color: UIColor, progress: CGFloat) {
|
||||||
|
self.color = color
|
||||||
|
self.progress = progress
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func degToRad(_ degrees: CGFloat) -> CGFloat {
|
||||||
|
return degrees * CGFloat.pi / 180.0
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
||||||
|
private var color: UIColor
|
||||||
|
|
||||||
|
private var effectiveProgress: CGFloat = 0.0 {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(color: UIColor) {
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.isLayerBacked = true
|
||||||
|
self.isOpaque = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func willEnterHierarchy() {
|
||||||
|
super.willEnterHierarchy()
|
||||||
|
|
||||||
|
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||||
|
|
||||||
|
let animation = POPBasicAnimation()
|
||||||
|
animation.property = POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
||||||
|
property?.readBlock = { node, values in
|
||||||
|
values?.pointee = (node as! LocationBroadcastPanelWavesNode).effectiveProgress
|
||||||
|
}
|
||||||
|
property?.writeBlock = { node, values in
|
||||||
|
(node as! LocationBroadcastPanelWavesNode).effectiveProgress = values!.pointee
|
||||||
|
}
|
||||||
|
property?.threshold = 0.01
|
||||||
|
}) as! POPAnimatableProperty
|
||||||
|
animation.fromValue = CGFloat(0.0) as NSNumber
|
||||||
|
animation.toValue = CGFloat(1.0) as NSNumber
|
||||||
|
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
||||||
|
animation.duration = 2.5
|
||||||
|
animation.repeatForever = true
|
||||||
|
self.pop_add(animation, forKey: "indefiniteProgress")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didExitHierarchy() {
|
||||||
|
super.didExitHierarchy()
|
||||||
|
|
||||||
|
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
|
let t = CACurrentMediaTime()
|
||||||
|
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
|
||||||
|
return LocationBroadcastPanelWavesNodeParams(color: self.color, progress: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
|
let context = UIGraphicsGetCurrentContext()!
|
||||||
|
|
||||||
|
if !isRasterizing {
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let parameters = parameters as? LocationBroadcastPanelWavesNodeParams {
|
||||||
|
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||||
|
let length: CGFloat = 9.0
|
||||||
|
|
||||||
|
context.setFillColor(parameters.color.cgColor)
|
||||||
|
|
||||||
|
let draw: (CGContext, CGFloat, Bool) -> Void = { context, pos, right in
|
||||||
|
let path = CGMutablePath()
|
||||||
|
|
||||||
|
path.addArc(center: center, radius: length * pos + 7.0, startAngle: right ? degToRad(-26.0) : degToRad(154.0), endAngle: right ? degToRad(26.0) : degToRad(206.0), clockwise: false)
|
||||||
|
|
||||||
|
let strokedArc = path.copy(strokingWithWidth: 1.65, lineCap: .round, lineJoin: .miter, miterLimit: 10.0)
|
||||||
|
|
||||||
|
context.addPath(strokedArc)
|
||||||
|
|
||||||
|
context.fillPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
let position = parameters.progress
|
||||||
|
var alpha = position / 0.5
|
||||||
|
if alpha > 1.0 {
|
||||||
|
alpha = 2.0 - alpha
|
||||||
|
}
|
||||||
|
context.setAlpha(alpha * 0.7)
|
||||||
|
|
||||||
|
draw(context, position, false)
|
||||||
|
draw(context, position, true)
|
||||||
|
|
||||||
|
var progress = parameters.progress + 0.5
|
||||||
|
if progress > 1.0 {
|
||||||
|
progress = progress - 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let largerPos = progress
|
||||||
|
var largerAlpha = largerPos / 0.5
|
||||||
|
if largerAlpha > 1.0 {
|
||||||
|
largerAlpha = 2.0 - largerAlpha
|
||||||
|
}
|
||||||
|
context.setAlpha(largerAlpha * 0.7)
|
||||||
|
|
||||||
|
draw(context, largerPos, false)
|
||||||
|
draw(context, largerPos, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@ import SwiftSignalKit
|
|||||||
|
|
||||||
private let titleFont = Font.regular(12.0)
|
private let titleFont = Font.regular(12.0)
|
||||||
private let subtitleFont = Font.regular(10.0)
|
private let subtitleFont = Font.regular(10.0)
|
||||||
private let maximizedTitleFont = Font.bold(17.0)
|
|
||||||
private let maximizedSubtitleFont = Font.regular(12.0)
|
|
||||||
|
|
||||||
final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
|
final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
|
||||||
static let minimizedHeight: CGFloat = 37.0
|
static let minimizedHeight: CGFloat = 37.0
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ func openChatMessage(account: Account, message: Message, reverseMessageGalleryOr
|
|||||||
present(legacyLocationController(message: message, mapMedia: mapMedia, account: account, openPeer: { peer in
|
present(legacyLocationController(message: message, mapMedia: mapMedia, account: account, openPeer: { peer in
|
||||||
openPeer(peer, .info)
|
openPeer(peer, .info)
|
||||||
}, sendLiveLocation: { coordinate, period in
|
}, sendLiveLocation: { coordinate, period in
|
||||||
|
let outMessage: EnqueueMessage = .message(text: "", attributes: [], media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period), replyToMessageId: nil, localGroupingKey: nil)
|
||||||
|
let _ = enqueueMessages(account: account, peerId: message.id.peerId, messages: [outMessage]).start()
|
||||||
}, stopLiveLocation: {
|
}, stopLiveLocation: {
|
||||||
account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: message.id.peerId)
|
account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: message.id.peerId)
|
||||||
}), nil)
|
}), nil)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
self.interfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
self.interfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||||
|
|
||||||
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), enableMediaAccessoryPanel: true)
|
super.init(account: account, navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), enableMediaAccessoryPanel: true, locationBroadcastPanelSource: .none)
|
||||||
|
|
||||||
self.title = self.presentationData.strings.SharedMedia_TitleAll
|
self.title = self.presentationData.strings.SharedMedia_TitleAll
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ enum PresentationResourceKey: Int32 {
|
|||||||
case navigationSearchIcon
|
case navigationSearchIcon
|
||||||
case navigationPlayerCloseButton
|
case navigationPlayerCloseButton
|
||||||
|
|
||||||
|
case navigationLiveLocationIcon
|
||||||
|
|
||||||
case navigationPlayerPlayIcon
|
case navigationPlayerPlayIcon
|
||||||
case navigationPlayerPauseIcon
|
case navigationPlayerPauseIcon
|
||||||
case navigationPlayerMaximizedPlayIcon
|
case navigationPlayerMaximizedPlayIcon
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ struct PresentationResourcesRootController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func navigationLiveLocationIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.navigationLiveLocationIcon.rawValue, { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.rootController.navigationBar.accentTextColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static func navigationPlayerMaximizedPlayIcon(_ theme: PresentationTheme) -> UIImage? {
|
static func navigationPlayerMaximizedPlayIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.navigationPlayerMaximizedPlayIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.navigationPlayerMaximizedPlayIcon.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Play"), color: theme.rootController.navigationBar.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Play"), color: theme.rootController.navigationBar.primaryTextColor)
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ public enum PresentationThemeParsingError: Error {
|
|||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class PresentationThemeColorPlaceholder {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public final class PresentationThemeRootTabBar {
|
public final class PresentationThemeRootTabBar {
|
||||||
public let backgroundColor: UIColor
|
public let backgroundColor: UIColor
|
||||||
public let separatorColor: UIColor
|
public let separatorColor: UIColor
|
||||||
|
|||||||
@@ -4,16 +4,52 @@ import TelegramCore
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
|
enum LocationBroadcastPanelSource {
|
||||||
|
case none
|
||||||
|
case summary
|
||||||
|
case peer(PeerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentLiveLocationController(account: Account, peerId: PeerId, controller: ViewController) {
|
||||||
|
if let id = account.telegramApplicationContext.liveLocationManager?.internalMessageForPeerId(peerId) {
|
||||||
|
let _ = (account.postbox.modify { modifier -> Message? in
|
||||||
|
return modifier.getMessage(id)
|
||||||
|
} |> deliverOnMainQueue).start(next: { [weak controller] message in
|
||||||
|
if let message = message, let strongController = controller {
|
||||||
|
let _ = openChatMessage(account: account, message: message, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, dismissInput: {
|
||||||
|
controller?.view.endEditing(true)
|
||||||
|
}, present: { c, a in
|
||||||
|
controller?.present(c, in: .window(.root), with: a)
|
||||||
|
}, transitionNode: { _, _ in
|
||||||
|
return nil
|
||||||
|
}, addToTransitionSurface: { _ in
|
||||||
|
}, openUrl: { _ in
|
||||||
|
}, openPeer: { peer, navigation in
|
||||||
|
}, callPeer: { _ in
|
||||||
|
}, sendSticker: { _ in
|
||||||
|
}, setupTemporaryHiddenMedia: { _, _, _ in
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class TelegramController: ViewController {
|
public class TelegramController: ViewController {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
|
|
||||||
let enableMediaAccessoryPanel: Bool
|
let enableMediaAccessoryPanel: Bool
|
||||||
|
let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||||
|
|
||||||
private var mediaStatusDisposable: Disposable?
|
private var mediaStatusDisposable: Disposable?
|
||||||
|
private var locationBroadcastDisposable: Disposable?
|
||||||
|
|
||||||
private(set) var playlistStateAndType: (SharedMediaPlaylistItem, MusicPlaybackSettingsOrder, MediaManagerPlayerType)?
|
private(set) var playlistStateAndType: (SharedMediaPlaylistItem, MusicPlaybackSettingsOrder, MediaManagerPlayerType)?
|
||||||
private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
||||||
|
|
||||||
|
private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode?
|
||||||
|
private var locationBroadcastPeers: [Peer]?
|
||||||
|
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
||||||
|
|
||||||
private var dismissingPanel: ASDisplayNode?
|
private var dismissingPanel: ASDisplayNode?
|
||||||
|
|
||||||
override public var navigationHeight: CGFloat {
|
override public var navigationHeight: CGFloat {
|
||||||
@@ -21,17 +57,21 @@ public class TelegramController: ViewController {
|
|||||||
if let _ = self.mediaAccessoryPanel {
|
if let _ = self.mediaAccessoryPanel {
|
||||||
height += 36.0
|
height += 36.0
|
||||||
}
|
}
|
||||||
|
if let _ = self.locationBroadcastAccessoryPanel {
|
||||||
|
height += 36.0
|
||||||
|
}
|
||||||
return height
|
return height
|
||||||
}
|
}
|
||||||
|
|
||||||
init(account: Account, navigationBarTheme: NavigationBarTheme?, enableMediaAccessoryPanel: Bool) {
|
init(account: Account, navigationBarTheme: NavigationBarTheme?, enableMediaAccessoryPanel: Bool, locationBroadcastPanelSource: LocationBroadcastPanelSource) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.enableMediaAccessoryPanel = enableMediaAccessoryPanel
|
self.enableMediaAccessoryPanel = enableMediaAccessoryPanel
|
||||||
|
self.locationBroadcastPanelSource = locationBroadcastPanelSource
|
||||||
|
|
||||||
super.init(navigationBarTheme: navigationBarTheme)
|
super.init(navigationBarTheme: navigationBarTheme)
|
||||||
|
|
||||||
if let applicationContext = account.applicationContext as? TelegramApplicationContext {
|
if enableMediaAccessoryPanel {
|
||||||
self.mediaStatusDisposable = (applicationContext.mediaManager.globalMediaPlayerState
|
self.mediaStatusDisposable = (account.telegramApplicationContext.mediaManager.globalMediaPlayerState
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in
|
|> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in
|
||||||
if let strongSelf = self, strongSelf.enableMediaAccessoryPanel {
|
if let strongSelf = self, strongSelf.enableMediaAccessoryPanel {
|
||||||
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.0.item) ||
|
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.0.item) ||
|
||||||
@@ -46,10 +86,52 @@ public class TelegramController: ViewController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let liveLocationManager = account.telegramApplicationContext.liveLocationManager {
|
||||||
|
switch locationBroadcastPanelSource {
|
||||||
|
case .none:
|
||||||
|
self.locationBroadcastMode = nil
|
||||||
|
case .summary, .peer:
|
||||||
|
let signal: Signal<[Peer]?, NoError>
|
||||||
|
switch locationBroadcastPanelSource {
|
||||||
|
case let .peer(peerId):
|
||||||
|
self.locationBroadcastMode = .peer
|
||||||
|
signal = liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId)
|
||||||
|
default:
|
||||||
|
self.locationBroadcastMode = .summary
|
||||||
|
signal = liveLocationManager.summaryManager.broadcastingToPeers()
|
||||||
|
|> map { $0.isEmpty ? nil : $0 }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
self.locationBroadcastDisposable = (signal
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
|
if let strongSelf = self {
|
||||||
|
var updated = false
|
||||||
|
if let current = strongSelf.locationBroadcastPeers, let peers = peers {
|
||||||
|
updated = !arePeerArraysEqual(current, peers)
|
||||||
|
} else if (strongSelf.locationBroadcastPeers != nil) != (peers != nil) {
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
let wasEmpty = strongSelf.locationBroadcastPeers == nil
|
||||||
|
strongSelf.locationBroadcastPeers = peers
|
||||||
|
if wasEmpty != (peers == nil) {
|
||||||
|
strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
} else if let peers = peers, let locationBroadcastMode = strongSelf.locationBroadcastMode {
|
||||||
|
strongSelf.locationBroadcastAccessoryPanel?.update(peers: peers, mode: locationBroadcastMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.mediaStatusDisposable?.dispose()
|
self.mediaStatusDisposable?.dispose()
|
||||||
|
self.locationBroadcastDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
@@ -59,10 +141,148 @@ public class TelegramController: ViewController {
|
|||||||
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
if let (item, _, type) = self.playlistStateAndType {
|
|
||||||
let navigationHeight = super.navigationHeight
|
let navigationHeight = super.navigationHeight
|
||||||
|
|
||||||
|
var additionalHeight: CGFloat = 0.0
|
||||||
|
|
||||||
|
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
|
||||||
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
|
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||||
|
additionalHeight += panelHeight
|
||||||
|
|
||||||
|
let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel
|
||||||
|
if let current = self.locationBroadcastAccessoryPanel {
|
||||||
|
locationBroadcastAccessoryPanel = current
|
||||||
|
transition.updateFrame(node: locationBroadcastAccessoryPanel, frame: panelFrame)
|
||||||
|
locationBroadcastAccessoryPanel.updateLayout(size: panelFrame.size, transition: transition)
|
||||||
|
} else {
|
||||||
|
let presentationData = self.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
locationBroadcastAccessoryPanel = LocationBroadcastNavigationAccessoryPanel(theme: presentationData.theme, strings: presentationData.strings, tapAction: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
switch strongSelf.locationBroadcastPanelSource {
|
||||||
|
case .none:
|
||||||
|
break
|
||||||
|
case .summary:
|
||||||
|
if let locationBroadcastPeers = strongSelf.locationBroadcastPeers {
|
||||||
|
if locationBroadcastPeers.count == 1 {
|
||||||
|
presentLiveLocationController(account: strongSelf.account, peerId: locationBroadcastPeers[0].id, controller: strongSelf)
|
||||||
|
} else {
|
||||||
|
let presentationData = strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
var items: [ActionSheetItem] = []
|
||||||
|
if !locationBroadcastPeers.isEmpty {
|
||||||
|
items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(locationBroadcastPeers.count))))
|
||||||
|
for peer in locationBroadcastPeers {
|
||||||
|
items.append(ActionSheetButtonItem(title: peer.displayTitle, action: {
|
||||||
|
dismissAction()
|
||||||
|
if let strongSelf = self {
|
||||||
|
presentLiveLocationController(account: strongSelf.account, peerId: peer.id, controller: strongSelf)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: {
|
||||||
|
dismissAction()
|
||||||
|
for peer in locationBroadcastPeers {
|
||||||
|
self?.account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: peer.id)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: items),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
strongSelf.view.endEditing(true)
|
||||||
|
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .peer(peerId):
|
||||||
|
presentLiveLocationController(account: strongSelf.account, peerId: peerId, controller: strongSelf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, close: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
var closePeers: [Peer]?
|
||||||
|
var closePeerId: PeerId?
|
||||||
|
switch strongSelf.locationBroadcastPanelSource {
|
||||||
|
case .none:
|
||||||
|
break
|
||||||
|
case .summary:
|
||||||
|
if let locationBroadcastPeers = strongSelf.locationBroadcastPeers {
|
||||||
|
if locationBroadcastPeers.count > 1 {
|
||||||
|
closePeers = locationBroadcastPeers
|
||||||
|
} else {
|
||||||
|
closePeerId = locationBroadcastPeers.first?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .peer(peerId):
|
||||||
|
closePeerId = peerId
|
||||||
|
}
|
||||||
|
let presentationData = strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
}
|
||||||
|
var items: [ActionSheetItem] = []
|
||||||
|
if let closePeers = closePeers, !closePeers.isEmpty {
|
||||||
|
items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(closePeers.count))))
|
||||||
|
for peer in closePeers {
|
||||||
|
items.append(ActionSheetButtonItem(title: peer.displayTitle, action: {
|
||||||
|
dismissAction()
|
||||||
|
if let strongSelf = self {
|
||||||
|
presentLiveLocationController(account: strongSelf.account, peerId: peer.id, controller: strongSelf)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: {
|
||||||
|
dismissAction()
|
||||||
|
for peer in closePeers {
|
||||||
|
self?.account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: peer.id)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else if let closePeerId = closePeerId {
|
||||||
|
items.append(ActionSheetButtonItem(title: presentationData.strings.Map_StopLiveLocation, color: .destructive, action: {
|
||||||
|
dismissAction()
|
||||||
|
self?.account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: closePeerId)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: items),
|
||||||
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||||
|
])
|
||||||
|
strongSelf.view.endEditing(true)
|
||||||
|
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if let navigationBar = self.navigationBar {
|
||||||
|
self.displayNode.insertSubnode(locationBroadcastAccessoryPanel, aboveSubnode: navigationBar)
|
||||||
|
} else {
|
||||||
|
self.displayNode.addSubnode(locationBroadcastAccessoryPanel)
|
||||||
|
}
|
||||||
|
self.locationBroadcastAccessoryPanel = locationBroadcastAccessoryPanel
|
||||||
|
locationBroadcastAccessoryPanel.frame = panelFrame
|
||||||
|
locationBroadcastAccessoryPanel.update(peers: locationBroadcastPeers, mode: locationBroadcastMode)
|
||||||
|
locationBroadcastAccessoryPanel.updateLayout(size: panelFrame.size, transition: .immediate)
|
||||||
|
if transition.isAnimated {
|
||||||
|
locationBroadcastAccessoryPanel.animateIn(transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let locationBroadcastAccessoryPanel = self.locationBroadcastAccessoryPanel {
|
||||||
|
self.locationBroadcastAccessoryPanel = nil
|
||||||
|
if transition.isAnimated {
|
||||||
|
locationBroadcastAccessoryPanel.animateOut(transition, completion: { [weak locationBroadcastAccessoryPanel] in
|
||||||
|
locationBroadcastAccessoryPanel?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
locationBroadcastAccessoryPanel.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (item, _, type) = self.playlistStateAndType {
|
||||||
|
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||||
|
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||||
if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type {
|
if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type {
|
||||||
transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame)
|
transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame)
|
||||||
mediaAccessoryPanel.updateLayout(size: panelFrame.size, transition: transition)
|
mediaAccessoryPanel.updateLayout(size: panelFrame.size, transition: transition)
|
||||||
|
|||||||
Submodule submodules/libtgvoip updated: dd43702ee0...6fff5b379d
Reference in New Issue
Block a user