[WIP] Chat Import

This commit is contained in:
Ali 2021-01-21 00:23:07 +04:00
parent da2faa5967
commit fe491b6831
14 changed files with 3747 additions and 3669 deletions

View File

@ -1130,6 +1130,7 @@
"ShareFileTip.CloseTip" = "Close Tip";
"DialogList.SearchSectionDialogs" = "Chats and Contacts";
"DialogList.SearchSectionChats" = "Chats";
"DialogList.SearchSectionGlobal" = "Global Search";
"DialogList.SearchSectionMessages" = "Messages";
@ -5900,3 +5901,5 @@ Sorry for the inconvenience.";
"DialogList.MultipleTypingPair" = "%@ and %@ are typing";
"Common.Save" = "Save";
"ChatList.HeaderImportIntoAnExistingGroup" = "OR IMPORT INTO AN EXISTING GROUP";

View File

@ -34,18 +34,22 @@ public final class PeerSelectionControllerParams {
public let filter: ChatListNodePeersFilter
public let hasChatListSelector: Bool
public let hasContactSelector: Bool
public let hasGlobalSearch: Bool
public let title: String?
public let attemptSelection: ((Peer) -> Void)?
public let createNewGroup: (() -> Void)?
public let pretendPresentedInModal: Bool
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil) {
public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false) {
self.context = context
self.filter = filter
self.hasChatListSelector = hasChatListSelector
self.hasContactSelector = hasContactSelector
self.hasGlobalSearch = hasGlobalSearch
self.title = title
self.attemptSelection = attemptSelection
self.createNewGroup = createNewGroup
self.pretendPresentedInModal = pretendPresentedInModal
}
}

View File

@ -120,13 +120,13 @@ public final class ChatImportActivityScreen: ViewController {
//TODO:localize
let iconSize = CGSize(width: 170.0, height: 170.0)
let radialStatusSize = CGSize(width: 180.0, height: 180.0)
let radialStatusSize = CGSize(width: 186.0, height: 186.0)
let maxIconStatusSpacing: CGFloat = 62.0
let maxProgressTextSpacing: CGFloat = 32.0
let progressStatusSpacing: CGFloat = 16.0
let statusButtonSpacing: CGFloat = 16.0
let maxProgressTextSpacing: CGFloat = 33.0
let progressStatusSpacing: CGFloat = 14.0
let statusButtonSpacing: CGFloat = 19.0
self.radialStatusText.attributedText = NSAttributedString(string: "\(Int(self.totalProgress * 100.0))%", font: Font.with(size: 42.0, design: .round, traits: []), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.radialStatusText.attributedText = NSAttributedString(string: "\(Int(self.totalProgress * 100.0))%", font: Font.with(size: 42.0, design: .round, weight: .semibold), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
let radialStatusTextSize = self.radialStatusText.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
self.progressText.attributedText = NSAttributedString(string: "\(dataSizeString(Int(self.totalProgress * CGFloat(self.totalBytes)))) of \(dataSizeString(Int(1.0 * CGFloat(self.totalBytes))))", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
@ -207,6 +207,13 @@ public final class ChatImportActivityScreen: ViewController {
private let disposable = MetaDisposable()
override public var _presentedInModal: Bool {
get {
return true
} set(value) {
}
}
public init(context: AccountContext, cancel: @escaping () -> Void, peerId: PeerId, archive: Archive, mainEntry: TempBoxFile, otherEntries: [(Entry, String, ChatHistoryImport.MediaType)]) {
self.context = context
self.cancel = cancel

View File

@ -25,6 +25,7 @@ public enum ChatListSearchItemHeaderType {
case groupMembers
case activeVoiceChats
case recentCalls
case orImportIntoAnExistingGroup
fileprivate func title(strings: PresentationStrings) -> String {
switch self {
@ -68,6 +69,8 @@ public enum ChatListSearchItemHeaderType {
return strings.CallList_ActiveVoiceChatsHeader
case .recentCalls:
return strings.CallList_RecentCallsHeader
case .orImportIntoAnExistingGroup:
return strings.ChatList_HeaderImportIntoAnExistingGroup
}
}
@ -113,6 +116,8 @@ public enum ChatListSearchItemHeaderType {
return .activeVoiceChats
case .recentCalls:
return .recentCalls
case .orImportIntoAnExistingGroup:
return .orImportIntoAnExistingGroup
}
}
}
@ -142,6 +147,7 @@ private enum ChatListSearchItemHeaderId: Int32 {
case groupMembers
case activeVoiceChats
case recentCalls
case orImportIntoAnExistingGroup
}
public final class ChatListSearchItemHeader: ListViewItemHeader {

View File

@ -429,7 +429,13 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case .collapse:
actionTitle = strings.ChatList_Search_ShowLess
}
header = ChatListSearchItemHeader(type: .localPeers, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : {
let headerType: ChatListSearchItemHeaderType
if filter.contains(.onlyGroups) {
headerType = .chats
} else {
headerType = .localPeers
}
header = ChatListSearchItemHeader(type: headerType, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : {
toggleExpandLocalResults()
})
}

View File

@ -250,7 +250,14 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
switch mode {
case let .peers(_, _, additionalCategories, _):
if !additionalCategories.isEmpty {
header = ChatListSearchItemHeader(type: .chats, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
let headerType: ChatListSearchItemHeaderType
if case .action = additionalCategories[0].appearance {
// TODO: hack, generalize
headerType = .orImportIntoAnExistingGroup
} else {
headerType = .chats
}
header = ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
}
default:
break
@ -320,7 +327,14 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
switch mode {
case let .peers(_, _, additionalCategories, _):
if !additionalCategories.isEmpty {
header = ChatListSearchItemHeader(type: .chats, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
let headerType: ChatListSearchItemHeaderType
if case .action = additionalCategories[0].appearance {
// TODO: hack, generalize
headerType = .orImportIntoAnExistingGroup
} else {
headerType = .chats
}
header = ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
}
default:
break

View File

@ -33,7 +33,7 @@ public struct Font {
case bold
}
public static func with(size: CGFloat, design: Design = .regular, traits: Traits = []) -> UIFont {
public static func with(size: CGFloat, design: Design = .regular, weight: Weight = .regular, traits: Traits = []) -> UIFont {
if #available(iOS 13.0, *) {
let descriptor = UIFont.systemFont(ofSize: size).fontDescriptor
var symbolicTraits = descriptor.symbolicTraits
@ -63,6 +63,15 @@ public struct Font {
default:
updatedDescriptor = updatedDescriptor?.withDesign(.default)
}
switch weight {
case .semibold:
let fontTraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold]
updatedDescriptor = updatedDescriptor?.addingAttributes([
UIFontDescriptor.AttributeName.traits: fontTraits
])
default:
break
}
if let updatedDescriptor = updatedDescriptor {
return UIFont(descriptor: updatedDescriptor, size: size)
} else {

View File

@ -139,7 +139,7 @@ public enum TabBarItemContextActionType {
}
open var navigationPresentation: ViewControllerNavigationPresentation = .default
var _presentedInModal: Bool = false
open var _presentedInModal: Bool = false
public var presentedOverCoveringView: Bool = false

View File

@ -12,7 +12,7 @@ public enum CreateChannelError {
case serverProvided(String)
}
private func createChannel(account: Account, title: String, description: String?, isSupergroup:Bool, location: (latitude: Double, longitude: Double, address: String)? = nil) -> Signal<PeerId, CreateChannelError> {
private func createChannel(account: Account, title: String, description: String?, isSupergroup:Bool, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal<PeerId, CreateChannelError> {
return account.postbox.transaction { transaction -> Signal<PeerId, CreateChannelError> in
var flags: Int32 = 0
if isSupergroup {
@ -20,6 +20,9 @@ private func createChannel(account: Account, title: String, description: String?
} else {
flags |= (1 << 0)
}
if isForHistoryImport {
flags |= (1 << 3)
}
var geoPoint: Api.InputGeoPoint?
var address: String?
@ -69,8 +72,8 @@ public func createChannel(account: Account, title: String, description: String?)
return createChannel(account: account, title: title, description: description, isSupergroup: false)
}
public func createSupergroup(account: Account, title: String, description: String?, location: (latitude: Double, longitude: Double, address: String)? = nil) -> Signal<PeerId, CreateChannelError> {
return createChannel(account: account, title: title, description: description, isSupergroup: true, location: location)
public func createSupergroup(account: Account, title: String, description: String?, location: (latitude: Double, longitude: Double, address: String)? = nil, isForHistoryImport: Bool = false) -> Signal<PeerId, CreateChannelError> {
return createChannel(account: account, title: title, description: description, isSupergroup: true, location: location, isForHistoryImport: isForHistoryImport)
}
public enum DeleteChannelError {
@ -81,7 +84,7 @@ public func deleteChannel(account: Account, peerId: PeerId) -> Signal<Void, Dele
return account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> mapError { _ -> DeleteChannelError in return .generic }
|> mapError { _ -> DeleteChannelError in }
|> mapToSignal { inputChannel -> Signal<Void, DeleteChannelError> in
if let inputChannel = inputChannel {
return account.network.request(Api.functions.channels.deleteChannel(channel: inputChannel))

View File

@ -56,6 +56,22 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
private let hasChatListSelector: Bool
private let hasContactSelector: Bool
private let hasGlobalSearch: Bool
private let pretendPresentedInModal: Bool
override public var _presentedInModal: Bool {
get {
if self.pretendPresentedInModal {
return true
} else {
return super._presentedInModal
}
} set(value) {
if !self.pretendPresentedInModal {
super._presentedInModal = value
}
}
}
private var searchContentNode: NavigationBarSearchContentNode?
@ -64,9 +80,11 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
self.filter = params.filter
self.hasChatListSelector = params.hasChatListSelector
self.hasContactSelector = params.hasContactSelector
self.hasGlobalSearch = params.hasGlobalSearch
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.attemptSelection = params.attemptSelection
self.createNewGroup = params.createNewGroup
self.pretendPresentedInModal = params.pretendPresentedInModal
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
@ -126,7 +144,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
}
override public func loadDisplayNode() {
self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, dismiss: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)

View File

@ -19,6 +19,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
private let present: (ViewController, Any?) -> Void
private let dismiss: () -> Void
private let filter: ChatListNodePeersFilter
private let hasGlobalSearch: Bool
var inProgress: Bool = false {
didSet {
@ -59,11 +60,12 @@ final class PeerSelectionControllerNode: ASDisplayNode {
return self.readyValue.get()
}
init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
self.context = context
self.present = present
self.dismiss = dismiss
self.filter = filter
self.hasGlobalSearch = hasGlobalSearch
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -264,7 +266,11 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, placeholder: placeholderNode)
} else if let contactListNode = self.contactListNode, contactListNode.supernode != nil {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: [.cloudContacts, .global], addContact: nil, openPeer: { [weak self] peer in
var categories: ContactsSearchCategories = [.cloudContacts]
if self.hasGlobalSearch {
categories.insert(.global)
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: categories, addContact: nil, openPeer: { [weak self] peer in
if let strongSelf = self {
switch peer {
case let .peer(peer, _, _):

View File

@ -489,11 +489,11 @@ public class ShareRootControllerImpl {
//TODO:localize
var attemptSelectionImpl: ((Peer) -> Void)?
var createNewGroupImpl: (() -> Void)?
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled], hasContactSelector: false, title: "Import Chat", attemptSelection: { peer in
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
attemptSelectionImpl?(peer)
}, createNewGroup: {
createNewGroupImpl?()
}))
}, pretendPresentedInModal: true))
controller.customDismiss = {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
@ -547,7 +547,7 @@ public class ShareRootControllerImpl {
createNewGroupImpl = {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Create Group and Import Messages", text: "Are you sure you want to create group **\(groupTitle)** and import messages from another messaging app?", actions: [TextAlertAction(type: .defaultAction, title: "Create and Import", action: {
var signal: Signal<PeerId?, NoError> = createSupergroup(account: context.account, title: groupTitle, description: nil)
var signal: Signal<PeerId?, NoError> = createSupergroup(account: context.account, title: groupTitle, description: nil, isForHistoryImport: true)
|> map(Optional.init)
|> `catch` { _ -> Signal<PeerId?, NoError> in
return .single(nil)
@ -596,9 +596,9 @@ public class ShareRootControllerImpl {
//TODO:localize
var attemptSelectionImpl: ((Peer) -> Void)?
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled], hasChatListSelector: false, hasContactSelector: true, title: "Import Chat", attemptSelection: { peer in
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
attemptSelectionImpl?(peer)
}))
}, pretendPresentedInModal: true))
controller.customDismiss = {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)