mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Bot peer request support
Launch screen icon
This commit is contained in:
parent
58e641230a
commit
930e237bf1
@ -1,19 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
<view contentMode="scaleToFill" interfaceStyle="light" id="O8c-13-3vw">
|
<view contentMode="scaleToFill" id="O8c-13-3vw">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Chat/Links/QrLogo" translatesAutoresizingMaskIntoConstraints="NO" id="Ra6-Gz-QsF">
|
||||||
|
<rect key="frame" x="147" y="388" width="120" height="120"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="120" id="Mhj-0F-KYG"/>
|
||||||
|
<constraint firstAttribute="height" constant="120" id="gdP-J3-bQE"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Ra6-Gz-QsF" firstAttribute="centerY" secondItem="O8c-13-3vw" secondAttribute="centerY" id="54l-Rw-Siu"/>
|
||||||
|
<constraint firstItem="Ra6-Gz-QsF" firstAttribute="centerX" secondItem="O8c-13-3vw" secondAttribute="centerX" id="zdX-Zd-oe6"/>
|
||||||
|
</constraints>
|
||||||
<point key="canvasLocation" x="139" y="117"/>
|
<point key="canvasLocation" x="139" y="117"/>
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="Chat/Links/QrLogo" width="72" height="72"/>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -8624,3 +8624,69 @@ Sorry for the inconvenience.";
|
|||||||
"StorageManagement.OpenFile" = "Open File";
|
"StorageManagement.OpenFile" = "Open File";
|
||||||
|
|
||||||
"ChatListFilter.AddChatsSearchPlaceholder" = "Search Chats";
|
"ChatListFilter.AddChatsSearchPlaceholder" = "Search Chats";
|
||||||
|
|
||||||
|
"RequestPeer.ChooseUserTitle" = "Choose User";
|
||||||
|
"RequestPeer.ChooseBotTitle" = "Choose Bot";
|
||||||
|
"RequestPeer.ChooseGroupTitle" = "Choose Group";
|
||||||
|
"RequestPeer.ChooseChannelTitle" = "Choose Channel";
|
||||||
|
"RequestPeer.Requirements" = "Requirements:";
|
||||||
|
|
||||||
|
"RequestPeer.CreateNewGroup" = "Create a New Group for This";
|
||||||
|
"RequestPeer.CreateNewChannel" = "Create a New Channel for This";
|
||||||
|
|
||||||
|
"RequestPeer.UsersEmpty" = "You don't have users that meet the following requirements:";
|
||||||
|
"RequestPeer.UsersAllEmpty" = "You don't have any users.";
|
||||||
|
"RequestPeer.BotsAllEmpty" = "You don't have any bots.";
|
||||||
|
"RequestPeer.GroupsEmpty" = "You don't have groups that meet the following requirements:";
|
||||||
|
"RequestPeer.GroupsAllEmpty" = "You don't have any groups.";
|
||||||
|
"RequestPeer.ChannelsEmpty" = "You don't have channels that meet the following requirements:";
|
||||||
|
"RequestPeer.ChannelsAllEmpty" = "You don't have any channels.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.UserPremiumOff" = "User should not have a Premium subscription.";
|
||||||
|
"RequestPeer.Requirement.UserPremiumOn" = "User should have a Premium subscription.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Group.HasUsernameOff" = "The group should be private.";
|
||||||
|
"RequestPeer.Requirement.Group.HasUsernameOn" = "The group should be public.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Group.ForumOff" = "The group should have topics off.";
|
||||||
|
"RequestPeer.Requirement.Group.ForumOn" = "The group should have topics on.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Group.CreatorOn" = "You should be the owner of the group.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Group.Rights" = "You have the admin rights to %@.";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Info" = "change group info";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Send" = "post messages";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Delete" = "delete messages";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Edit" = "delete messages";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Ban" = "ban users";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Invite" = "invite users via link";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Pin" = "pin messages";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Topics" = "manage topics";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.VideoChats" = "manage video chats";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.Anonymous" = "remain anonymous";
|
||||||
|
"RequestPeer.Requirement.Group.Rights.AddAdmins" = "add new admins";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Channel.HasUsernameOff" = "The channel should be private.";
|
||||||
|
"RequestPeer.Requirement.Channel.HasUsernameOn" = "The channel should be public.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Channel.CreatorOn" = "You should be the owner of the channel.";
|
||||||
|
|
||||||
|
"RequestPeer.Requirement.Channel.Rights" = "You have the admin rights to %@.";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Info" = "change group info";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Send" = "post messages";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Delete" = "delete messages";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Edit" = "delete messages";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Ban" = "ban users";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Invite" = "invite users via link";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Pin" = "pin messages";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Topics" = "manage topics";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.VideoChats" = "manage video chats";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.Anonymous" = "remain anonymous";
|
||||||
|
"RequestPeer.Requirement.Channel.Rights.AddAdmins" = "add new admins";
|
||||||
|
|
||||||
|
"CreateGroup.PublicLinkTitle" = "SET A PUBLIC LINK";
|
||||||
|
"CreateGroup.PublicLinkInfo" = "You can use **a-z**, **0-9** and underscores. Minimum length is **5** characters.";
|
||||||
|
|
||||||
|
"Notification.RequestedPeer" = "You shared %@ with the bot.";
|
||||||
|
|
||||||
|
"Conversation.ViewInChannel" = "View in Channel";
|
||||||
|
@ -719,6 +719,7 @@ public enum CreateGroupMode {
|
|||||||
case generic
|
case generic
|
||||||
case supergroup
|
case supergroup
|
||||||
case locatedGroup(latitude: Double, longitude: Double, address: String?)
|
case locatedGroup(latitude: Double, longitude: Double, address: String?)
|
||||||
|
case requestPeer(ReplyMarkupButtonRequestPeerType.Group)
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol AppLockContext: AnyObject {
|
public protocol AppLockContext: AnyObject {
|
||||||
|
@ -41,6 +41,7 @@ public final class PeerSelectionControllerParams {
|
|||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
public let filter: ChatListNodePeersFilter
|
public let filter: ChatListNodePeersFilter
|
||||||
|
public let requestPeerType: ReplyMarkupButtonRequestPeerType?
|
||||||
public let forumPeerId: EnginePeer.Id?
|
public let forumPeerId: EnginePeer.Id?
|
||||||
public let hasChatListSelector: Bool
|
public let hasChatListSelector: Bool
|
||||||
public let hasContactSelector: Bool
|
public let hasContactSelector: Bool
|
||||||
@ -54,10 +55,11 @@ public final class PeerSelectionControllerParams {
|
|||||||
public let hasTypeHeaders: Bool
|
public let hasTypeHeaders: Bool
|
||||||
public let selectForumThreads: Bool
|
public let selectForumThreads: Bool
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter = [.onlyWriteable], forumPeerId: EnginePeer.Id? = nil, hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer, Int64?) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false) {
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter = [.onlyWriteable], requestPeerType: ReplyMarkupButtonRequestPeerType? = nil, forumPeerId: EnginePeer.Id? = nil, hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer, Int64?) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
|
self.requestPeerType = requestPeerType
|
||||||
self.forumPeerId = forumPeerId
|
self.forumPeerId = forumPeerId
|
||||||
self.hasChatListSelector = hasChatListSelector
|
self.hasChatListSelector = hasChatListSelector
|
||||||
self.hasContactSelector = hasContactSelector
|
self.hasContactSelector = hasContactSelector
|
||||||
|
@ -1117,8 +1117,11 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
|||||||
let wasEmpty = self.viewControllers.isEmpty
|
let wasEmpty = self.viewControllers.isEmpty
|
||||||
super.setViewControllers(viewControllers, animated: animated)
|
super.setViewControllers(viewControllers, animated: animated)
|
||||||
if wasEmpty {
|
if wasEmpty {
|
||||||
|
if self.topViewController is AuthorizationSequenceSplashController {
|
||||||
|
} else {
|
||||||
self.topViewController?.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
self.topViewController?.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !self.didSetReady {
|
if !self.didSetReady {
|
||||||
self.didSetReady = true
|
self.didSetReady = true
|
||||||
self._ready.set(.single(true))
|
self._ready.set(.single(true))
|
||||||
@ -1150,7 +1153,13 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func animateIn() {
|
private func animateIn() {
|
||||||
|
if !self.otherAccountPhoneNumbers.1.isEmpty {
|
||||||
self.view.layer.animatePosition(from: CGPoint(x: self.view.layer.position.x, y: self.view.layer.position.y + self.view.layer.bounds.size.height), to: self.view.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
self.view.layer.animatePosition(from: CGPoint(x: self.view.layer.position.x, y: self.view.layer.position.y + self.view.layer.bounds.size.height), to: self.view.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
} else {
|
||||||
|
if let splashController = self.topViewController as? AuthorizationSequenceSplashController {
|
||||||
|
splashController.animateIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func animateOut(completion: (() -> Void)? = nil) {
|
private func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
@ -10,7 +10,7 @@ import LegacyComponents
|
|||||||
import SolidRoundedButtonNode
|
import SolidRoundedButtonNode
|
||||||
import RMIntro
|
import RMIntro
|
||||||
|
|
||||||
final class AuthorizationSequenceSplashController: ViewController {
|
public final class AuthorizationSequenceSplashController: ViewController {
|
||||||
private var controllerNode: AuthorizationSequenceSplashControllerNode {
|
private var controllerNode: AuthorizationSequenceSplashControllerNode {
|
||||||
return self.displayNode as! AuthorizationSequenceSplashControllerNode
|
return self.displayNode as! AuthorizationSequenceSplashControllerNode
|
||||||
}
|
}
|
||||||
@ -107,11 +107,15 @@ final class AuthorizationSequenceSplashController: ViewController {
|
|||||||
self.activateLocalizationDisposable.dispose()
|
self.activateLocalizationDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
public override func loadDisplayNode() {
|
||||||
self.displayNode = AuthorizationSequenceSplashControllerNode(theme: self.theme)
|
self.displayNode = AuthorizationSequenceSplashControllerNode(theme: self.theme)
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
self.controller.animateIn()
|
||||||
|
}
|
||||||
|
|
||||||
var buttonFrame: CGRect {
|
var buttonFrame: CGRect {
|
||||||
return self.startButton.frame
|
return self.startButton.frame
|
||||||
}
|
}
|
||||||
@ -138,31 +142,31 @@ final class AuthorizationSequenceSplashController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
public override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
self.addControllerIfNeeded()
|
self.addControllerIfNeeded()
|
||||||
self.controller.viewWillAppear(false)
|
self.controller.viewWillAppear(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
public override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
controller.viewDidAppear(animated)
|
controller.viewDidAppear(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
public override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
controller.viewWillDisappear(animated)
|
controller.viewWillDisappear(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
public override func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
controller.viewDidDisappear(animated)
|
controller.viewDidDisappear(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
self.validLayout = layout
|
self.validLayout = layout
|
||||||
|
@ -121,8 +121,10 @@ public class ChatListAdditionalCategoryItem: ItemListItem, ListViewItemWithHeade
|
|||||||
}
|
}
|
||||||
} else if let _ = nextItem as? ChatListAdditionalCategoryItem {
|
} else if let _ = nextItem as? ChatListAdditionalCategoryItem {
|
||||||
} else {
|
} else {
|
||||||
|
if let nextItem = nextItem as? ListViewItemWithHeader, nextItem.header != nil {
|
||||||
last = true
|
last = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
last = true
|
last = true
|
||||||
}
|
}
|
||||||
|
@ -1535,7 +1535,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
//filter.insert(.excludeRecent)
|
//filter.insert(.excludeRecent)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: effectiveLocation, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in
|
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, requestPeerType: nil, location: effectiveLocation, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in
|
||||||
self?.requestOpenPeerFromSearch?(peer, threadId, dismissSearch)
|
self?.requestOpenPeerFromSearch?(peer, threadId, dismissSearch)
|
||||||
}, openDisabledPeer: { _, _ in
|
}, openDisabledPeer: { _, _ in
|
||||||
}, openRecentPeerOptions: { [weak self] peer in
|
}, openRecentPeerOptions: { [weak self] peer in
|
||||||
|
@ -86,6 +86,7 @@ private struct ChatListSearchContainerNodeSearchState: Equatable {
|
|||||||
public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peersFilter: ChatListNodePeersFilter
|
private let peersFilter: ChatListNodePeersFilter
|
||||||
|
private let requestPeerType: ReplyMarkupButtonRequestPeerType?
|
||||||
private let location: ChatListControllerLocation
|
private let location: ChatListControllerLocation
|
||||||
private let displaySearchFilters: Bool
|
private let displaySearchFilters: Bool
|
||||||
private let hasDownloads: Bool
|
private let hasDownloads: Bool
|
||||||
@ -135,7 +136,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
||||||
var initialFilter = initialFilter
|
var initialFilter = initialFilter
|
||||||
if case .chats = initialFilter, case .forum = location {
|
if case .chats = initialFilter, case .forum = location {
|
||||||
initialFilter = .topics
|
initialFilter = .topics
|
||||||
@ -143,6 +144,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
|
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peersFilter = filter
|
self.peersFilter = filter
|
||||||
|
self.requestPeerType = requestPeerType
|
||||||
self.location = location
|
self.location = location
|
||||||
self.displaySearchFilters = displaySearchFilters
|
self.displaySearchFilters = displaySearchFilters
|
||||||
self.hasDownloads = hasDownloads
|
self.hasDownloads = hasDownloads
|
||||||
@ -160,7 +162,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
||||||
|
|
||||||
self.filterContainerNode = ChatListSearchFiltersContainerNode()
|
self.filterContainerNode = ChatListSearchFiltersContainerNode()
|
||||||
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, location: location, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
|
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, requestPeerType: self.requestPeerType, location: location, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
|
||||||
self.paneContainerNode.clipsToBounds = true
|
self.paneContainerNode.clipsToBounds = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -933,6 +933,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
private let animationRenderer: MultiAnimationRenderer
|
private let animationRenderer: MultiAnimationRenderer
|
||||||
private let interaction: ChatListSearchInteraction
|
private let interaction: ChatListSearchInteraction
|
||||||
private let peersFilter: ChatListNodePeersFilter
|
private let peersFilter: ChatListNodePeersFilter
|
||||||
|
private let requestPeerType: ReplyMarkupButtonRequestPeerType?
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let key: ChatListSearchPaneKey
|
private let key: ChatListSearchPaneKey
|
||||||
private let tagMask: EngineMessage.Tags?
|
private let tagMask: EngineMessage.Tags?
|
||||||
@ -981,7 +982,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
private let emptyResultsTitleNode: ImmediateTextNode
|
private let emptyResultsTitleNode: ImmediateTextNode
|
||||||
private let emptyResultsTextNode: ImmediateTextNode
|
private let emptyResultsTextNode: ImmediateTextNode
|
||||||
private let emptyResultsAnimationNode: AnimatedStickerNode
|
private let emptyResultsAnimationNode: AnimatedStickerNode
|
||||||
private var emptyResultsAnimationSize: CGSize = CGSize()
|
private var emptyResultsAnimationSize = CGSize()
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
|
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
|
||||||
|
|
||||||
@ -1002,7 +1003,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
|
|
||||||
private var hiddenMediaDisposable: Disposable?
|
private var hiddenMediaDisposable: Disposable?
|
||||||
|
|
||||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.animationCache = animationCache
|
self.animationCache = animationCache
|
||||||
self.animationRenderer = animationRenderer
|
self.animationRenderer = animationRenderer
|
||||||
@ -1018,6 +1019,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
peersFilter.insert(.excludeRecent)
|
peersFilter.insert(.excludeRecent)
|
||||||
}
|
}
|
||||||
self.peersFilter = peersFilter
|
self.peersFilter = peersFilter
|
||||||
|
self.requestPeerType = requestPeerType
|
||||||
|
|
||||||
let tagMask: EngineMessage.Tags?
|
let tagMask: EngineMessage.Tags?
|
||||||
switch key {
|
switch key {
|
||||||
@ -1645,6 +1647,96 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1))
|
let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1))
|
||||||
|
|
||||||
let filteredPeer: (EnginePeer, EnginePeer) -> Bool = { peer, accountPeer in
|
let filteredPeer: (EnginePeer, EnginePeer) -> Bool = { peer, accountPeer in
|
||||||
|
if let peerType = requestPeerType {
|
||||||
|
switch peerType {
|
||||||
|
case let .user(userType):
|
||||||
|
if case let .user(user) = peer {
|
||||||
|
if let isBot = userType.isBot {
|
||||||
|
if isBot != (user.botInfo != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isPremium = userType.isPremium {
|
||||||
|
if isPremium != user.isPremium {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .group(groupType):
|
||||||
|
if case let .legacyGroup(group) = peer {
|
||||||
|
if groupType.isCreator {
|
||||||
|
if case .creator = group.role {
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isForum = groupType.isForum, isForum {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let hasUsername = groupType.hasUsername, hasUsername {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let userAdminRights = groupType.userAdminRights {
|
||||||
|
if case let .admin(rights, _) = group.role {
|
||||||
|
if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if case .member = group.role {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else if case let .channel(channel) = peer, case .group = channel.info {
|
||||||
|
if groupType.isCreator {
|
||||||
|
if !channel.flags.contains(.isCreator) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isForum = groupType.isForum, isForum {
|
||||||
|
if isForum != channel.flags.contains(.isForum) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let hasUsername = groupType.hasUsername, hasUsername {
|
||||||
|
if hasUsername != (channel.addressName != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let userAdminRights = groupType.userAdminRights {
|
||||||
|
if channel.flags.contains(.isCreator) {
|
||||||
|
} else if let rights = channel.adminRights {
|
||||||
|
if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .channel(channelType):
|
||||||
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||||
|
if channelType.isCreator {
|
||||||
|
if !channel.flags.contains(.isCreator) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let hasUsername = channelType.hasUsername, hasUsername {
|
||||||
|
if hasUsername != (channel.addressName != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
guard !peersFilter.contains(.excludeSavedMessages) || peer.id != accountPeer.id else { return false }
|
guard !peersFilter.contains(.excludeSavedMessages) || peer.id != accountPeer.id else { return false }
|
||||||
guard !peersFilter.contains(.excludeSecretChats) || peer.id.namespace != Namespaces.Peer.SecretChat else { return false }
|
guard !peersFilter.contains(.excludeSecretChats) || peer.id.namespace != Namespaces.Peer.SecretChat else { return false }
|
||||||
guard !peersFilter.contains(.onlyPrivateChats) || peer.id.namespace == Namespaces.Peer.CloudUser else { return false }
|
guard !peersFilter.contains(.onlyPrivateChats) || peer.id.namespace == Namespaces.Peer.CloudUser else { return false }
|
||||||
@ -1674,6 +1766,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -119,13 +119,14 @@ private final class ChatListSearchPendingPane {
|
|||||||
interaction: ChatListSearchInteraction,
|
interaction: ChatListSearchInteraction,
|
||||||
navigationController: NavigationController?,
|
navigationController: NavigationController?,
|
||||||
peersFilter: ChatListNodePeersFilter,
|
peersFilter: ChatListNodePeersFilter,
|
||||||
|
requestPeerType: ReplyMarkupButtonRequestPeerType?,
|
||||||
location: ChatListControllerLocation,
|
location: ChatListControllerLocation,
|
||||||
searchQuery: Signal<String?, NoError>,
|
searchQuery: Signal<String?, NoError>,
|
||||||
searchOptions: Signal<ChatListSearchOptions?, NoError>,
|
searchOptions: Signal<ChatListSearchOptions?, NoError>,
|
||||||
key: ChatListSearchPaneKey,
|
key: ChatListSearchPaneKey,
|
||||||
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
|
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
|
||||||
) {
|
) {
|
||||||
let paneNode = ChatListSearchListPaneNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: (key == .chats || key == .topics) ? peersFilter : [], location: location, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
let paneNode = ChatListSearchListPaneNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: (key == .chats || key == .topics) ? peersFilter : [], requestPeerType: requestPeerType, location: location, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
||||||
|
|
||||||
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
|
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
|
||||||
self.disposable = (paneNode.isReady
|
self.disposable = (paneNode.isReady
|
||||||
@ -147,6 +148,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
|||||||
private let animationRenderer: MultiAnimationRenderer
|
private let animationRenderer: MultiAnimationRenderer
|
||||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
private let peersFilter: ChatListNodePeersFilter
|
private let peersFilter: ChatListNodePeersFilter
|
||||||
|
private let requestPeerType: ReplyMarkupButtonRequestPeerType?
|
||||||
private let location: ChatListControllerLocation
|
private let location: ChatListControllerLocation
|
||||||
private let searchQuery: Signal<String?, NoError>
|
private let searchQuery: Signal<String?, NoError>
|
||||||
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
|
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
|
||||||
@ -181,12 +183,13 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
|||||||
|
|
||||||
private var currentAvailablePanes: [ChatListSearchPaneKey]?
|
private var currentAvailablePanes: [ChatListSearchPaneKey]?
|
||||||
|
|
||||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peersFilter: ChatListNodePeersFilter, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peersFilter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.animationCache = animationCache
|
self.animationCache = animationCache
|
||||||
self.animationRenderer = animationRenderer
|
self.animationRenderer = animationRenderer
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.peersFilter = peersFilter
|
self.peersFilter = peersFilter
|
||||||
|
self.requestPeerType = requestPeerType
|
||||||
self.location = location
|
self.location = location
|
||||||
self.searchQuery = searchQuery
|
self.searchQuery = searchQuery
|
||||||
self.searchOptions = searchOptions
|
self.searchOptions = searchOptions
|
||||||
@ -420,6 +423,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
|||||||
interaction: self.interaction!,
|
interaction: self.interaction!,
|
||||||
navigationController: self.navigationController,
|
navigationController: self.navigationController,
|
||||||
peersFilter: self.peersFilter,
|
peersFilter: self.peersFilter,
|
||||||
|
requestPeerType: self.requestPeerType,
|
||||||
location: self.location,
|
location: self.location,
|
||||||
searchQuery: self.searchQuery,
|
searchQuery: self.searchQuery,
|
||||||
searchOptions: self.searchOptions,
|
searchOptions: self.searchOptions,
|
||||||
|
@ -21,6 +21,7 @@ import Postbox
|
|||||||
public enum ChatListNodeMode {
|
public enum ChatListNodeMode {
|
||||||
case chatList
|
case chatList
|
||||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool)
|
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool)
|
||||||
|
case peerType(type: ReplyMarkupButtonRequestPeerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChatListNodeListViewTransition {
|
struct ChatListNodeListViewTransition {
|
||||||
@ -307,10 +308,13 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||||
case let .AdditionalCategory(_, id, title, image, appearance, selected, presentationData):
|
case let .AdditionalCategory(_, id, title, image, appearance, selected, presentationData):
|
||||||
var header: ChatListSearchItemHeader?
|
var header: ChatListSearchItemHeader?
|
||||||
|
if case .peerType = mode {
|
||||||
|
} else {
|
||||||
if case .action = appearance {
|
if case .action = appearance {
|
||||||
// TODO: hack, generalize
|
// TODO: hack, generalize
|
||||||
header = ChatListSearchItemHeader(type: .orImportIntoAnExistingGroup, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
header = ChatListSearchItemHeader(type: .orImportIntoAnExistingGroup, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
|
||||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
|
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
|
||||||
context: context,
|
context: context,
|
||||||
@ -546,6 +550,37 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
animationCache: nodeInteraction.animationCache,
|
animationCache: nodeInteraction.animationCache,
|
||||||
animationRenderer: nodeInteraction.animationRenderer
|
animationRenderer: nodeInteraction.animationRenderer
|
||||||
), directionHint: entry.directionHint)
|
), directionHint: entry.directionHint)
|
||||||
|
case .peerType:
|
||||||
|
let itemPeer = peer.chatMainPeer
|
||||||
|
var chatPeer: EnginePeer?
|
||||||
|
if let peer = peer.peers[peer.peerId] {
|
||||||
|
chatPeer = peer
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerContent: ContactsPeerItemPeer = .peer(peer: itemPeer, chatPeer: chatPeer)
|
||||||
|
let status: ContactsPeerItemStatus = .none
|
||||||
|
|
||||||
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||||
|
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
|
||||||
|
sortOrder: presentationData.nameSortOrder,
|
||||||
|
displayOrder: presentationData.nameDisplayOrder,
|
||||||
|
context: context,
|
||||||
|
peerMode: .generalSearch,
|
||||||
|
peer: peerContent,
|
||||||
|
status: status,
|
||||||
|
enabled: true,
|
||||||
|
selection: .none,
|
||||||
|
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||||
|
index: nil,
|
||||||
|
header: nil,
|
||||||
|
action: { _ in
|
||||||
|
if let chatPeer = chatPeer {
|
||||||
|
nodeInteraction.peerSelected(chatPeer, nil, nil, nil)
|
||||||
|
}
|
||||||
|
}, disabledAction: nil,
|
||||||
|
animationCache: nodeInteraction.animationCache,
|
||||||
|
animationRenderer: nodeInteraction.animationRenderer
|
||||||
|
), directionHint: entry.directionHint)
|
||||||
}
|
}
|
||||||
case let .HoleEntry(_, theme):
|
case let .HoleEntry(_, theme):
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||||
@ -766,6 +801,37 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
animationCache: nodeInteraction.animationCache,
|
animationCache: nodeInteraction.animationCache,
|
||||||
animationRenderer: nodeInteraction.animationRenderer
|
animationRenderer: nodeInteraction.animationRenderer
|
||||||
), directionHint: entry.directionHint)
|
), directionHint: entry.directionHint)
|
||||||
|
case .peerType:
|
||||||
|
let itemPeer = peer.chatMainPeer
|
||||||
|
var chatPeer: EnginePeer?
|
||||||
|
if let peer = peer.peers[peer.peerId] {
|
||||||
|
chatPeer = peer
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerContent: ContactsPeerItemPeer = .peer(peer: itemPeer, chatPeer: chatPeer)
|
||||||
|
let status: ContactsPeerItemStatus = .none
|
||||||
|
|
||||||
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
|
||||||
|
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
|
||||||
|
sortOrder: presentationData.nameSortOrder,
|
||||||
|
displayOrder: presentationData.nameDisplayOrder,
|
||||||
|
context: context,
|
||||||
|
peerMode: .generalSearch,
|
||||||
|
peer: peerContent,
|
||||||
|
status: status,
|
||||||
|
enabled: true,
|
||||||
|
selection: .none,
|
||||||
|
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||||
|
index: nil,
|
||||||
|
header: nil,
|
||||||
|
action: { _ in
|
||||||
|
if let chatPeer = chatPeer {
|
||||||
|
nodeInteraction.peerSelected(chatPeer, nil, nil, nil)
|
||||||
|
}
|
||||||
|
}, disabledAction: nil,
|
||||||
|
animationCache: nodeInteraction.animationCache,
|
||||||
|
animationRenderer: nodeInteraction.animationRenderer
|
||||||
|
), directionHint: entry.directionHint)
|
||||||
}
|
}
|
||||||
case let .HoleEntry(_, theme):
|
case let .HoleEntry(_, theme):
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||||
@ -806,10 +872,13 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||||
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
|
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
|
||||||
var header: ChatListSearchItemHeader?
|
var header: ChatListSearchItemHeader?
|
||||||
|
if case .peerType = mode {
|
||||||
|
} else {
|
||||||
if case .action = appearance {
|
if case .action = appearance {
|
||||||
// TODO: hack, generalize
|
// TODO: hack, generalize
|
||||||
header = ChatListSearchItemHeader(type: .orImportIntoAnExistingGroup, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
header = ChatListSearchItemHeader(type: .orImportIntoAnExistingGroup, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListAdditionalCategoryItem(
|
||||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
|
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings),
|
||||||
context: context,
|
context: context,
|
||||||
@ -963,7 +1032,7 @@ public final class ChatListNode: ListView {
|
|||||||
public var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
|
public var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
|
||||||
public var contentScrollingEnded: ((ListView) -> Bool)?
|
public var contentScrollingEnded: ((ListView) -> Bool)?
|
||||||
|
|
||||||
var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ContainedViewLayoutTransition) -> Void)?
|
public var isEmptyUpdated: ((ChatListNodeEmptyState, Bool, ContainedViewLayoutTransition) -> Void)?
|
||||||
private var currentIsEmptyState: ChatListNodeEmptyState?
|
private var currentIsEmptyState: ChatListNodeEmptyState?
|
||||||
|
|
||||||
public var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
|
public var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
|
||||||
@ -1491,13 +1560,15 @@ public final class ChatListNode: ListView {
|
|||||||
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
||||||
|
|
||||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, storageInfo: storageInfo, suggestPasswordSetup: suggestPasswordSetup, mode: mode, chatListLocation: location)
|
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, storageInfo: storageInfo, suggestPasswordSetup: suggestPasswordSetup, mode: mode, chatListLocation: location)
|
||||||
let entries = rawEntries.filter { entry in
|
var isEmpty = true
|
||||||
|
var entries = rawEntries.filter { entry in
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .PeerEntry(peerEntry):
|
case let .PeerEntry(peerEntry):
|
||||||
let peer = peerEntry.peer
|
let peer = peerEntry.peer
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case .chatList:
|
case .chatList:
|
||||||
|
isEmpty = false
|
||||||
return true
|
return true
|
||||||
case let .peers(filter, _, _, _, _):
|
case let .peers(filter, _, _, _, _):
|
||||||
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
|
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
|
||||||
@ -1601,12 +1672,113 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEmpty = false
|
||||||
return true
|
return true
|
||||||
|
case let .peerType(peerType):
|
||||||
|
if let peer = peer.peer {
|
||||||
|
switch peerType {
|
||||||
|
case let .user(userType):
|
||||||
|
if case let .user(user) = peer {
|
||||||
|
if let isBot = userType.isBot {
|
||||||
|
if isBot != (user.botInfo != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isPremium = userType.isPremium {
|
||||||
|
if isPremium != user.isPremium {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isEmpty = false
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .group(groupType):
|
||||||
|
if case let .legacyGroup(group) = peer {
|
||||||
|
if groupType.isCreator {
|
||||||
|
if case .creator = group.role {
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isForum = groupType.isForum, isForum {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let hasUsername = groupType.hasUsername, hasUsername {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let userAdminRights = groupType.userAdminRights {
|
||||||
|
if case let .admin(rights, _) = group.role {
|
||||||
|
if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if case .member = group.role {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isEmpty = false
|
||||||
|
return true
|
||||||
|
} else if case let .channel(channel) = peer, case .group = channel.info {
|
||||||
|
if groupType.isCreator {
|
||||||
|
if !channel.flags.contains(.isCreator) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isForum = groupType.isForum, isForum {
|
||||||
|
if isForum != channel.flags.contains(.isForum) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let hasUsername = groupType.hasUsername, hasUsername {
|
||||||
|
if hasUsername != (channel.addressName != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let userAdminRights = groupType.userAdminRights {
|
||||||
|
if channel.flags.contains(.isCreator) {
|
||||||
|
} else if let rights = channel.adminRights {
|
||||||
|
if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isEmpty = false
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .channel(channelType):
|
||||||
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||||
|
if channelType.isCreator {
|
||||||
|
if !channel.flags.contains(.isCreator) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let hasUsername = channelType.hasUsername, hasUsername {
|
||||||
|
if hasUsername != (channel.addressName != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isEmpty = false
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isEmpty {
|
||||||
|
entries = []
|
||||||
|
}
|
||||||
|
|
||||||
let processedView = ChatListNodeView(originalList: update.list, filteredEntries: entries, isLoading: isLoading, filter: filter)
|
let processedView = ChatListNodeView(originalList: update.list, filteredEntries: entries, isLoading: isLoading, filter: filter)
|
||||||
let previousView = previousView.swap(processedView)
|
let previousView = previousView.swap(processedView)
|
||||||
@ -1849,7 +2021,7 @@ public final class ChatListNode: ListView {
|
|||||||
switch mode {
|
switch mode {
|
||||||
case .chatList:
|
case .chatList:
|
||||||
initialLocation = .initial(count: 50, filter: self.chatListFilter)
|
initialLocation = .initial(count: 50, filter: self.chatListFilter)
|
||||||
case .peers:
|
case .peers, .peerType:
|
||||||
initialLocation = .initial(count: 200, filter: self.chatListFilter)
|
initialLocation = .initial(count: 200, filter: self.chatListFilter)
|
||||||
}
|
}
|
||||||
self.setChatListLocation(initialLocation)
|
self.setChatListLocation(initialLocation)
|
||||||
|
@ -675,12 +675,23 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
|||||||
result.append(.HeaderEntry)
|
result.append(.HeaderEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !view.hasLater, case let .peers(_, _, additionalCategories, _, _) = mode {
|
if !view.hasLater {
|
||||||
|
if case let .peers(_, _, additionalCategories, _, _) = mode {
|
||||||
var index = 0
|
var index = 0
|
||||||
for category in additionalCategories.reversed() {
|
for category in additionalCategories.reversed() {
|
||||||
result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData))
|
result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
} else if case let .peerType(type) = mode, !result.isEmpty {
|
||||||
|
switch type {
|
||||||
|
case .group:
|
||||||
|
result.append(.AdditionalCategory(index: 0, id: 0, title: "Create a New Group for This", image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
|
||||||
|
case .channel:
|
||||||
|
result.append(.AdditionalCategory(index: 0, id: 0, title: "Create a New Channel for This", image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +345,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
|
|
||||||
Queue.concurrentDefaultQueue().async {
|
Queue.concurrentDefaultQueue().async {
|
||||||
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
|
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
|
||||||
strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|
strongSelf.recognitionDisposable.set((recognizedContent(context: strongSelf.context, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] results in
|
|> deliverOnMainQueue).start(next: { [weak self] results in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.recognizedContentNode?.removeFromSupernode()
|
strongSelf.recognizedContentNode?.removeFromSupernode()
|
||||||
|
@ -15,6 +15,7 @@ swift_library(
|
|||||||
"//submodules/Postbox:Postbox",
|
"//submodules/Postbox:Postbox",
|
||||||
"//submodules/TelegramCore:TelegramCore",
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -5,6 +5,7 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
private final class CachedImageRecognizedContent: Codable {
|
private final class CachedImageRecognizedContent: Codable {
|
||||||
public let results: [RecognizedContent]
|
public let results: [RecognizedContent]
|
||||||
@ -332,8 +333,11 @@ private func recognizeContent(in image: UIImage?) -> Signal<[RecognizedContent],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func recognizedContent(engine: TelegramEngine, image: @escaping () -> UIImage?, messageId: MessageId) -> Signal<[RecognizedContent], NoError> {
|
public func recognizedContent(context: AccountContext, image: @escaping () -> UIImage?, messageId: MessageId) -> Signal<[RecognizedContent], NoError> {
|
||||||
return cachedImageRecognizedContent(engine: engine, messageId: messageId)
|
if context.sharedContext.immediateExperimentalUISettings.disableImageContentAnalysis {
|
||||||
|
return .single([])
|
||||||
|
}
|
||||||
|
return cachedImageRecognizedContent(engine: context.engine, messageId: messageId)
|
||||||
|> mapToSignal { cachedContent -> Signal<[RecognizedContent], NoError> in
|
|> mapToSignal { cachedContent -> Signal<[RecognizedContent], NoError> in
|
||||||
if let cachedContent = cachedContent {
|
if let cachedContent = cachedContent {
|
||||||
return .single(cachedContent.results)
|
return .single(cachedContent.results)
|
||||||
@ -343,7 +347,7 @@ public func recognizedContent(engine: TelegramEngine, image: @escaping () -> UII
|
|||||||
|> then(
|
|> then(
|
||||||
recognizeContent(in: image())
|
recognizeContent(in: image())
|
||||||
|> beforeNext { results in
|
|> beforeNext { results in
|
||||||
let _ = updateCachedImageRecognizedContent(engine: engine, messageId: messageId, content: CachedImageRecognizedContent(results: results)).start()
|
let _ = updateCachedImageRecognizedContent(engine: context.engine, messageId: messageId, content: CachedImageRecognizedContent(results: results)).start()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,8 @@
|
|||||||
- (UIView *)createAnimationSnapshot;
|
- (UIView *)createAnimationSnapshot;
|
||||||
- (UIView *)createTextSnapshot;
|
- (UIView *)createTextSnapshot;
|
||||||
|
|
||||||
|
- (void)animateIn;
|
||||||
|
|
||||||
@property (nonatomic) bool isEnabled;
|
@property (nonatomic) bool isEnabled;
|
||||||
|
|
||||||
- (void)startTimer;
|
- (void)startTimer;
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
NSMutableAttributedString *_description;
|
NSMutableAttributedString *_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) UILabel *headerLabel;
|
||||||
|
@property (nonatomic, readonly) UILabel *descriptionLabel;
|
||||||
|
|
||||||
- (id)initWithFrame:(CGRect)frame headline:(NSString*)headline description:(NSString*)description color:(UIColor *)color;
|
- (id)initWithFrame:(CGRect)frame headline:(NSString*)headline description:(NSString*)description color:(UIColor *)color;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
headlineLabel.textColor = color;
|
headlineLabel.textColor = color;
|
||||||
headlineLabel.textAlignment = NSTextAlignmentCenter;
|
headlineLabel.textAlignment = NSTextAlignmentCenter;
|
||||||
headlineLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
headlineLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
||||||
|
_headerLabel = headlineLabel;
|
||||||
|
|
||||||
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
|
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
|
||||||
style.lineSpacing = IPAD ? 4 : 3;
|
style.lineSpacing = IPAD ? 4 : 3;
|
||||||
@ -76,7 +76,7 @@
|
|||||||
descriptionLabel.numberOfLines=0;
|
descriptionLabel.numberOfLines=0;
|
||||||
descriptionLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
descriptionLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
||||||
[self addSubview:descriptionLabel];
|
[self addSubview:descriptionLabel];
|
||||||
|
_descriptionLabel = descriptionLabel;
|
||||||
|
|
||||||
[self addSubview:headlineLabel];
|
[self addSubview:headlineLabel];
|
||||||
|
|
||||||
|
@ -102,6 +102,7 @@ typedef enum {
|
|||||||
NSDictionary<NSString *, NSString *> *_englishStrings;
|
NSDictionary<NSString *, NSString *> *_englishStrings;
|
||||||
|
|
||||||
UIView *_wrapperView;
|
UIView *_wrapperView;
|
||||||
|
UIView *_startButton;
|
||||||
|
|
||||||
bool _loadedView;
|
bool _loadedView;
|
||||||
}
|
}
|
||||||
@ -124,8 +125,6 @@ typedef enum {
|
|||||||
_regularDotColor = regularDotColor;
|
_regularDotColor = regularDotColor;
|
||||||
_highlightedDotColor = highlightedDotColor;
|
_highlightedDotColor = highlightedDotColor;
|
||||||
|
|
||||||
self.automaticallyAdjustsScrollViewInsets = false;
|
|
||||||
|
|
||||||
NSArray<NSString *> *stringKeys = @[
|
NSArray<NSString *> *stringKeys = @[
|
||||||
@"Tour.Title1",
|
@"Tour.Title1",
|
||||||
@"Tour.Title2",
|
@"Tour.Title2",
|
||||||
@ -227,6 +226,45 @@ typedef enum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)animateIn {
|
||||||
|
CGPoint logoTargetPosition = _glkView.center;
|
||||||
|
_glkView.center = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0);
|
||||||
|
|
||||||
|
RMIntroPageView *firstPage = (RMIntroPageView *)[_pageViews firstObject];
|
||||||
|
CGPoint headerTargetPosition = firstPage.headerLabel.center;
|
||||||
|
firstPage.headerLabel.center = CGPointMake(headerTargetPosition.x, headerTargetPosition.y + 140.0);
|
||||||
|
|
||||||
|
CGPoint descriptionTargetPosition = firstPage.descriptionLabel.center;
|
||||||
|
firstPage.descriptionLabel.center = CGPointMake(descriptionTargetPosition.x, descriptionTargetPosition.y + 160.0);
|
||||||
|
|
||||||
|
CGPoint pageControlTargetPosition = _pageControl.center;
|
||||||
|
_pageControl.center = CGPointMake(pageControlTargetPosition.x, pageControlTargetPosition.y + 200.0);
|
||||||
|
|
||||||
|
CGPoint buttonTargetPosition = _startButton.center;
|
||||||
|
_startButton.center = CGPointMake(buttonTargetPosition.x, buttonTargetPosition.y + 220.0);
|
||||||
|
|
||||||
|
_glkView.transform = CGAffineTransformMakeScale(0.68, 0.68);
|
||||||
|
|
||||||
|
[UIView animateWithDuration:0.65 delay:0.15 usingSpringWithDamping:1.2f initialSpringVelocity:0.0 options:kNilOptions animations:^{
|
||||||
|
_glkView.center = logoTargetPosition;
|
||||||
|
firstPage.headerLabel.center = headerTargetPosition;
|
||||||
|
firstPage.descriptionLabel.center = descriptionTargetPosition;
|
||||||
|
_pageControl.center = pageControlTargetPosition;
|
||||||
|
_startButton.center = buttonTargetPosition;
|
||||||
|
_glkView.transform = CGAffineTransformIdentity;
|
||||||
|
} completion:nil];
|
||||||
|
|
||||||
|
_pageScrollView.alpha = 0.0;
|
||||||
|
_pageControl.alpha = 0.0;
|
||||||
|
_startButton.alpha = 0.0;
|
||||||
|
|
||||||
|
[UIView animateWithDuration:0.3 delay:0.15 options:kNilOptions animations:^{
|
||||||
|
_pageScrollView.alpha = 1.0;
|
||||||
|
_pageControl.alpha = 1.0;
|
||||||
|
_startButton.alpha = 1.0;
|
||||||
|
} completion:nil];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)loadGL
|
- (void)loadGL
|
||||||
{
|
{
|
||||||
#if TARGET_OS_SIMULATOR && defined(__aarch64__)
|
#if TARGET_OS_SIMULATOR && defined(__aarch64__)
|
||||||
@ -313,6 +351,7 @@ typedef enum {
|
|||||||
[self.view addSubview:_wrapperView];
|
[self.view addSubview:_wrapperView];
|
||||||
|
|
||||||
_pageScrollView = [[UIScrollView alloc]initWithFrame:self.view.bounds];
|
_pageScrollView = [[UIScrollView alloc]initWithFrame:self.view.bounds];
|
||||||
|
_pageScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||||
_pageScrollView.clipsToBounds = true;
|
_pageScrollView.clipsToBounds = true;
|
||||||
_pageScrollView.opaque = true;
|
_pageScrollView.opaque = true;
|
||||||
_pageScrollView.clearsContextBeforeDrawing = false;
|
_pageScrollView.clearsContextBeforeDrawing = false;
|
||||||
@ -512,6 +551,7 @@ typedef enum {
|
|||||||
CGFloat startButtonWidth = MIN(430.0 - 48.0, self.view.bounds.size.width - 48.0f);
|
CGFloat startButtonWidth = MIN(430.0 - 48.0, self.view.bounds.size.width - 48.0f);
|
||||||
UIView *startButton = self.createStartButton(startButtonWidth);
|
UIView *startButton = self.createStartButton(startButtonWidth);
|
||||||
if (startButton.superview == nil) {
|
if (startButton.superview == nil) {
|
||||||
|
_startButton = startButton;
|
||||||
[self.view addSubview:startButton];
|
[self.view addSubview:startButton];
|
||||||
}
|
}
|
||||||
startButton.frame = CGRectMake(floor((self.view.bounds.size.width - startButtonWidth) / 2.0f), self.view.bounds.size.height - startButtonY - statusBarHeight, startButtonWidth, 50.0f);
|
startButton.frame = CGRectMake(floor((self.view.bounds.size.width - startButtonWidth) / 2.0f), self.view.bounds.size.height - startButtonY - statusBarHeight, startButtonWidth, 50.0f);
|
||||||
@ -523,8 +563,7 @@ typedef enum {
|
|||||||
_pageScrollView.contentSize=CGSizeMake(_headlines.count * self.view.bounds.size.width, 150);
|
_pageScrollView.contentSize=CGSizeMake(_headlines.count * self.view.bounds.size.width, 150);
|
||||||
_pageScrollView.contentOffset = CGPointMake(_currentPage * self.view.bounds.size.width, 0);
|
_pageScrollView.contentOffset = CGPointMake(_currentPage * self.view.bounds.size.width, 0);
|
||||||
|
|
||||||
[_pageViews enumerateObjectsUsingBlock:^(UIView *pageView, NSUInteger index, __unused BOOL *stop)
|
[_pageViews enumerateObjectsUsingBlock:^(UIView *pageView, NSUInteger index, __unused BOOL *stop) {
|
||||||
{
|
|
||||||
pageView.frame = CGRectMake(index * self.view.bounds.size.width, (pageY - statusBarHeight), self.view.bounds.size.width, 150);
|
pageView.frame = CGRectMake(index * self.view.bounds.size.width, (pageY - statusBarHeight), self.view.bounds.size.width, 150);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -512,7 +512,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ViewInChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
@ -834,9 +834,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
case .attachMenuBotAllowed:
|
case .attachMenuBotAllowed:
|
||||||
attributedString = NSAttributedString(string: strings.Notification_BotWriteAllowed, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.Notification_BotWriteAllowed, font: titleFont, textColor: primaryTextColor)
|
||||||
case let .requestedPeer(_, peerId):
|
case let .requestedPeer(_, peerId):
|
||||||
//TODO:localize
|
let peerName = message.peers[peerId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
|
||||||
let _ = peerId
|
attributedString = addAttributesToStringWithRanges(strings.Notification_RequestedPeer(peerName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId)]))
|
||||||
attributedString = NSAttributedString(string: "Requested Chat", font: titleFont, textColor: primaryTextColor)
|
|
||||||
case .unknown:
|
case .unknown:
|
||||||
attributedString = nil
|
attributedString = nil
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,7 @@ public final class ChatControllerInteraction {
|
|||||||
public let openJoinLink: (String) -> Void
|
public let openJoinLink: (String) -> Void
|
||||||
public let openWebView: (String, String, Bool, Bool) -> Void
|
public let openWebView: (String, String, Bool, Bool) -> Void
|
||||||
public let activateAdAction: (EngineMessage.Id) -> Void
|
public let activateAdAction: (EngineMessage.Id) -> Void
|
||||||
|
public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32) -> Void
|
||||||
|
|
||||||
public let requestMessageUpdate: (MessageId, Bool) -> Void
|
public let requestMessageUpdate: (MessageId, Bool) -> Void
|
||||||
public let cancelInteractiveKeyboardGestures: () -> Void
|
public let cancelInteractiveKeyboardGestures: () -> Void
|
||||||
@ -277,6 +278,7 @@ public final class ChatControllerInteraction {
|
|||||||
openJoinLink: @escaping (String) -> Void,
|
openJoinLink: @escaping (String) -> Void,
|
||||||
openWebView: @escaping (String, String, Bool, Bool) -> Void,
|
openWebView: @escaping (String, String, Bool, Bool) -> Void,
|
||||||
activateAdAction: @escaping (EngineMessage.Id) -> Void,
|
activateAdAction: @escaping (EngineMessage.Id) -> Void,
|
||||||
|
openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32) -> Void,
|
||||||
requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
|
requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
|
||||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||||
dismissTextInput: @escaping () -> Void,
|
dismissTextInput: @escaping () -> Void,
|
||||||
@ -370,6 +372,7 @@ public final class ChatControllerInteraction {
|
|||||||
self.openJoinLink = openJoinLink
|
self.openJoinLink = openJoinLink
|
||||||
self.openWebView = openWebView
|
self.openWebView = openWebView
|
||||||
self.activateAdAction = activateAdAction
|
self.activateAdAction = activateAdAction
|
||||||
|
self.openRequestedPeerSelection = openRequestedPeerSelection
|
||||||
self.requestMessageUpdate = requestMessageUpdate
|
self.requestMessageUpdate = requestMessageUpdate
|
||||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||||
self.dismissTextInput = dismissTextInput
|
self.dismissTextInput = dismissTextInput
|
||||||
|
@ -35,6 +35,7 @@ import BackgroundTasks
|
|||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
import StoreKit
|
import StoreKit
|
||||||
import PhoneNumberFormat
|
import PhoneNumberFormat
|
||||||
|
import AuthorizationUI
|
||||||
|
|
||||||
#if canImport(AppCenter)
|
#if canImport(AppCenter)
|
||||||
import AppCenter
|
import AppCenter
|
||||||
@ -315,6 +316,11 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
self.window = window
|
self.window = window
|
||||||
self.nativeWindow = window
|
self.nativeWindow = window
|
||||||
|
|
||||||
|
let launchIconSize = CGSize(width: 120.0, height: 120.0)
|
||||||
|
let launchIconView = UIImageView(image: UIImage(bundleImageName: "Chat/Links/QrLogo"))
|
||||||
|
launchIconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((hostView.containerView.frame.width - launchIconSize.width) / 2.0), y: floorToScreenPixels((hostView.containerView.frame.height - launchIconSize.height) / 2.0)), size: launchIconSize)
|
||||||
|
hostView.containerView.addSubview(launchIconView)
|
||||||
|
|
||||||
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
||||||
@ -1044,7 +1050,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
|
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
self.contextDisposable.set((self.context.get()
|
self.contextDisposable.set((self.context.get()
|
||||||
|> deliverOnMainQueue).start(next: { context in
|
|> deliverOnMainQueue).start(next: { [weak launchIconView] context in
|
||||||
print("Application: context took \(CFAbsoluteTimeGetCurrent() - startTime) to become available")
|
print("Application: context took \(CFAbsoluteTimeGetCurrent() - startTime) to become available")
|
||||||
|
|
||||||
var network: Network?
|
var network: Network?
|
||||||
@ -1075,8 +1081,15 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
print("Launch to ready took \((CFAbsoluteTimeGetCurrent() - launchStartTime) * 1000.0) ms")
|
print("Launch to ready took \((CFAbsoluteTimeGetCurrent() - launchStartTime) * 1000.0) ms")
|
||||||
|
|
||||||
self.mainWindow.debugAction = nil
|
self.mainWindow.debugAction = nil
|
||||||
|
|
||||||
self.mainWindow.viewController = context.rootController
|
self.mainWindow.viewController = context.rootController
|
||||||
|
|
||||||
|
if let launchIconView {
|
||||||
|
launchIconView.alpha = 0.0
|
||||||
|
launchIconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak launchIconView] _ in
|
||||||
|
launchIconView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if firstTime {
|
if firstTime {
|
||||||
let layer = context.rootController.view.layer
|
let layer = context.rootController.view.layer
|
||||||
layer.allowsGroupOpacity = true
|
layer.allowsGroupOpacity = true
|
||||||
@ -1123,7 +1136,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
let authContextReadyDisposable = MetaDisposable()
|
let authContextReadyDisposable = MetaDisposable()
|
||||||
|
|
||||||
self.authContextDisposable.set((self.authContext.get()
|
self.authContextDisposable.set((self.authContext.get()
|
||||||
|> deliverOnMainQueue).start(next: { context in
|
|> deliverOnMainQueue).start(next: { [weak launchIconView] context in
|
||||||
var network: Network?
|
var network: Network?
|
||||||
if let context = context {
|
if let context = context {
|
||||||
network = context.account.network
|
network = context.account.network
|
||||||
@ -1155,15 +1168,38 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
self.authContextValue = context
|
self.authContextValue = context
|
||||||
if let context = context {
|
if let context = context {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
|
||||||
|
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||||
let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
let statusController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||||
self.mainWindow.present(statusController, on: .root)
|
self?.mainWindow.present(statusController, on: .root)
|
||||||
|
return ActionDisposable { [weak statusController] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
statusController?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.5, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = progressSignal.start()
|
||||||
|
|
||||||
let isReady: Signal<Bool, NoError> = context.isReady.get()
|
let isReady: Signal<Bool, NoError> = context.isReady.get()
|
||||||
authContextReadyDisposable.set((isReady
|
authContextReadyDisposable.set((isReady
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { _ in
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
statusController.dismiss()
|
progressDisposable.dispose()
|
||||||
self.mainWindow.present(context.rootController, on: .root)
|
self.mainWindow.present(context.rootController, on: .root)
|
||||||
|
|
||||||
|
if let launchIconView {
|
||||||
|
if context.rootController.topViewController is AuthorizationSequenceSplashController {
|
||||||
|
context.rootController.view.addSubview(launchIconView)
|
||||||
|
Queue.mainQueue().after(0.01, {
|
||||||
|
launchIconView.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
launchIconView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
authContextReadyDisposable.set(nil)
|
authContextReadyDisposable.set(nil)
|
||||||
|
@ -431,8 +431,10 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
|
|||||||
})
|
})
|
||||||
case let .openWebView(url, simple):
|
case let .openWebView(url, simple):
|
||||||
self.controllerInteraction.openWebView(markupButton.title, url, simple, false)
|
self.controllerInteraction.openWebView(markupButton.title, url, simple, false)
|
||||||
case .requestPeer:
|
case let .requestPeer(peerType, buttonId):
|
||||||
break
|
if let message = self.message {
|
||||||
|
self.controllerInteraction.openRequestedPeerSelection(message.id, peerType, buttonId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if dismissIfOnce {
|
if dismissIfOnce {
|
||||||
if let message = self.message {
|
if let message = self.message {
|
||||||
|
@ -4131,13 +4131,53 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
case let .join(_, joinHash):
|
case let .join(_, joinHash):
|
||||||
self.controllerInteraction?.openJoinLink(joinHash)
|
self.controllerInteraction?.openJoinLink(joinHash)
|
||||||
}
|
}
|
||||||
|
}, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let context = self.context
|
||||||
|
let peerId = self.chatLocation.peerId
|
||||||
|
var createNewGroupImpl: (() -> Void)?
|
||||||
|
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent], requestPeerType: peerType, hasContactSelector: false, createNewGroup: {
|
||||||
|
createNewGroupImpl?()
|
||||||
|
}))
|
||||||
|
controller.peerSelected = { [weak controller] peer, _ in
|
||||||
|
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peer.id).start()
|
||||||
|
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
createNewGroupImpl = { [weak controller] in
|
||||||
|
switch peerType {
|
||||||
|
case .user:
|
||||||
|
break
|
||||||
|
case let .group(group):
|
||||||
|
let createGroupController = createGroupControllerImpl(context: context, peerIds: peerId.flatMap { [$0] } ?? [], mode: .requestPeer(group), completion: { peerId, dismiss in
|
||||||
|
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peerId).start()
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
|
createGroupController.navigationPresentation = .modal
|
||||||
|
controller?.replace(with: createGroupController)
|
||||||
|
case let .channel(channel):
|
||||||
|
let createChannelController = createChannelController(context: context, mode: .requestPeer(channel), completion: { peerId, dismiss in
|
||||||
|
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peerId).start()
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
|
createChannelController.navigationPresentation = .modal
|
||||||
|
controller?.replace(with: createChannelController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.push(controller)
|
||||||
}, requestMessageUpdate: { [weak self] id, scroll in
|
}, requestMessageUpdate: { [weak self] id, scroll in
|
||||||
if let strongSelf = self {
|
if let self {
|
||||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
|
self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
|
||||||
}
|
}
|
||||||
}, cancelInteractiveKeyboardGestures: { [weak self] in
|
}, cancelInteractiveKeyboardGestures: { [weak self] in
|
||||||
(self?.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
|
if let self {
|
||||||
self?.chatDisplayNode.cancelInteractiveKeyboardGestures()
|
(self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
|
||||||
|
self.chatDisplayNode.cancelInteractiveKeyboardGestures()
|
||||||
|
}
|
||||||
}, dismissTextInput: { [weak self] in
|
}, dismissTextInput: { [weak self] in
|
||||||
self?.chatDisplayNode.dismissTextInput()
|
self?.chatDisplayNode.dismissTextInput()
|
||||||
}, scrollToMessageId: { [weak self] index in
|
}, scrollToMessageId: { [weak self] index in
|
||||||
|
@ -1552,7 +1552,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isReplyThreadHead {
|
if isReplyThreadHead {
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.SharedMedia_ViewInChat, icon: { theme in
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ViewInChannel, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { c, _ in
|
}, action: { c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
|
@ -555,6 +555,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openWebView: { _, _, _, _ in
|
}, openWebView: { _, _, _, _ in
|
||||||
}, activateAdAction: { _ in
|
}, activateAdAction: { _ in
|
||||||
|
}, openRequestedPeerSelection: { _, _, _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
@ -206,7 +206,13 @@ private func CreateChannelEntries(presentationData: PresentationData, state: Cre
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createChannelController(context: AccountContext) -> ViewController {
|
public enum CreateChannelMode {
|
||||||
|
case generic
|
||||||
|
case requestPeer(ReplyMarkupButtonRequestPeerType.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public func createChannelController(context: AccountContext, mode: CreateChannelMode = .generic, completion: ((PeerId, @escaping () -> Void) -> Void)? = nil) -> ViewController {
|
||||||
let initialState = CreateChannelState(creating: false, editingName: ItemListAvatarAndNameInfoItemName.title(title: "", type: .channel), editingDescriptionText: "", avatar: nil)
|
let initialState = CreateChannelState(creating: false, editingName: ItemListAvatarAndNameInfoItemName.title(title: "", type: .channel), editingDescriptionText: "", avatar: nil)
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
|
@ -29,6 +29,7 @@ import LegacyMediaPickerUI
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import ChatTimerScreen
|
import ChatTimerScreen
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
private struct CreateGroupArguments {
|
private struct CreateGroupArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -39,10 +40,14 @@ private struct CreateGroupArguments {
|
|||||||
let changeLocation: () -> Void
|
let changeLocation: () -> Void
|
||||||
let updateWithVenue: (TelegramMediaMap) -> Void
|
let updateWithVenue: (TelegramMediaMap) -> Void
|
||||||
let updateAutoDelete: () -> Void
|
let updateAutoDelete: () -> Void
|
||||||
|
let updatePublicLinkText: (String) -> Void
|
||||||
|
let openAuction: (String) -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CreateGroupSection: Int32 {
|
private enum CreateGroupSection: Int32 {
|
||||||
case info
|
case info
|
||||||
|
case username
|
||||||
|
case topics
|
||||||
case autoDelete
|
case autoDelete
|
||||||
case members
|
case members
|
||||||
case location
|
case location
|
||||||
@ -65,6 +70,12 @@ private enum CreateGroupEntryTag: ItemListItemTag {
|
|||||||
private enum CreateGroupEntry: ItemListNodeEntry {
|
private enum CreateGroupEntry: ItemListNodeEntry {
|
||||||
case groupInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
case groupInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
||||||
case setProfilePhoto(PresentationTheme, String)
|
case setProfilePhoto(PresentationTheme, String)
|
||||||
|
case usernameHeader(PresentationTheme, String)
|
||||||
|
case username(PresentationTheme, String, String)
|
||||||
|
case usernameStatus(PresentationTheme, String, AddressNameValidationStatus, String, String)
|
||||||
|
case usernameInfo(PresentationTheme, String)
|
||||||
|
case topics(PresentationTheme, String)
|
||||||
|
case topicsInfo(PresentationTheme, String)
|
||||||
case autoDelete(title: String, value: String)
|
case autoDelete(title: String, value: String)
|
||||||
case autoDeleteInfo(String)
|
case autoDeleteInfo(String)
|
||||||
case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?)
|
case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?)
|
||||||
@ -79,6 +90,10 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case .groupInfo, .setProfilePhoto:
|
case .groupInfo, .setProfilePhoto:
|
||||||
return CreateGroupSection.info.rawValue
|
return CreateGroupSection.info.rawValue
|
||||||
|
case .usernameHeader, .username, .usernameStatus, .usernameInfo:
|
||||||
|
return CreateGroupSection.username.rawValue
|
||||||
|
case .topics, .topicsInfo:
|
||||||
|
return CreateGroupSection.topics.rawValue
|
||||||
case .autoDelete, .autoDeleteInfo:
|
case .autoDelete, .autoDeleteInfo:
|
||||||
return CreateGroupSection.autoDelete.rawValue
|
return CreateGroupSection.autoDelete.rawValue
|
||||||
case .member:
|
case .member:
|
||||||
@ -96,12 +111,24 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
return 0
|
return 0
|
||||||
case .setProfilePhoto:
|
case .setProfilePhoto:
|
||||||
return 1
|
return 1
|
||||||
case .autoDelete:
|
case .usernameHeader:
|
||||||
return 2
|
return 2
|
||||||
case .autoDeleteInfo:
|
case .username:
|
||||||
return 3
|
return 3
|
||||||
|
case .usernameStatus:
|
||||||
|
return 4
|
||||||
|
case .usernameInfo:
|
||||||
|
return 5
|
||||||
|
case .topics:
|
||||||
|
return 6
|
||||||
|
case .topicsInfo:
|
||||||
|
return 7
|
||||||
|
case .autoDelete:
|
||||||
|
return 8
|
||||||
|
case .autoDeleteInfo:
|
||||||
|
return 9
|
||||||
case let .member(index, _, _, _, _, _, _):
|
case let .member(index, _, _, _, _, _, _):
|
||||||
return 4 + index
|
return 10 + index
|
||||||
case .locationHeader:
|
case .locationHeader:
|
||||||
return 10000
|
return 10000
|
||||||
case .location:
|
case .location:
|
||||||
@ -153,6 +180,43 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case let .usernameHeader(lhsTheme, lhsText):
|
||||||
|
if case let .usernameHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .username(lhsTheme, lhsText, lhsValue):
|
||||||
|
if case let .username(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .usernameStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername):
|
||||||
|
if case let .usernameStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .usernameInfo(lhsTheme, lhsText):
|
||||||
|
if case let .usernameInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .topics(lhsTheme, lhsText):
|
||||||
|
if case let .topics(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .topicsInfo(lhsTheme, lhsText):
|
||||||
|
if case let .topicsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .autoDelete(title, value):
|
case let .autoDelete(title, value):
|
||||||
if case .autoDelete(title, value) = rhs {
|
if case .autoDelete(title, value) = rhs {
|
||||||
return true
|
return true
|
||||||
@ -263,6 +327,41 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
arguments.changeProfilePhoto()
|
arguments.changeProfilePhoto()
|
||||||
})
|
})
|
||||||
|
case let .usernameHeader(_, title):
|
||||||
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||||
|
case let .username(theme, placeholder, text):
|
||||||
|
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in
|
||||||
|
arguments.updatePublicLinkText(updatedText)
|
||||||
|
}, action: {
|
||||||
|
})
|
||||||
|
case let .usernameStatus(_, _, status, text, username):
|
||||||
|
var displayActivity = false
|
||||||
|
let textColor: ItemListActivityTextItem.TextColor
|
||||||
|
switch status {
|
||||||
|
case .invalidFormat:
|
||||||
|
textColor = .destructive
|
||||||
|
case let .availability(availability):
|
||||||
|
switch availability {
|
||||||
|
case .available:
|
||||||
|
textColor = .constructive
|
||||||
|
case .purchaseAvailable:
|
||||||
|
textColor = .generic
|
||||||
|
case .invalid, .taken:
|
||||||
|
textColor = .destructive
|
||||||
|
}
|
||||||
|
case .checking:
|
||||||
|
textColor = .generic
|
||||||
|
displayActivity = true
|
||||||
|
}
|
||||||
|
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
|
||||||
|
arguments.openAuction(username)
|
||||||
|
}, sectionId: self.section)
|
||||||
|
case let .usernameInfo(_, text):
|
||||||
|
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||||
|
case let .topics(_, text):
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in })
|
||||||
|
case let .topicsInfo(_, text):
|
||||||
|
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||||
case let .autoDelete(text, value):
|
case let .autoDelete(text, value):
|
||||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||||
arguments.updateAutoDelete()
|
arguments.updateAutoDelete()
|
||||||
@ -299,9 +398,11 @@ private struct CreateGroupState: Equatable {
|
|||||||
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
||||||
var location: PeerGeoLocation?
|
var location: PeerGeoLocation?
|
||||||
var autoremoveTimeout: Int32?
|
var autoremoveTimeout: Int32?
|
||||||
|
var editingPublicLinkText: String?
|
||||||
|
var addressNameValidationStatus: AddressNameValidationStatus?
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, venues: [TelegramMediaMap]?, globalAutoremoveTimeout: Int32) -> [CreateGroupEntry] {
|
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, venues: [TelegramMediaMap]?, globalAutoremoveTimeout: Int32, requestPeer: ReplyMarkupButtonRequestPeerType.Group?) -> [CreateGroupEntry] {
|
||||||
var entries: [CreateGroupEntry] = []
|
var entries: [CreateGroupEntry] = []
|
||||||
|
|
||||||
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
||||||
@ -310,6 +411,58 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
|||||||
|
|
||||||
entries.append(.groupInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar))
|
entries.append(.groupInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar))
|
||||||
|
|
||||||
|
if let requestPeer {
|
||||||
|
if let hasUsername = requestPeer.hasUsername, hasUsername {
|
||||||
|
let currentUsername = state.editingPublicLinkText ?? ""
|
||||||
|
entries.append(.usernameHeader(presentationData.theme, presentationData.strings.CreateGroup_PublicLinkTitle.uppercased()))
|
||||||
|
entries.append(.username(presentationData.theme, "link", currentUsername))
|
||||||
|
|
||||||
|
if let status = state.addressNameValidationStatus {
|
||||||
|
let statusText: String
|
||||||
|
switch status {
|
||||||
|
case let .invalidFormat(error):
|
||||||
|
switch error {
|
||||||
|
case .startsWithDigit:
|
||||||
|
statusText = presentationData.strings.Username_InvalidStartsWithNumber
|
||||||
|
case .startsWithUnderscore:
|
||||||
|
statusText = presentationData.strings.Username_InvalidStartsWithUnderscore
|
||||||
|
case .endsWithUnderscore:
|
||||||
|
statusText = presentationData.strings.Username_InvalidEndsWithUnderscore
|
||||||
|
case .invalidCharacters:
|
||||||
|
statusText = presentationData.strings.Username_InvalidCharacters
|
||||||
|
case .tooShort:
|
||||||
|
statusText = presentationData.strings.Username_InvalidTooShort
|
||||||
|
}
|
||||||
|
case let .availability(availability):
|
||||||
|
switch availability {
|
||||||
|
case .available:
|
||||||
|
statusText = presentationData.strings.Username_UsernameIsAvailable(currentUsername).string
|
||||||
|
case .invalid:
|
||||||
|
statusText = presentationData.strings.Username_InvalidCharacters
|
||||||
|
case .taken:
|
||||||
|
statusText = presentationData.strings.Username_InvalidTaken
|
||||||
|
case .purchaseAvailable:
|
||||||
|
var markdownString = presentationData.strings.Username_UsernamePurchaseAvailable
|
||||||
|
let entities = generateTextEntities(markdownString, enabledTypes: [.mention])
|
||||||
|
if let entity = entities.first {
|
||||||
|
markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound))
|
||||||
|
markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound))
|
||||||
|
}
|
||||||
|
statusText = markdownString
|
||||||
|
}
|
||||||
|
case .checking:
|
||||||
|
statusText = presentationData.strings.Username_CheckingUsername
|
||||||
|
}
|
||||||
|
entries.append(.usernameStatus(presentationData.theme, currentUsername, status, statusText, currentUsername))
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.usernameInfo(presentationData.theme, presentationData.strings.CreateGroup_PublicLinkInfo))
|
||||||
|
}
|
||||||
|
if let isForum = requestPeer.isForum, isForum {
|
||||||
|
entries.append(.topics(presentationData.theme, presentationData.strings.PeerInfo_OptionTopics))
|
||||||
|
entries.append(.topicsInfo(presentationData.theme, presentationData.strings.PeerInfo_OptionTopicsText))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let autoremoveTimeout = state.autoremoveTimeout ?? globalAutoremoveTimeout
|
let autoremoveTimeout = state.autoremoveTimeout ?? globalAutoremoveTimeout
|
||||||
let autoRemoveText: String
|
let autoRemoveText: String
|
||||||
if autoremoveTimeout == 0 {
|
if autoremoveTimeout == 0 {
|
||||||
@ -319,6 +472,7 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
|||||||
}
|
}
|
||||||
entries.append(.autoDelete(title: presentationData.strings.CreateGroup_AutoDeleteTitle, value: autoRemoveText))
|
entries.append(.autoDelete(title: presentationData.strings.CreateGroup_AutoDeleteTitle, value: autoRemoveText))
|
||||||
entries.append(.autoDeleteInfo(presentationData.strings.CreateGroup_AutoDeleteText))
|
entries.append(.autoDeleteInfo(presentationData.strings.CreateGroup_AutoDeleteText))
|
||||||
|
}
|
||||||
|
|
||||||
var peers: [Peer] = []
|
var peers: [Peer] = []
|
||||||
for peerId in peerIds {
|
for peerId in peerIds {
|
||||||
@ -382,7 +536,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
|
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location, autoremoveTimeout: nil)
|
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location, autoremoveTimeout: nil, editingPublicLinkText: nil, addressNameValidationStatus: nil)
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
||||||
@ -400,6 +554,9 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
|
let checkAddressNameDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(checkAddressNameDisposable)
|
||||||
|
|
||||||
let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
||||||
|
|
||||||
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
let uploadedAvatar = Promise<UploadedPeerPhotoData>()
|
||||||
@ -429,8 +586,8 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
}, done: {
|
}, done: {
|
||||||
let (creating, title, location) = stateValue.with { state -> (Bool, String, PeerGeoLocation?) in
|
let (creating, title, location, publicLink) = stateValue.with { state -> (Bool, String, PeerGeoLocation?, String?) in
|
||||||
return (state.creating, state.editingName.composedTitle, state.location)
|
return (state.creating, state.editingName.composedTitle, state.location, state.editingPublicLinkText)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !creating && !title.isEmpty {
|
if !creating && !title.isEmpty {
|
||||||
@ -447,7 +604,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
let autoremoveTimeout = stateValue.with({ $0 }).autoremoveTimeout ?? globalAutoremoveTimeout
|
let autoremoveTimeout = stateValue.with({ $0 }).autoremoveTimeout ?? globalAutoremoveTimeout
|
||||||
let ttlPeriod: Int32? = autoremoveTimeout == 0 ? nil : autoremoveTimeout
|
let ttlPeriod: Int32? = autoremoveTimeout == 0 ? nil : autoremoveTimeout
|
||||||
|
|
||||||
let createSignal: Signal<PeerId?, CreateGroupError>
|
var createSignal: Signal<PeerId?, CreateGroupError>
|
||||||
switch mode {
|
switch mode {
|
||||||
case .generic:
|
case .generic:
|
||||||
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod)
|
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod)
|
||||||
@ -496,6 +653,81 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .requestPeer(group):
|
||||||
|
var isForum = false
|
||||||
|
if let isForumRequested = group.isForum, isForumRequested {
|
||||||
|
isForum = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isForum {
|
||||||
|
createSignal = context.engine.peers.createSupergroup(title: title, description: nil, isForum: true)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> mapError { error -> CreateGroupError in
|
||||||
|
switch error {
|
||||||
|
case .generic:
|
||||||
|
return .generic
|
||||||
|
case .restricted:
|
||||||
|
return .restricted
|
||||||
|
case .tooMuchJoined:
|
||||||
|
return .tooMuchJoined
|
||||||
|
case .tooMuchLocationBasedGroups:
|
||||||
|
return .tooMuchLocationBasedGroups
|
||||||
|
case let .serverProvided(error):
|
||||||
|
return .serverProvided(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let publicLink, !publicLink.isEmpty {
|
||||||
|
createSignal = createSignal
|
||||||
|
|> mapToSignal { peerId in
|
||||||
|
if let peerId = peerId {
|
||||||
|
return context.engine.peers.updateAddressName(domain: .peer(peerId), name: publicLink)
|
||||||
|
|> mapError { _ in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> map { _ in
|
||||||
|
return peerId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let publicLink, !publicLink.isEmpty {
|
||||||
|
createSignal = context.engine.peers.createSupergroup(title: title, description: nil)
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> mapError { error -> CreateGroupError in
|
||||||
|
switch error {
|
||||||
|
case .generic:
|
||||||
|
return .generic
|
||||||
|
case .restricted:
|
||||||
|
return .restricted
|
||||||
|
case .tooMuchJoined:
|
||||||
|
return .tooMuchJoined
|
||||||
|
case .tooMuchLocationBasedGroups:
|
||||||
|
return .tooMuchLocationBasedGroups
|
||||||
|
case let .serverProvided(error):
|
||||||
|
return .serverProvided(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { peerId in
|
||||||
|
if let peerId = peerId {
|
||||||
|
return context.engine.peers.updateAddressName(domain: .peer(peerId), name: publicLink)
|
||||||
|
|> mapError { _ in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> map { _ in
|
||||||
|
return peerId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsDisposable.add((createSignal
|
actionsDisposable.add((createSignal
|
||||||
@ -910,8 +1142,42 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
presentInGlobalOverlay?(contextController)
|
presentInGlobalOverlay?(contextController)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}, updatePublicLinkText: { text in
|
||||||
|
if text.isEmpty {
|
||||||
|
checkAddressNameDisposable.set(nil)
|
||||||
|
updateState { state in
|
||||||
|
var updated = state
|
||||||
|
updated.editingPublicLinkText = text
|
||||||
|
updated.addressNameValidationStatus = nil
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateState { state in
|
||||||
|
var updated = state
|
||||||
|
updated.editingPublicLinkText = text
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: .peer(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(0))), name: text)
|
||||||
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
|
updateState { state in
|
||||||
|
var updated = state
|
||||||
|
updated.addressNameValidationStatus = result
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, openAuction: { username in
|
||||||
|
endEditingImpl?()
|
||||||
|
|
||||||
|
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var requestPeer: ReplyMarkupButtonRequestPeerType.Group?
|
||||||
|
if case let .requestPeer(peerType) = mode {
|
||||||
|
requestPeer = peerType
|
||||||
|
}
|
||||||
|
|
||||||
let signal = combineLatest(queue: .mainQueue(),
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
context.sharedContext.presentationData,
|
context.sharedContext.presentationData,
|
||||||
statePromise.get(),
|
statePromise.get(),
|
||||||
@ -921,7 +1187,6 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.GlobalAutoremoveTimeout())
|
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.GlobalAutoremoveTimeout())
|
||||||
)
|
)
|
||||||
|> map { presentationData, state, view, address, venues, globalAutoremoveTimeout -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> map { presentationData, state, view, address, venues, globalAutoremoveTimeout -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||||
|
|
||||||
let rightNavigationButton: ItemListNavigationButton
|
let rightNavigationButton: ItemListNavigationButton
|
||||||
if state.creating {
|
if state.creating {
|
||||||
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
@ -932,7 +1197,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Compose_NewGroupTitle), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view, venues: venues, globalAutoremoveTimeout: globalAutoremoveTimeout ?? 0), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGroupEntries(presentationData: presentationData, state: state, peerIds: peerIds, view: view, venues: venues, globalAutoremoveTimeout: globalAutoremoveTimeout ?? 0, requestPeer: requestPeer), style: .blocks, focusItemTag: CreateGroupEntryTag.info)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
@ -941,6 +1206,9 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controller = ItemListController(context: context, state: signal)
|
let controller = ItemListController(context: context, state: signal)
|
||||||
|
controller.beganInteractiveDragging = {
|
||||||
|
endEditingImpl?()
|
||||||
|
}
|
||||||
replaceControllerImpl = { [weak controller] value in
|
replaceControllerImpl = { [weak controller] value in
|
||||||
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)
|
(controller?.navigationController as? NavigationController)?.replaceAllButRootController(value, animated: true)
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openWebView: { _, _, _, _ in
|
}, openWebView: { _, _, _, _ in
|
||||||
}, activateAdAction: { _ in
|
}, activateAdAction: { _ in
|
||||||
|
}, openRequestedPeerSelection: { _, _, _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
@ -2728,6 +2728,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openWebView: { _, _, _, _ in
|
}, openWebView: { _, _, _, _ in
|
||||||
}, activateAdAction: { _ in
|
}, activateAdAction: { _ in
|
||||||
|
}, openRequestedPeerSelection: { _, _, _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
@ -3094,7 +3095,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.firstName != firstName || peer.lastName != lastName || (bio != nil && bio != cachedData.about) {
|
if (peer.firstName ?? "") != firstName || (peer.lastName ?? "") != lastName || (bio ?? "") != (cachedData.about ?? "") {
|
||||||
var updateNameSignal: Signal<Void, NoError> = .complete()
|
var updateNameSignal: Signal<Void, NoError> = .complete()
|
||||||
var hasProgress = false
|
var hasProgress = false
|
||||||
if peer.firstName != firstName || peer.lastName != lastName {
|
if peer.firstName != firstName || peer.lastName != lastName {
|
||||||
|
@ -62,6 +62,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
private let pretendPresentedInModal: Bool
|
private let pretendPresentedInModal: Bool
|
||||||
private let forwardedMessageIds: [EngineMessage.Id]
|
private let forwardedMessageIds: [EngineMessage.Id]
|
||||||
private let hasTypeHeaders: Bool
|
private let hasTypeHeaders: Bool
|
||||||
|
private let requestPeerType: ReplyMarkupButtonRequestPeerType?
|
||||||
|
|
||||||
override public var _presentedInModal: Bool {
|
override public var _presentedInModal: Bool {
|
||||||
get {
|
get {
|
||||||
@ -93,12 +94,29 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
self.forwardedMessageIds = params.forwardedMessageIds
|
self.forwardedMessageIds = params.forwardedMessageIds
|
||||||
self.hasTypeHeaders = params.hasTypeHeaders
|
self.hasTypeHeaders = params.hasTypeHeaders
|
||||||
self.selectForumThreads = params.selectForumThreads
|
self.selectForumThreads = params.selectForumThreads
|
||||||
|
self.requestPeerType = params.requestPeerType
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||||
|
|
||||||
self.customTitle = params.title
|
self.customTitle = params.title
|
||||||
|
|
||||||
|
if let peerType = params.requestPeerType {
|
||||||
|
switch peerType {
|
||||||
|
case let .user(user):
|
||||||
|
if let isBot = user.isBot, isBot {
|
||||||
|
self.customTitle = self.presentationData.strings.RequestPeer_ChooseBotTitle
|
||||||
|
} else {
|
||||||
|
self.customTitle = self.presentationData.strings.RequestPeer_ChooseUserTitle
|
||||||
|
}
|
||||||
|
case .group:
|
||||||
|
self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle
|
||||||
|
case .channel:
|
||||||
|
self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle
|
self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle
|
||||||
|
|
||||||
if params.forumPeerId == nil {
|
if params.forumPeerId == nil {
|
||||||
@ -159,7 +177,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = PeerSelectionControllerNode(context: self.context, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
|
self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}, presentInGlobalOverlay: { [weak self] c, a in
|
}, presentInGlobalOverlay: { [weak self] c, a in
|
||||||
self?.presentInGlobalOverlay(c, with: a)
|
self?.presentInGlobalOverlay(c, with: a)
|
||||||
|
@ -18,9 +18,13 @@ import ChatSendMessageActionUI
|
|||||||
import ChatTextLinkEditUI
|
import ChatTextLinkEditUI
|
||||||
import AnimationCache
|
import AnimationCache
|
||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
import SolidRoundedButtonNode
|
||||||
|
|
||||||
final class PeerSelectionControllerNode: ASDisplayNode {
|
final class PeerSelectionControllerNode: ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private weak var controller: PeerSelectionController?
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
private let presentInGlobalOverlay: (ViewController, Any?) -> Void
|
private let presentInGlobalOverlay: (ViewController, Any?) -> Void
|
||||||
private let dismiss: () -> Void
|
private let dismiss: () -> Void
|
||||||
@ -29,6 +33,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
private let hasGlobalSearch: Bool
|
private let hasGlobalSearch: Bool
|
||||||
private let forwardedMessageIds: [EngineMessage.Id]
|
private let forwardedMessageIds: [EngineMessage.Id]
|
||||||
private let hasTypeHeaders: Bool
|
private let hasTypeHeaders: Bool
|
||||||
|
private let requestPeerType: ReplyMarkupButtonRequestPeerType?
|
||||||
|
|
||||||
private var presentationInterfaceState: ChatPresentationInterfaceState
|
private var presentationInterfaceState: ChatPresentationInterfaceState
|
||||||
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||||
@ -41,6 +46,16 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
var navigationBar: NavigationBar?
|
var navigationBar: NavigationBar?
|
||||||
|
|
||||||
|
private let requirementsBackgroundNode: NavigationBackgroundNode?
|
||||||
|
private let requirementsSeparatorNode: ASDisplayNode?
|
||||||
|
private let requirementsTextNode: ImmediateTextNode?
|
||||||
|
|
||||||
|
private let emptyAnimationNode: AnimatedStickerNode
|
||||||
|
private var emptyAnimationSize = CGSize()
|
||||||
|
private let emptyTitleNode: ImmediateTextNode
|
||||||
|
private let emptyTextNode: ImmediateTextNode
|
||||||
|
private let emptyButtonNode: SolidRoundedButtonNode
|
||||||
|
|
||||||
private let toolbarBackgroundNode: NavigationBackgroundNode?
|
private let toolbarBackgroundNode: NavigationBackgroundNode?
|
||||||
private let toolbarSeparatorNode: ASDisplayNode?
|
private let toolbarSeparatorNode: ASDisplayNode?
|
||||||
private let segmentedControlNode: SegmentedControlNode?
|
private let segmentedControlNode: SegmentedControlNode?
|
||||||
@ -83,12 +98,15 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
return self.readyValue.get()
|
return self.readyValue.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isEmpty = false
|
||||||
|
|
||||||
private var updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) {
|
private var updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) {
|
||||||
return (self.presentationData, self.presentationDataPromise.get())
|
return (self.presentationData, self.presentationDataPromise.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: EnginePeer.Id?, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
init(context: AccountContext, controller: PeerSelectionController, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: EnginePeer.Id?, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: ReplyMarkupButtonRequestPeerType?, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.controller = controller
|
||||||
self.present = present
|
self.present = present
|
||||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
@ -97,6 +115,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
self.hasGlobalSearch = hasGlobalSearch
|
self.hasGlobalSearch = hasGlobalSearch
|
||||||
self.forwardedMessageIds = forwardedMessageIds
|
self.forwardedMessageIds = forwardedMessageIds
|
||||||
self.hasTypeHeaders = hasTypeHeaders
|
self.hasTypeHeaders = hasTypeHeaders
|
||||||
|
self.requestPeerType = requestPeerType
|
||||||
|
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
@ -107,6 +126,43 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(forwardedMessageIds) }
|
self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(forwardedMessageIds) }
|
||||||
|
|
||||||
|
if let _ = self.requestPeerType {
|
||||||
|
self.requirementsBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
||||||
|
self.requirementsSeparatorNode = ASDisplayNode()
|
||||||
|
self.requirementsSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||||
|
self.requirementsTextNode = ImmediateTextNode()
|
||||||
|
self.requirementsTextNode?.maximumNumberOfLines = 0
|
||||||
|
self.requirementsTextNode?.lineSpacing = 0.1
|
||||||
|
} else {
|
||||||
|
self.requirementsBackgroundNode = nil
|
||||||
|
self.requirementsSeparatorNode = nil
|
||||||
|
self.requirementsTextNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emptyTitleNode = ImmediateTextNode()
|
||||||
|
self.emptyTitleNode.displaysAsynchronously = false
|
||||||
|
self.emptyTitleNode.maximumNumberOfLines = 0
|
||||||
|
self.emptyTitleNode.isHidden = true
|
||||||
|
self.emptyTitleNode.textAlignment = .center
|
||||||
|
self.emptyTitleNode.lineSpacing = 0.25
|
||||||
|
|
||||||
|
self.emptyTextNode = ImmediateTextNode()
|
||||||
|
self.emptyTextNode.displaysAsynchronously = false
|
||||||
|
self.emptyTextNode.maximumNumberOfLines = 0
|
||||||
|
self.emptyTextNode.isHidden = true
|
||||||
|
self.emptyTextNode.lineSpacing = 0.25
|
||||||
|
|
||||||
|
self.emptyAnimationNode = DefaultAnimatedStickerNodeImpl()
|
||||||
|
self.emptyAnimationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListNoResults"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||||
|
self.emptyAnimationNode.isHidden = true
|
||||||
|
self.emptyAnimationSize = CGSize(width: 120.0, height: 120.0)
|
||||||
|
|
||||||
|
self.emptyButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), cornerRadius: 11.0, gloss: true)
|
||||||
|
self.emptyButtonNode.isHidden = true
|
||||||
|
self.emptyButtonNode.pressed = {
|
||||||
|
createNewGroup?()
|
||||||
|
}
|
||||||
|
|
||||||
if hasChatListSelector && hasContactSelector {
|
if hasChatListSelector && hasContactSelector {
|
||||||
self.toolbarBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
self.toolbarBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
|
||||||
|
|
||||||
@ -137,7 +193,14 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
chatListLocation = .chatList(groupId: .root)
|
chatListLocation = .chatList(groupId: .root)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil, displayAutoremoveTimeout: false), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
|
let chatListMode: ChatListNodeMode
|
||||||
|
if let requestPeerType = self.requestPeerType {
|
||||||
|
chatListMode = .peerType(type: requestPeerType)
|
||||||
|
} else {
|
||||||
|
chatListMode = .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil, displayAutoremoveTimeout: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: chatListMode, theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -184,6 +247,17 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
return self?.contentScrollingEnded?(listView) ?? false
|
return self?.contentScrollingEnded?(listView) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.chatListNode.isEmptyUpdated = { [weak self] state, _, _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case .empty(false, _) = state, let (layout, navigationBarHeight, actualNavigationBarHeight) = strongSelf.containerLayout {
|
||||||
|
strongSelf.isEmpty = true
|
||||||
|
strongSelf.controller?.navigationBar?.setContentNode(nil, animated: false)
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubnode(self.chatListNode)
|
self.addSubnode(self.chatListNode)
|
||||||
|
|
||||||
if hasChatListSelector && hasContactSelector {
|
if hasChatListSelector && hasContactSelector {
|
||||||
@ -196,6 +270,17 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.segmentedControlNode!)
|
self.addSubnode(self.segmentedControlNode!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let requirementsBackgroundNode = self.requirementsBackgroundNode, let requirementsSeparatorNode = self.requirementsSeparatorNode, let requirementsTextNode = self.requirementsTextNode {
|
||||||
|
self.chatListNode.addSubnode(requirementsBackgroundNode)
|
||||||
|
self.chatListNode.addSubnode(requirementsSeparatorNode)
|
||||||
|
self.chatListNode.addSubnode(requirementsTextNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.emptyAnimationNode)
|
||||||
|
self.addSubnode(self.emptyTitleNode)
|
||||||
|
self.addSubnode(self.emptyTextNode)
|
||||||
|
self.addSubnode(self.emptyButtonNode)
|
||||||
|
}
|
||||||
|
|
||||||
if !hasChatListSelector && hasContactSelector {
|
if !hasChatListSelector && hasContactSelector {
|
||||||
self.indexChanged(1)
|
self.indexChanged(1)
|
||||||
}
|
}
|
||||||
@ -484,6 +569,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.updateChatPresentationInterfaceState({ $0.updatedTheme(self.presentationData.theme) })
|
self.updateChatPresentationInterfaceState({ $0.updatedTheme(self.presentationData.theme) })
|
||||||
|
|
||||||
|
self.requirementsBackgroundNode?.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||||
self.toolbarBackgroundNode?.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
self.toolbarBackgroundNode?.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||||
self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||||
self.segmentedControlNode?.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
|
self.segmentedControlNode?.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
|
||||||
@ -574,6 +660,113 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||||
self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||||
|
|
||||||
|
if let requestPeerType = self.requestPeerType {
|
||||||
|
if self.isEmpty {
|
||||||
|
self.chatListNode.isHidden = true
|
||||||
|
self.requirementsBackgroundNode?.isHidden = true
|
||||||
|
self.requirementsTextNode?.isHidden = true
|
||||||
|
self.requirementsSeparatorNode?.isHidden = true
|
||||||
|
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||||
|
|
||||||
|
var emptyTitle: String
|
||||||
|
var emptyText: String
|
||||||
|
var emptyButtonText: String
|
||||||
|
switch requestPeerType {
|
||||||
|
case let .user(user):
|
||||||
|
if let isBot = user.isBot, isBot {
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_BotsAllEmpty
|
||||||
|
emptyText = ""
|
||||||
|
} else {
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_UsersAllEmpty
|
||||||
|
if let text = stringForRequestPeerType(strings: self.presentationData.strings, peerType: requestPeerType, offset: false) {
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_UsersEmpty
|
||||||
|
emptyText = text
|
||||||
|
} else {
|
||||||
|
emptyText = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emptyButtonText = ""
|
||||||
|
case .group:
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_GroupsAllEmpty
|
||||||
|
if let text = stringForRequestPeerType(strings: self.presentationData.strings, peerType: requestPeerType, offset: false) {
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_GroupsEmpty
|
||||||
|
emptyText = text
|
||||||
|
} else {
|
||||||
|
emptyText = ""
|
||||||
|
}
|
||||||
|
emptyButtonText = self.presentationData.strings.RequestPeer_CreateNewGroup
|
||||||
|
case .channel:
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_ChannelsEmpty
|
||||||
|
if let text = stringForRequestPeerType(strings: self.presentationData.strings, peerType: requestPeerType, offset: false) {
|
||||||
|
emptyTitle = self.presentationData.strings.RequestPeer_ChannelsEmpty
|
||||||
|
emptyText = text
|
||||||
|
} else {
|
||||||
|
emptyText = ""
|
||||||
|
}
|
||||||
|
emptyButtonText = self.presentationData.strings.RequestPeer_CreateNewGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emptyTitleNode.attributedText = NSAttributedString(string: emptyTitle, font: Font.semibold(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
|
self.emptyTextNode.attributedText = NSAttributedString(string: emptyText, font: Font.regular(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||||
|
|
||||||
|
let padding: CGFloat = 44.0
|
||||||
|
let emptyTitleSize = self.emptyTitleNode.updateLayout(CGSize(width: layout.size.width - insets.left * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
let emptyTextSize = self.emptyTextNode.updateLayout(CGSize(width: layout.size.width - insets.left * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
let emptyAnimationHeight = self.emptyAnimationSize.height
|
||||||
|
let emptyAnimationSpacing: CGFloat = 12.0
|
||||||
|
let emptyTextSpacing: CGFloat = 17.0
|
||||||
|
var emptyButtonSpacing: CGFloat = 15.0
|
||||||
|
var emptyButtonHeight: CGFloat = 50.0
|
||||||
|
if emptyButtonText.isEmpty {
|
||||||
|
emptyButtonSpacing = 0.0
|
||||||
|
emptyButtonHeight = 0.0
|
||||||
|
}
|
||||||
|
let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing + emptyButtonSpacing + emptyButtonHeight
|
||||||
|
let emptyAnimationY = floorToScreenPixels((layout.size.height - emptyTotalHeight) / 2.0)
|
||||||
|
|
||||||
|
if !emptyButtonText.isEmpty {
|
||||||
|
let buttonPadding: CGFloat = 30.0
|
||||||
|
self.emptyButtonNode.title = emptyButtonText
|
||||||
|
self.emptyButtonNode.isHidden = false
|
||||||
|
let emptyButtonWidth = layout.size.width - insets.left - insets.right - buttonPadding * 2.0
|
||||||
|
let _ = self.emptyButtonNode.updateLayout(width: emptyButtonWidth, transition: transition)
|
||||||
|
transition.updateFrame(node: self.emptyButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - emptyButtonWidth) / 2.0), y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing + emptyTextSize.height + emptyButtonSpacing), size: CGSize(width: emptyButtonWidth, height: emptyButtonHeight)))
|
||||||
|
} else {
|
||||||
|
self.emptyButtonNode.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let textTransition = ContainedViewLayoutTransition.immediate
|
||||||
|
textTransition.updateFrame(node: self.emptyAnimationNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - self.emptyAnimationSize.width) / 2.0), y: emptyAnimationY), size: self.emptyAnimationSize))
|
||||||
|
textTransition.updateFrame(node: self.emptyTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - emptyTitleSize.width) / 2.0), y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing), size: emptyTitleSize))
|
||||||
|
textTransition.updateFrame(node: self.emptyTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - emptyTextSize.width) / 2.0), y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
|
||||||
|
self.emptyAnimationNode.updateLayout(size: self.emptyAnimationSize)
|
||||||
|
|
||||||
|
self.emptyAnimationNode.isHidden = false
|
||||||
|
self.emptyTitleNode.isHidden = false
|
||||||
|
self.emptyTextNode.isHidden = false
|
||||||
|
self.emptyAnimationNode.visibility = true
|
||||||
|
} else if let requirementsBackgroundNode = self.requirementsBackgroundNode, let requirementsSeparatorNode = self.requirementsSeparatorNode, let requirementsTextNode = self.requirementsTextNode, let requirementsText = stringForRequestPeerType(strings: self.presentationData.strings, peerType: requestPeerType, offset: true) {
|
||||||
|
let requirements = NSMutableAttributedString(string: self.presentationData.strings.RequestPeer_Requirements + "\n", font: Font.semibold(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
requirements.append(NSAttributedString(string: requirementsText, font: Font.regular(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor))
|
||||||
|
|
||||||
|
requirementsTextNode.attributedText = requirements
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
let verticalInset: CGFloat = 11.0
|
||||||
|
let requirementsSize = requirementsTextNode.updateLayout(CGSize(width: layout.size.width - insets.left - insets.right - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
let requirementsBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: actualNavigationBarHeight), size: CGSize(width: layout.size.width, height: requirementsSize.height + verticalInset * 2.0))
|
||||||
|
insets.top += requirementsBackgroundFrame.height
|
||||||
|
|
||||||
|
requirementsBackgroundNode.update(size: requirementsBackgroundFrame.size, transition: transition)
|
||||||
|
transition.updateFrame(node: requirementsBackgroundNode, frame: requirementsBackgroundFrame)
|
||||||
|
|
||||||
|
transition.updateFrame(node: requirementsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: requirementsBackgroundFrame.maxY - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
requirementsTextNode.frame = CGRect(origin: CGPoint(x: insets.left + sideInset, y: requirementsBackgroundFrame.minY + verticalInset), size: requirementsSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve)
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve)
|
||||||
|
|
||||||
@ -614,6 +807,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
animationRenderer: self.animationRenderer,
|
animationRenderer: self.animationRenderer,
|
||||||
updatedPresentationData: self.updatedPresentationData,
|
updatedPresentationData: self.updatedPresentationData,
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
|
requestPeerType: self.requestPeerType,
|
||||||
location: chatListLocation,
|
location: chatListLocation,
|
||||||
displaySearchFilters: false,
|
displaySearchFilters: false,
|
||||||
hasDownloads: false,
|
hasDownloads: false,
|
||||||
@ -865,3 +1059,100 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func stringForRequestPeerType(strings: PresentationStrings, peerType: ReplyMarkupButtonRequestPeerType, offset: Bool) -> String? {
|
||||||
|
var lines: [String] = []
|
||||||
|
|
||||||
|
func append(_ string: String) {
|
||||||
|
if offset {
|
||||||
|
lines.append(" • \(string)")
|
||||||
|
} else {
|
||||||
|
lines.append("• \(string)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch peerType {
|
||||||
|
case let .user(user):
|
||||||
|
if let isPremium = user.isPremium {
|
||||||
|
if isPremium {
|
||||||
|
append(strings.RequestPeer_Requirement_UserPremiumOn)
|
||||||
|
} else {
|
||||||
|
append(strings.RequestPeer_Requirement_UserPremiumOff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .group(group):
|
||||||
|
if group.isCreator {
|
||||||
|
append(strings.RequestPeer_Requirement_Group_CreatorOn)
|
||||||
|
}
|
||||||
|
if let hasUsername = group.hasUsername {
|
||||||
|
if hasUsername {
|
||||||
|
append(strings.RequestPeer_Requirement_Group_HasUsernameOn)
|
||||||
|
} else {
|
||||||
|
append(strings.RequestPeer_Requirement_Group_HasUsernameOff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let isForum = group.isForum {
|
||||||
|
if isForum {
|
||||||
|
append(strings.RequestPeer_Requirement_Group_ForumOn)
|
||||||
|
} else {
|
||||||
|
append(strings.RequestPeer_Requirement_Group_ForumOff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let adminRights = group.userAdminRights {
|
||||||
|
var rights: [String] = []
|
||||||
|
if adminRights.rights.contains(.canChangeInfo) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Info)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canPostMessages) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Send)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canDeleteMessages) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Delete)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canEditMessages) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Edit)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canBanUsers) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Ban)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canInviteUsers) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Invite)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canPinMessages) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Pin)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canManageTopics) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Topics)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canManageCalls) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_VideoChats)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canBeAnonymous) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_Anonymous)
|
||||||
|
}
|
||||||
|
if adminRights.rights.contains(.canAddAdmins) {
|
||||||
|
rights.append(strings.RequestPeer_Requirement_Group_Rights_AddAdmins)
|
||||||
|
}
|
||||||
|
if !rights.isEmpty {
|
||||||
|
let rightsString = strings.RequestPeer_Requirement_Group_Rights(String(rights.joined(separator: ", "))).string
|
||||||
|
append(rightsString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .channel(channel):
|
||||||
|
if channel.isCreator {
|
||||||
|
append(strings.RequestPeer_Requirement_Channel_CreatorOn)
|
||||||
|
}
|
||||||
|
if let hasUsername = channel.hasUsername {
|
||||||
|
if hasUsername {
|
||||||
|
append(strings.RequestPeer_Requirement_Channel_HasUsernameOn)
|
||||||
|
} else {
|
||||||
|
append(strings.RequestPeer_Requirement_Channel_HasUsernameOff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lines.isEmpty {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return String(lines.joined(separator: "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1379,6 +1379,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
}, openWebView: { _, _, _, _ in
|
}, openWebView: { _, _, _, _ in
|
||||||
}, activateAdAction: { _ in
|
}, activateAdAction: { _ in
|
||||||
|
}, openRequestedPeerSelection: { _, _, _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
@ -41,7 +41,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var enableDebugDataDisplay: Bool
|
public var enableDebugDataDisplay: Bool
|
||||||
public var acceleratedStickers: Bool
|
public var acceleratedStickers: Bool
|
||||||
public var experimentalBackground: Bool
|
public var experimentalBackground: Bool
|
||||||
public var snow: Bool
|
|
||||||
public var inlineStickers: Bool
|
public var inlineStickers: Bool
|
||||||
public var localTranscription: Bool
|
public var localTranscription: Bool
|
||||||
public var enableReactionOverrides: Bool
|
public var enableReactionOverrides: Bool
|
||||||
@ -49,6 +48,9 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var accountReactionEffectOverrides: [AccountReactionOverrides]
|
public var accountReactionEffectOverrides: [AccountReactionOverrides]
|
||||||
public var accountStickerEffectOverrides: [AccountReactionOverrides]
|
public var accountStickerEffectOverrides: [AccountReactionOverrides]
|
||||||
public var disableQuickReaction: Bool
|
public var disableQuickReaction: Bool
|
||||||
|
public var disableLanguageRecognition: Bool
|
||||||
|
public var disableImageContentAnalysis: Bool
|
||||||
|
public var disableBackgroundAnimation: Bool
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -67,14 +69,16 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
enableDebugDataDisplay: false,
|
enableDebugDataDisplay: false,
|
||||||
acceleratedStickers: false,
|
acceleratedStickers: false,
|
||||||
experimentalBackground: false,
|
experimentalBackground: false,
|
||||||
snow: false,
|
|
||||||
inlineStickers: false,
|
inlineStickers: false,
|
||||||
localTranscription: false,
|
localTranscription: false,
|
||||||
enableReactionOverrides: false,
|
enableReactionOverrides: false,
|
||||||
inlineForums: false,
|
inlineForums: false,
|
||||||
accountReactionEffectOverrides: [],
|
accountReactionEffectOverrides: [],
|
||||||
accountStickerEffectOverrides: [],
|
accountStickerEffectOverrides: [],
|
||||||
disableQuickReaction: false
|
disableQuickReaction: false,
|
||||||
|
disableLanguageRecognition: false,
|
||||||
|
disableImageContentAnalysis: false,
|
||||||
|
disableBackgroundAnimation: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,14 +98,16 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
enableDebugDataDisplay: Bool,
|
enableDebugDataDisplay: Bool,
|
||||||
acceleratedStickers: Bool,
|
acceleratedStickers: Bool,
|
||||||
experimentalBackground: Bool,
|
experimentalBackground: Bool,
|
||||||
snow: Bool,
|
|
||||||
inlineStickers: Bool,
|
inlineStickers: Bool,
|
||||||
localTranscription: Bool,
|
localTranscription: Bool,
|
||||||
enableReactionOverrides: Bool,
|
enableReactionOverrides: Bool,
|
||||||
inlineForums: Bool,
|
inlineForums: Bool,
|
||||||
accountReactionEffectOverrides: [AccountReactionOverrides],
|
accountReactionEffectOverrides: [AccountReactionOverrides],
|
||||||
accountStickerEffectOverrides: [AccountReactionOverrides],
|
accountStickerEffectOverrides: [AccountReactionOverrides],
|
||||||
disableQuickReaction: Bool
|
disableQuickReaction: Bool,
|
||||||
|
disableLanguageRecognition: Bool,
|
||||||
|
disableImageContentAnalysis: Bool,
|
||||||
|
disableBackgroundAnimation: Bool
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -118,7 +124,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.enableDebugDataDisplay = enableDebugDataDisplay
|
self.enableDebugDataDisplay = enableDebugDataDisplay
|
||||||
self.acceleratedStickers = acceleratedStickers
|
self.acceleratedStickers = acceleratedStickers
|
||||||
self.experimentalBackground = experimentalBackground
|
self.experimentalBackground = experimentalBackground
|
||||||
self.snow = snow
|
|
||||||
self.inlineStickers = inlineStickers
|
self.inlineStickers = inlineStickers
|
||||||
self.localTranscription = localTranscription
|
self.localTranscription = localTranscription
|
||||||
self.enableReactionOverrides = enableReactionOverrides
|
self.enableReactionOverrides = enableReactionOverrides
|
||||||
@ -126,6 +131,9 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.accountReactionEffectOverrides = accountReactionEffectOverrides
|
self.accountReactionEffectOverrides = accountReactionEffectOverrides
|
||||||
self.accountStickerEffectOverrides = accountStickerEffectOverrides
|
self.accountStickerEffectOverrides = accountStickerEffectOverrides
|
||||||
self.disableQuickReaction = disableQuickReaction
|
self.disableQuickReaction = disableQuickReaction
|
||||||
|
self.disableLanguageRecognition = disableLanguageRecognition
|
||||||
|
self.disableImageContentAnalysis = disableImageContentAnalysis
|
||||||
|
self.disableBackgroundAnimation = disableBackgroundAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -146,7 +154,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
|
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
|
||||||
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
|
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
|
||||||
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
|
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
|
||||||
self.snow = (try container.decodeIfPresent(Int32.self, forKey: "snow") ?? 0) != 0
|
|
||||||
self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0
|
self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0
|
||||||
self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0
|
self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0
|
||||||
self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false
|
self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false
|
||||||
@ -154,6 +161,9 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.accountReactionEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountReactionEffectOverrides")) ?? []
|
self.accountReactionEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountReactionEffectOverrides")) ?? []
|
||||||
self.accountStickerEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountStickerEffectOverrides")) ?? []
|
self.accountStickerEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountStickerEffectOverrides")) ?? []
|
||||||
self.disableQuickReaction = try container.decodeIfPresent(Bool.self, forKey: "disableQuickReaction") ?? false
|
self.disableQuickReaction = try container.decodeIfPresent(Bool.self, forKey: "disableQuickReaction") ?? false
|
||||||
|
self.disableLanguageRecognition = try container.decodeIfPresent(Bool.self, forKey: "disableLanguageRecognition") ?? false
|
||||||
|
self.disableImageContentAnalysis = try container.decodeIfPresent(Bool.self, forKey: "disableImageContentAnalysis") ?? false
|
||||||
|
self.disableBackgroundAnimation = try container.decodeIfPresent(Bool.self, forKey: "disableBackgroundAnimation") ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -174,7 +184,6 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
|
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
|
||||||
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
|
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
|
||||||
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
|
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
|
||||||
try container.encode((self.snow ? 1 : 0) as Int32, forKey: "snow")
|
|
||||||
try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers")
|
try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers")
|
||||||
try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription")
|
try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription")
|
||||||
try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides")
|
try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides")
|
||||||
@ -182,6 +191,9 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
try container.encode(self.accountReactionEffectOverrides, forKey: "accountReactionEffectOverrides")
|
try container.encode(self.accountReactionEffectOverrides, forKey: "accountReactionEffectOverrides")
|
||||||
try container.encode(self.accountStickerEffectOverrides, forKey: "accountStickerEffectOverrides")
|
try container.encode(self.accountStickerEffectOverrides, forKey: "accountStickerEffectOverrides")
|
||||||
try container.encode(self.disableQuickReaction, forKey: "disableQuickReaction")
|
try container.encode(self.disableQuickReaction, forKey: "disableQuickReaction")
|
||||||
|
try container.encode(self.disableLanguageRecognition, forKey: "disableLanguageRecognition")
|
||||||
|
try container.encode(self.disableImageContentAnalysis, forKey: "disableImageContentAnalysis")
|
||||||
|
try container.encode(self.disableBackgroundAnimation, forKey: "disableBackgroundAnimation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +147,10 @@ public func canTranslateText(context: AccountContext, text: String, showTranslat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 12.0, *) {
|
if #available(iOS 12.0, *) {
|
||||||
|
if context.sharedContext.immediateExperimentalUISettings.disableLanguageRecognition {
|
||||||
|
return (true, nil)
|
||||||
|
}
|
||||||
|
|
||||||
var dontTranslateLanguages: [String] = []
|
var dontTranslateLanguages: [String] = []
|
||||||
if let ignoredLanguages = ignoredLanguages {
|
if let ignoredLanguages = ignoredLanguages {
|
||||||
dontTranslateLanguages = ignoredLanguages
|
dontTranslateLanguages = ignoredLanguages
|
||||||
|
Loading…
x
Reference in New Issue
Block a user