Video avatar fixes

This commit is contained in:
Ilya Laktyushin 2020-07-17 14:05:59 +03:00
parent 913588f205
commit e94e5d90d2
7 changed files with 55 additions and 907 deletions

View File

@ -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 {

View File

@ -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<TGMediaAvatarMenuMixin?>(value: nil)
let cachedAvatarEntries = Atomic<Promise<[AvatarGalleryEntry]>?>(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<Void, NoError> = .complete()
if let updateName = updateName, case let .personName(firstName, lastName, _) = updateName {
updateNameSignal = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName)
}
var updateBioSignal: Signal<Void, NoError> = .complete()
if let updateBio = updateBio {
updateBioSignal = updateAbout(account: context.account, about: updateBio)
|> `catch` { _ -> Signal<Void, NoError> 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<TelegramMediaResource, UploadPeerPhotoError> { 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
}

View File

@ -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<Never, NoError> { 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
}

View File

@ -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 ?? ""

View File

@ -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

View File

@ -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<Never, NoError> in
var signals: [Signal<Never, NoError>] = [.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<Never, NoError> in
var signals: [Signal<Never, NoError>] = [.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())
}
}
}

View File

@ -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<Bool>(false)
private var loadingDisposable = MetaDisposable()
let isReady = Promise<Bool>()
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<Bool, NoError> 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)
}