From e94e5d90d2f54cc648aa680b611980f9b048d149 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 14:05:59 +0300 Subject: [PATCH] Video avatar fixes --- .../Sources/AvatarGalleryController.swift | 12 +- .../Sources/EditSettingsController.swift | 773 ------------------ .../SettingsUI/Sources/OpenSettings.swift | 57 -- .../Search/SettingsSearchableItems.swift | 15 - .../Sources/SettingsController.swift | 7 - .../TelegramUI/Sources/ChatController.swift | 27 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 71 +- 7 files changed, 55 insertions(+), 907 deletions(-) delete mode 100644 submodules/SettingsUI/Sources/EditSettingsController.swift delete mode 100644 submodules/SettingsUI/Sources/OpenSettings.swift diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index c740e75b2a..39fa7faa48 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -136,7 +136,7 @@ public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal< initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), [], peer, nil, nil, nil)) } - if peer is TelegramChannel || peer is TelegramGroup { + if peer is TelegramChannel || peer is TelegramGroup, let peerReference = PeerReference(peer) { return account.postbox.transaction { transaction in return transaction.getPeerCachedData(peerId: peer.id) } |> map { cachedData in @@ -148,10 +148,14 @@ public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal< initialPhoto = photo } - if let photo = initialPhoto, !photo.videoRepresentations.isEmpty, let peerReference = PeerReference(peer) { - return [.topImage(photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil)] + if let photo = initialPhoto, !photo.videoRepresentations.isEmpty { + return [.topImage(photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), peer, nil, photo.immediateThumbnailData, nil)] } else { - return initialEntries + if !peer.profileImageRepresentations.isEmpty { + return [.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), [], peer, nil, initialPhoto?.immediateThumbnailData, nil)] + } else { + return [] + } } } } else { diff --git a/submodules/SettingsUI/Sources/EditSettingsController.swift b/submodules/SettingsUI/Sources/EditSettingsController.swift deleted file mode 100644 index 8ebbfa7eec..0000000000 --- a/submodules/SettingsUI/Sources/EditSettingsController.swift +++ /dev/null @@ -1,773 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import SwiftSignalKit -import Postbox -import TelegramCore -import SyncCore -import LegacyComponents -import TelegramPresentationData -import ItemListUI -import PresentationDataUtils -import AccountContext -import GalleryUI -import LegacyUI -import ItemListAvatarAndNameInfoItem -import WebSearchUI -import PeerAvatarGalleryUI -import MapResourceToAvatarSizes -import PhoneNumberFormat -import LegacyMediaPickerUI -import LocalMediaResources - -private struct EditSettingsItemArguments { - let context: AccountContext - let accountManager: AccountManager - let avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext - - let avatarTapAction: () -> Void - let setProfilePhoto: () -> Void - - let pushController: (ViewController) -> Void - let presentController: (ViewController) -> Void - let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void - let updateBioText: (String, String) -> Void - let saveEditingState: () -> Void - let addAccount: () -> Void - let logout: () -> Void -} - -private enum SettingsSection: Int32 { - case info - case bio - case personalData - case addAccount - case logOut -} - -public enum EditSettingsEntryTag: ItemListItemTag { - case bio - - public func isEqual(to other: ItemListItemTag) -> Bool { - if let other = other as? EditSettingsEntryTag, self == other { - return true - } else { - return false - } - } -} - - -private enum SettingsEntry: ItemListNodeEntry { - case userInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, CachedPeerData?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?) - case setProfilePhoto(PresentationTheme, String) - case userInfoNotice(PresentationTheme, String) - - case bioText(PresentationTheme, String, String) - case bioInfo(PresentationTheme, String) - - case phoneNumber(PresentationTheme, String, String) - case username(PresentationTheme, String, String) - - case addAccount(PresentationTheme, String) - case logOut(PresentationTheme, String) - - var section: ItemListSectionId { - switch self { - case .userInfo, .setProfilePhoto, .userInfoNotice: - return SettingsSection.info.rawValue - case .bioText, .bioInfo: - return SettingsSection.bio.rawValue - case .phoneNumber, .username: - return SettingsSection.personalData.rawValue - case .addAccount: - return SettingsSection.addAccount.rawValue - case .logOut: - return SettingsSection.logOut.rawValue - } - } - - var stableId: Int32 { - switch self { - case .userInfo: - return 0 - case .setProfilePhoto: - return 1 - case .userInfoNotice: - return 2 - case .bioText: - return 3 - case .bioInfo: - return 4 - case .phoneNumber: - return 5 - case .username: - return 6 - case .addAccount: - return 7 - case .logOut: - return 8 - } - } - - static func ==(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool { - switch lhs { - case let .userInfo(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsCachedData, lhsEditingState, lhsUpdatingImage): - if case let .userInfo(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsCachedData, rhsEditingState, rhsUpdatingImage) = rhs { - if lhsTheme !== rhsTheme { - return false - } - if lhsStrings !== rhsStrings { - return false - } - if lhsDateTimeFormat != rhsDateTimeFormat { - return false - } - if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer { - if !lhsPeer.isEqual(rhsPeer) { - return false - } - } else if (lhsPeer != nil) != (rhsPeer != nil) { - return false - } - if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData { - if !lhsCachedData.isEqual(to: rhsCachedData) { - return false - } - } else if (lhsCachedData != nil) != (rhsCachedData != nil) { - return false - } - if lhsEditingState != rhsEditingState { - return false - } - if lhsUpdatingImage != rhsUpdatingImage { - return false - } - return true - } else { - return false - } - case let .setProfilePhoto(lhsTheme, lhsText): - if case let .setProfilePhoto(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .userInfoNotice(lhsTheme, lhsText): - if case let .userInfoNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .bioText(lhsTheme, lhsCurrentText, lhsText): - if case let .bioText(rhsTheme, rhsCurrentText, rhsText) = rhs, lhsTheme === rhsTheme, lhsCurrentText == rhsCurrentText, lhsText == rhsText { - return true - } else { - return false - } - case let .bioInfo(lhsTheme, lhsText): - if case let .bioInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .phoneNumber(lhsTheme, lhsText, lhsNumber): - if case let .phoneNumber(rhsTheme, rhsText, rhsNumber) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsNumber == rhsNumber { - return true - } else { - return false - } - case let .username(lhsTheme, lhsText, lhsAddress): - if case let .username(rhsTheme, rhsText, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsAddress == rhsAddress { - return true - } else { - return false - } - case let .addAccount(lhsTheme, lhsText): - if case let .addAccount(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .logOut(lhsTheme, lhsText): - if case let .logOut(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - } - } - - static func <(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool { - return lhs.stableId < rhs.stableId - } - - func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { - let arguments = arguments as! EditSettingsItemArguments - switch self { - case let .userInfo(_, _, dateTimeFormat, peer, cachedData, state, updatingImage): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer, presence: TelegramUserPresence(status: .present(until: Int32.max), lastActivity: 0), cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in - arguments.updateEditingName(editingName) - }, avatarTapped: { - arguments.avatarTapAction() - }, context: arguments.avatarAndNameInfoContext, updatingImage: updatingImage) - case let .setProfilePhoto(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.setProfilePhoto() - }) - case let .userInfoNotice(_, text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .bioText(_, currentText, placeholder): - return ItemListMultilineInputItem(presentationData: presentationData, text: currentText, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 70, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in - arguments.updateBioText(currentText, updatedText) - }, tag: EditSettingsEntryTag.bio) - case let .bioInfo(_, text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .phoneNumber(_, text, number): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: number, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.pushController(ChangePhoneNumberIntroController(context: arguments.context, phoneNumber: number)) - }) - case let .username(_, text, address): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: address, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.pushController(usernameSetupController(context: arguments.context)) - }) - case let .addAccount(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .center, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.addAccount() - }) - case let .logOut(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .center, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.logout() - }) - } - } -} - -private struct EditSettingsState: Equatable { - let updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar? - let editingName: ItemListAvatarAndNameInfoItemName - let updatingName: ItemListAvatarAndNameInfoItemName? - let editingBioText: String - let updatingBioText: Bool - - init(updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, editingName: ItemListAvatarAndNameInfoItemName, updatingName: ItemListAvatarAndNameInfoItemName? = nil, editingBioText: String, updatingBioText: Bool = false) { - self.updatingAvatar = updatingAvatar - self.editingName = editingName - self.updatingName = updatingName - self.editingBioText = editingBioText - self.updatingBioText = updatingBioText - } - - func withUpdatedUpdatingAvatar(_ updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?) -> EditSettingsState { - return EditSettingsState(updatingAvatar: updatingAvatar, editingName: self.editingName, updatingName: self.updatingName, editingBioText: self.editingBioText, updatingBioText: self.updatingBioText) - } - - func withUpdatedEditingName(_ editingName: ItemListAvatarAndNameInfoItemName) -> EditSettingsState { - return EditSettingsState(updatingAvatar: self.updatingAvatar, editingName: editingName, updatingName: self.updatingName, editingBioText: self.editingBioText, updatingBioText: self.updatingBioText) - } - - func withUpdatedUpdatingName(_ updatingName: ItemListAvatarAndNameInfoItemName?) -> EditSettingsState { - return EditSettingsState(updatingAvatar: self.updatingAvatar, editingName: self.editingName, updatingName: updatingName, editingBioText: self.editingBioText, updatingBioText: self.updatingBioText) - } - - func withUpdatedEditingBioText(_ editingBioText: String) -> EditSettingsState { - return EditSettingsState(updatingAvatar: self.updatingAvatar, editingName: self.editingName, updatingName: self.updatingName, editingBioText: editingBioText, updatingBioText: self.updatingBioText) - } - - func withUpdatedUpdatingBioText(_ updatingBioText: Bool) -> EditSettingsState { - return EditSettingsState(updatingAvatar: self.updatingAvatar, editingName: self.editingName, updatingName: self.updatingName, editingBioText: self.editingBioText, updatingBioText: updatingBioText) - } - - static func ==(lhs: EditSettingsState, rhs: EditSettingsState) -> Bool { - if lhs.updatingAvatar != rhs.updatingAvatar { - return false - } - if lhs.editingName != rhs.editingName { - return false - } - if lhs.updatingName != rhs.updatingName { - return false - } - if lhs.editingBioText != rhs.editingBioText { - return false - } - if lhs.updatingBioText != rhs.updatingBioText { - return false - } - return true - } -} - -private func editSettingsEntries(presentationData: PresentationData, state: EditSettingsState, view: PeerView, canAddAccounts: Bool) -> [SettingsEntry] { - var entries: [SettingsEntry] = [] - - if let peer = peerViewMainPeer(view) as? TelegramUser { - let userInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: state.updatingName) - entries.append(.userInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, view.cachedData, userInfoState, state.updatingAvatar)) - entries.append(.setProfilePhoto(presentationData.theme, peer.photo.isEmpty ? presentationData.strings.Settings_SetProfilePhotoOrVideo : presentationData.strings.Settings_SetNewProfilePhotoOrVideo)) - entries.append(.userInfoNotice(presentationData.theme, presentationData.strings.EditProfile_NameAndPhotoHelp)) - - entries.append(.bioText(presentationData.theme, state.editingBioText, presentationData.strings.UserInfo_About_Placeholder)) - entries.append(.bioInfo(presentationData.theme, presentationData.strings.Settings_About_Help)) - - if let phone = peer.phone { - entries.append(.phoneNumber(presentationData.theme, presentationData.strings.Settings_PhoneNumber, formatPhoneNumber(phone))) - } - entries.append(.username(presentationData.theme, presentationData.strings.Settings_Username, peer.addressName == nil ? "" : ("@" + peer.addressName!))) - - if canAddAccounts { - entries.append(.addAccount(presentationData.theme, presentationData.strings.Settings_AddAccount)) - } - entries.append(.logOut(presentationData.theme, presentationData.strings.Settings_Logout)) - } - - return entries -} - -func editSettingsController(context: AccountContext, currentName: ItemListAvatarAndNameInfoItemName, currentBioText: String, accountManager: AccountManager, canAddAccounts: Bool, focusOnItemTag: EditSettingsEntryTag? = nil) -> ViewController { - let initialState = EditSettingsState(editingName: currentName, editingBioText: currentBioText) - let statePromise = ValuePromise(initialState, ignoreRepeated: true) - let stateValue = Atomic(value: initialState) - let updateState: ((EditSettingsState) -> EditSettingsState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - var pushControllerImpl: ((ViewController) -> Void)? - var presentControllerImpl: ((ViewController, Any?) -> Void)? - var dismissImpl: (() -> Void)? - var errorImpl: (() -> Void)? - - let actionsDisposable = DisposableSet() - - let updateAvatarDisposable = MetaDisposable() - //actionsDisposable.add(updateAvatarDisposable) - - let updatePeerNameDisposable = MetaDisposable() - actionsDisposable.add(updatePeerNameDisposable) - - let supportPeerDisposable = MetaDisposable() - actionsDisposable.add(supportPeerDisposable) - - let hiddenAvatarRepresentationDisposable = MetaDisposable() - actionsDisposable.add(hiddenAvatarRepresentationDisposable) - - let currentAvatarMixin = Atomic(value: nil) - let cachedAvatarEntries = Atomic?>(value: nil) - - var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)? - let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext() - var updateHiddenAvatarImpl: (() -> Void)? - var changeProfilePhotoImpl: (() -> Void)? - - var getNavigationController: (() -> NavigationController?)? - - let arguments = EditSettingsItemArguments(context: context, accountManager: accountManager, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: { - var updating = false - updateState { - updating = $0.updatingAvatar != nil - return $0 - } - - if updating { - return - } - - changeProfilePhotoImpl?() - }, setProfilePhoto: { - changeProfilePhotoImpl?() - }, pushController: { controller in - pushControllerImpl?(controller) - }, presentController: { controller in - presentControllerImpl?(controller, nil) - }, updateEditingName: { editingName in - updateState { state in - return state.withUpdatedEditingName(editingName) - } - }, updateBioText: { currentText, text in - updateState { state in - return state.withUpdatedEditingBioText(text) - } - }, saveEditingState: { - var updateName: ItemListAvatarAndNameInfoItemName? - var updateBio: String? - var failed = false - updateState { state in - if state.editingName != currentName { - updateName = state.editingName - } - if state.editingBioText != currentBioText { - updateBio = state.editingBioText - } - - if (updateBio?.count ?? 0) > 70 { - failed = true - return state - } - - if updateName != nil || updateBio != nil { - return state.withUpdatedUpdatingName(state.editingName).withUpdatedUpdatingBioText(true) - } else { - return state - } - } - - guard !failed else { - errorImpl?() - return - } - - var updateNameSignal: Signal = .complete() - if let updateName = updateName, case let .personName(firstName, lastName, _) = updateName { - updateNameSignal = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName) - } - var updateBioSignal: Signal = .complete() - if let updateBio = updateBio { - updateBioSignal = updateAbout(account: context.account, about: updateBio) - |> `catch` { _ -> Signal in - return .complete() - } - } - updatePeerNameDisposable.set((combineLatest(updateNameSignal, updateBioSignal) |> deliverOnMainQueue).start(completed: { - dismissImpl?() - })) - }, addAccount: { - let isTestingEnvironment = context.account.testingEnvironment - context.sharedContext.beginNewAuth(testingEnvironment: isTestingEnvironment) - }, logout: { - let _ = (context.account.postbox.transaction { transaction -> String in - return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? "" - } - |> deliverOnMainQueue).start(next: { phoneNumber in - if let navigationController = getNavigationController?() { - presentControllerImpl?(logoutOptionsController(context: context, navigationController: navigationController, canAddAccounts: canAddAccounts, phoneNumber: phoneNumber), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - }) - }) - - let peerView = context.account.viewTracker.peerView(context.account.peerId) - - let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peerView) - |> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, Any)) in - let rightNavigationButton: ItemListNavigationButton - if state.updatingName != nil || state.updatingBioText { - rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) - } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { - arguments.saveEditingState() - }) - } - - let peer = peerViewMainPeer(view) - if let peer = peer { - let _ = cachedAvatarEntries.modify { value in - if value != nil { - return value - } else { - let promise = Promise<[AvatarGalleryEntry]>() - promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) - return promise - } - } - } - - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.EditProfile_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: editSettingsEntries(presentationData: presentationData, state: state, view: view, canAddAccounts: canAddAccounts), style: .blocks, ensureVisibleItemTag: focusOnItemTag) - - return (controllerState, (listState, arguments)) - } |> afterDisposed { - actionsDisposable.dispose() - } - - let controller = ItemListController(context: context, state: signal, tabBarItem: nil) - pushControllerImpl = { [weak controller] value in - (controller?.navigationController as? NavigationController)?.pushViewController(value) - } - presentControllerImpl = { [weak controller] value, arguments in - controller?.present(value, in: .window(.root), with: arguments) - } - dismissImpl = { [weak controller] in - let _ = (controller?.navigationController as? NavigationController)?.popViewController(animated: true) - } - avatarGalleryTransitionArguments = { [weak controller] entry in - if let controller = controller { - var result: ((ASDisplayNode, CGRect, () -> (UIView?, UIView?)), CGRect)? - controller.forEachItemNode { itemNode in - if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode { - result = itemNode.avatarTransitionNode() - } - } - if let (node, _) = result { - return GalleryTransitionArguments(transitionNode: node, addToTransitionSurface: { _ in - }) - } - } - return nil - } - updateHiddenAvatarImpl = { [weak controller] in - if let controller = controller { - controller.forEachItemNode { itemNode in - if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode { - itemNode.updateAvatarHidden() - } - } - } - } - changeProfilePhotoImpl = { [weak controller] in - let avatarEntries = (cachedAvatarEntries.with({ $0 })?.get()) ?? .single([]) - let _ = (combineLatest(context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in - return (transaction.getPeer(context.account.peerId), currentSearchBotsConfiguration(transaction: transaction)) - }, avatarEntries |> take(1)) |> deliverOnMainQueue).start(next: { peerAndSearchBotsConfiguration, avatarEntries in - let peer = peerAndSearchBotsConfiguration.0 - let searchBotsConfiguration = peerAndSearchBotsConfiguration.1 - - let mainIsVideo: Bool - if let main = avatarEntries.first, case let .image(image) = main { - mainIsVideo = !image.3.isEmpty - } else { - mainIsVideo = false - } - - controller?.view.endEditing(true) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) - legacyController.statusBar.statusBarStyle = .Ignore - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) - - legacyController.bind(controller: navigationController) - - presentControllerImpl?(legacyController, nil) - - var hasPhotos = false - if let peer = peer, !peer.profileImageRepresentations.isEmpty { - hasPhotos = true - } - - let completedProfilePhotoImpl: (UIImage) -> Void = { image in - if let data = image.jpegData(compressionQuality: 0.6) { - let resource = LocalFileMediaResource(fileId: arc4random64()) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource) - updateState { - $0.withUpdatedUpdatingAvatar(.image(representation, true)) - } - updateAvatarDisposable.set((updateAccountPhoto(account: context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) - }) |> deliverOnMainQueue).start(next: { result in - switch result { - case .complete: - updateState { - $0.withUpdatedUpdatingAvatar(nil) - } - - if let peer = peer { - let _ = cachedAvatarEntries.modify { value in - let promise = Promise<[AvatarGalleryEntry]>() - promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) - return promise - } - } - case .progress: - break - } - })) - } - } - - let completedProfileVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in - if let data = image.jpegData(compressionQuality: 0.6) { - let photoResource = LocalFileMediaResource(fileId: arc4random64()) - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource) - updateState { - $0.withUpdatedUpdatingAvatar(.image(representation, true)) - } - - var videoStartTimestamp: Double? = nil - if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { - videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue - } - - let signal = Signal { subscriber in - var filteredPath = url.path - if filteredPath.hasPrefix("file://") { - filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)]) - } - - let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath)) - let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in - if let paintingData = adjustments.paintingData, paintingData.hasAnimation { - return LegacyPaintEntityRenderer(account: context.account, adjustments: adjustments) - } else { - return nil - } - } - let uploadInterface = LegacyLiveUploadInterface(account: context.account) - let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! - - let signalDisposable = signal.start(next: { next in - if let result = next as? TGMediaVideoConversionResult { - if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - } - - var value = stat() - if stat(result.fileURL.path, &value) == 0 { - if let data = try? Data(contentsOf: result.fileURL) { - let resource: TelegramMediaResource - if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { - resource = LocalFileMediaResource(fileId: liveUploadData.id) - } else { - resource = LocalFileMediaResource(fileId: arc4random64()) - } - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - subscriber.putNext(resource) - } - } - subscriber.putCompletion() - } - }, error: { _ in - }, completed: nil) - - let disposable = ActionDisposable { - signalDisposable?.dispose() - } - - return ActionDisposable { - disposable.dispose() - } - } - - updateAvatarDisposable.set((signal - |> mapToSignal { videoResource in - return updateAccountPhoto(account: context.account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) - }) - } |> deliverOnMainQueue).start(next: { result in - switch result { - case .complete: - updateState { - $0.withUpdatedUpdatingAvatar(nil) - } - - if let peer = peer { - let _ = cachedAvatarEntries.modify { value in - let promise = Promise<[AvatarGalleryEntry]>() - promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) - return promise - } - } - case .progress: - break - } - })) - } - } - - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, isVideo: mainIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! - let _ = currentAvatarMixin.swap(mixin) - mixin.requestSearchController = { assetsController in - let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in - assetsController?.dismiss() - completedProfilePhotoImpl(result) - })) - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - mixin.didFinishWithImage = { image in - if let image = image { - completedProfilePhotoImpl(image) - } - } - mixin.didFinishWithVideo = { image, asset, adjustments in - if let image = image { -// completedProfileVideoImpl(image, url, adjustments) - } - } - mixin.didFinishWithDelete = { - let _ = currentAvatarMixin.swap(nil) - updateState { - if let profileImage = peer?.smallProfileImage { - return $0.withUpdatedUpdatingAvatar(.image(profileImage, false)) - } else { - return $0.withUpdatedUpdatingAvatar(ItemListAvatarAndNameInfoItemUpdatingAvatar.none) - } - } - updateAvatarDisposable.set((updateAccountPhoto(account: context.account, resource: nil, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) - }) |> deliverOnMainQueue).start(next: { result in - switch result { - case .complete: - updateState { - $0.withUpdatedUpdatingAvatar(nil) - } - if let peer = peer { - let _ = cachedAvatarEntries.modify { value in - let promise = Promise<[AvatarGalleryEntry]>() - promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) - return promise - } - } - case .progress: - break - } - })) - } - mixin.didFinishWithView = { - let _ = currentAvatarMixin.swap(nil) - - let _ = (context.account.postbox.loadedPeerWithId(context.account.peerId) - |> take(1) - |> deliverOnMainQueue).start(next: { peer in - if peer.smallProfileImage != nil { - let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: cachedAvatarEntries.with { $0 }, replaceRootController: { controller, ready in - }) - galleryController.avatarPhotoEditCompletion = { image in - completedProfilePhotoImpl(image) - } - galleryController.avatarVideoEditCompletion = { image, url, adjustments in - completedProfileVideoImpl(image, url, adjustments) - } - presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in - return nil - })) - } else { - changeProfilePhotoImpl?() - } - }) - } - mixin.didDismiss = { [weak legacyController] in - let _ = currentAvatarMixin.swap(nil) - legacyController?.dismiss() - } - let menuController = mixin.present() - if let menuController = menuController { - menuController.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } - } - }) - } - - let hapticFeedback = HapticFeedback() - errorImpl = { [weak controller] in - hapticFeedback.error() - controller?.forEachItemNode { itemNode in - if let itemNode = itemNode as? ItemListMultilineInputItemNode { - itemNode.animateError() - } - } - } - - getNavigationController = { [weak controller] in - return controller?.navigationController as? NavigationController - } - - return controller -} - diff --git a/submodules/SettingsUI/Sources/OpenSettings.swift b/submodules/SettingsUI/Sources/OpenSettings.swift deleted file mode 100644 index 333a2810fc..0000000000 --- a/submodules/SettingsUI/Sources/OpenSettings.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation -import Display -import SwiftSignalKit -import Postbox -import TelegramCore -import SyncCore -import OverlayStatusController -import AccountContext -import PresentationDataUtils -import AccountUtils - -func openEditSettings(context: AccountContext, accountsAndPeers: Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError>, focusOnItemTag: EditSettingsEntryTag? = nil, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void) -> Disposable { - let openEditingDisposable = MetaDisposable() - var cancelImpl: (() -> Void)? - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - presentController(controller, nil) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - let peerKey: PostboxViewKey = .peer(peerId: context.account.peerId, components: []) - let cachedDataKey: PostboxViewKey = .cachedPeerData(peerId: context.account.peerId) - let signal = (combineLatest(accountsAndPeers |> take(1), context.account.postbox.combinedView(keys: [peerKey, cachedDataKey])) - |> mapToSignal { accountsAndPeers, view -> Signal<(TelegramUser, CachedUserData, Bool), NoError> in - guard let cachedDataView = view.views[cachedDataKey] as? CachedPeerDataView, let cachedData = cachedDataView.cachedPeerData as? CachedUserData else { - return .complete() - } - guard let peerView = view.views[peerKey] as? PeerView, let peer = peerView.peers[context.account.peerId] as? TelegramUser else { - return .complete() - } - return .single((peer, cachedData, accountsAndPeers.1.count + 1 < maximumNumberOfAccounts)) - } - |> take(1)) - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - cancelImpl = { - openEditingDisposable.set(nil) - } - openEditingDisposable.set((signal - |> deliverOnMainQueue).start(next: { peer, cachedData, canAddAccounts in - pushController(editSettingsController(context: context, currentName: .personName(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phone: ""), currentBioText: cachedData.about ?? "", accountManager: context.sharedContext.accountManager, canAddAccounts: canAddAccounts, focusOnItemTag: focusOnItemTag)) - })) - return openEditingDisposable -} diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 2cbfcec749..c0fea2181e 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -188,22 +188,7 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool let icon: SettingsSearchableItemIcon = .profile let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - let presentProfileSettings: (AccountContext, @escaping (SettingsSearchableItemPresentation, ViewController?) -> Void, EditSettingsEntryTag?) -> Void = { context, present, itemTag in - let _ = openEditSettings(context: context, accountsAndPeers: activeAccountsAndPeers(context: context), focusOnItemTag: itemTag, presentController: { controller, _ in - present(.immediate, controller) - }, pushController: { controller in - present(.push, controller) - }) - } - var items: [SettingsSearchableItem] = [] -// items.append(SettingsSearchableItem(id: .profile(0), title: strings.EditProfile_Title, alternate: synonyms(strings.SettingsSearch_Synonyms_EditProfile_Title), icon: icon, breadcrumbs: [], present: { context, _, present in -// presentProfileSettings(context, present, nil) -// })) -// -// items.append(SettingsSearchableItem(id: .profile(1), title: strings.UserInfo_About_Placeholder, alternate: synonyms(strings.SettingsSearch_Synonyms_EditProfile_Title), icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in -// presentProfileSettings(context, present, .bio) -// })) items.append(SettingsSearchableItem(id: .profile(2), title: strings.Settings_PhoneNumber, alternate: synonyms(strings.SettingsSearch_Synonyms_EditProfile_PhoneNumber), icon: icon, breadcrumbs: [strings.EditProfile_Title], present: { context, _, present in let _ = (context.account.postbox.transaction { transaction -> String in return (transaction.getPeer(context.account.peerId) as? TelegramUser)?.phone ?? "" diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index b0ded4b2fa..f67c910898 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -1266,13 +1266,6 @@ public func settingsController(context: AccountContext, accountManager: AccountM resolvedUrlPromise.set(resolvedUrl) openFaq(resolvedUrlPromise, anchor) }, openEditing: { - let _ = (contextValue.get() - |> deliverOnMainQueue - |> take(1)).start(next: { context in - if let presentControllerImpl = presentControllerImpl, let pushControllerImpl = pushControllerImpl { - openEditingDisposable.set(openEditSettings(context: context, accountsAndPeers: accountsAndPeers.get(), presentController: presentControllerImpl, pushController: pushControllerImpl)) - } - }) }, displayCopyContextMenu: { let _ = (contextValue.get() |> deliverOnMainQueue diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2f9da9a78d..441c1ff8d4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5067,19 +5067,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .peer(peerId) = self.chatLocation { let context = self.context self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: context, peerId: peerId).start()) - self.preloadAvatarDisposable.set((peerInfoProfilePhotosWithCache(context: context, peerId: peerId) - |> mapToSignal { result -> Signal in - var signals: [Signal] = [.complete()] - for i in 0 ..< min(1, result.count) { - if let video = result[i].videoRepresentations.first { - let duration: Double = (video.representation.startTimestamp ?? 0.0) + (i == 0 ? 4.0 : 2.0) - signals.append(preloadVideoResource(postbox: context.account.postbox, resourceReference: video.reference, duration: duration)) + + if peerId.namespace == Namespaces.Peer.CloudUser { + self.preloadAvatarDisposable.set((peerInfoProfilePhotosWithCache(context: context, peerId: peerId) + |> mapToSignal { result -> Signal in + var signals: [Signal] = [.complete()] + for i in 0 ..< min(1, result.count) { + if let video = result[i].videoRepresentations.first { + let duration: Double = (video.representation.startTimestamp ?? 0.0) + (i == 0 ? 4.0 : 2.0) + signals.append(preloadVideoResource(postbox: context.account.postbox, resourceReference: video.reference, duration: duration)) + } } - } - return combineLatest(signals) |> mapToSignal { _ in - return .never() - } - }).start()) + return combineLatest(signals) |> mapToSignal { _ in + return .never() + } + }).start()) + } } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index b60fabd78a..83f415ced0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -201,10 +201,12 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { private let playbackStartDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() private let preloadDisposable = MetaDisposable() - private var statusNode: RadialStatusNode? + private let statusNode: RadialStatusNode private var fetchStatus: MediaResourceStatus? private var playerStatus: MediaPlayerStatus? + private var isLoading = ValuePromise(false) + private var loadingDisposable = MetaDisposable() let isReady = Promise() private var didSetReady: Bool = false @@ -258,12 +260,34 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { self.peer = peer self.imageNode = TransformImageNode() + self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.3)) + self.statusNode.isUserInteractionEnabled = false + super.init() self.clipsToBounds = true - + self.imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] self.addSubnode(self.imageNode) + self.addSubnode(self.statusNode) + + self.loadingDisposable.set((self.isLoading.get() + |> mapToSignal { value -> Signal in + if value { + return .single(value) |> delay(0.5, queue: Queue.mainQueue()) + } else { + return .single(value) + } + }).start(next: { [weak self] loading in + guard let strongSelf = self else { + return + } + if loading { + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false), completion: {}) + } else { + strongSelf.statusNode.transitionToState(.none, completion: {}) + } + })) } deinit { @@ -300,41 +324,8 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } else if case .Remote = fetchStatus, !isPlaying { progressRequired = true } - - if progressRequired { - if self.statusNode == nil { - let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.3)) - statusNode.isUserInteractionEnabled = false - statusNode.frame = CGRect(origin: CGPoint(x: floor((self.frame.size.width - 50.0) / 2.0), y: floor((self.frame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) - self.statusNode = statusNode - self.addSubnode(statusNode) - } - } else { - if let statusNode = self.statusNode { - statusNode.transitionToState(.none, completion: { [weak statusNode] in - statusNode?.removeFromSupernode() - }) - self.statusNode = nil - } - } - - var state: RadialStatusNodeState - if progressRequired { - state = .progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false) - } else { - state = .none - } - - if let statusNode = self.statusNode { - if state == .none { - self.statusNode = nil - } - statusNode.transitionToState(state, completion: { [weak statusNode] in - if state == .none { - statusNode?.removeFromSupernode() - } - }) - } + + self.isLoading.set(progressRequired) } func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) { @@ -398,7 +389,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } })) - self.addSubnode(videoNode) + self.insertSubnode(videoNode, belowSubnode: self.statusNode) self.isReady.set(videoNode.ready |> map { return true }) } @@ -473,6 +464,8 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize) transition.updateFrame(node: self.imageNode, frame: imageFrame) + transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floor((size.width - 50.0) / 2.0), y: floor((size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0))) + if let videoNode = self.videoNode { videoNode.updateLayout(size: imageSize, transition: .immediate) videoNode.frame = imageFrame @@ -1127,7 +1120,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode { wasAdded = true let addedItemNode = PeerInfoAvatarListItemNode(context: self.context, peer: peer) itemNode = addedItemNode - addedItemNode.setup(item: self.items[i], synchronous: synchronous && i == self.currentIndex) + addedItemNode.setup(item: self.items[i], synchronous: (i == 0 && i == self.currentIndex) || (synchronous && i == self.currentIndex)) self.itemNodes[self.items[i].id] = addedItemNode self.contentNode.addSubnode(addedItemNode) }