mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add bio editing and photo setup in voice chats
This commit is contained in:
parent
1fd84c653b
commit
2c404a8b5c
@ -6317,6 +6317,7 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.EditBioText" = "Any details such as age, occupation or city.";
|
||||
"VoiceChat.EditBioPlaceholder" = "Bio";
|
||||
"VoiceChat.EditBioSave" = "Save";
|
||||
"VoiceChat.EditBioSuccess" = "Your bio is saved.";
|
||||
|
||||
"VoiceChat.SendPublicLinkText" = "%1$@ isn't a member of \"%2$@\" yet. Send them a public invite link instead?";
|
||||
"VoiceChat.SendPublicLinkSend" = "Send";
|
||||
|
@ -41,6 +41,7 @@ public final class OpenChatMessageParams {
|
||||
public let actionInteraction: GalleryControllerActionInteraction?
|
||||
public let playlistLocation: PeerMessagesPlaylistLocation?
|
||||
public let gallerySource: GalleryControllerItemSource?
|
||||
public let centralItemUpdated: ((MessageId) -> Void)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -65,7 +66,8 @@ public final class OpenChatMessageParams {
|
||||
chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void,
|
||||
actionInteraction: GalleryControllerActionInteraction? = nil,
|
||||
playlistLocation: PeerMessagesPlaylistLocation? = nil,
|
||||
gallerySource: GalleryControllerItemSource? = nil
|
||||
gallerySource: GalleryControllerItemSource? = nil,
|
||||
centralItemUpdated: ((MessageId) -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
@ -90,5 +92,6 @@ public final class OpenChatMessageParams {
|
||||
self.actionInteraction = actionInteraction
|
||||
self.playlistLocation = playlistLocation
|
||||
self.gallerySource = gallerySource
|
||||
self.centralItemUpdated = centralItemUpdated
|
||||
}
|
||||
}
|
||||
|
@ -384,6 +384,8 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
|
||||
private var screenCaptureEventsDisposable: Disposable?
|
||||
|
||||
public var centralItemUpdated: ((MessageId) -> Void)?
|
||||
|
||||
public init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) {
|
||||
self.context = context
|
||||
self.source = source
|
||||
@ -1192,6 +1194,9 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
}
|
||||
if strongSelf.didSetReady {
|
||||
strongSelf._hiddenMedia.set(.single(hiddenItem))
|
||||
if let hiddenItem = hiddenItem {
|
||||
strongSelf.centralItemUpdated?(hiddenItem.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ typedef enum {
|
||||
|
||||
- (TGNavigationBarPallete *)navigationBarPallete;
|
||||
- (TGMenuSheetPallete *)menuSheetPallete;
|
||||
- (TGMenuSheetPallete *)darkMenuSheetPallete;
|
||||
- (TGMediaAssetsPallete *)mediaAssetsPallete;
|
||||
- (TGCheckButtonPallete *)checkButtonPallete;
|
||||
|
||||
|
@ -13,6 +13,8 @@ typedef void (^TGMediaAvatarPresentImpl)(id<LegacyComponentsContext>, void (^)(U
|
||||
|
||||
@interface TGMediaAvatarMenuMixin : NSObject
|
||||
|
||||
@property (nonatomic, assign) bool forceDark;
|
||||
|
||||
@property (nonatomic, copy) void (^didFinishWithImage)(UIImage *image);
|
||||
@property (nonatomic, copy) void (^didFinishWithVideo)(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments);
|
||||
@property (nonatomic, copy) void (^didFinishWithDelete)(void);
|
||||
|
@ -34,6 +34,8 @@
|
||||
@property (nonatomic, assign) bool dismissesByOutsideTap;
|
||||
@property (nonatomic, assign) bool hasSwipeGesture;
|
||||
|
||||
@property (nonatomic, assign) bool forceDark;
|
||||
|
||||
@property (nonatomic, assign) bool followsKeyboard;
|
||||
|
||||
@property (nonatomic, assign) bool ignoreNextDismissal;
|
||||
|
@ -73,7 +73,7 @@
|
||||
- (TGMenuSheetController *)_presentAvatarMenu
|
||||
{
|
||||
__weak TGMediaAvatarMenuMixin *weakSelf = self;
|
||||
TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
|
||||
TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:self.forceDark];
|
||||
controller.dismissesByOutsideTap = true;
|
||||
controller.hasSwipeGesture = true;
|
||||
controller.didDismiss = ^(bool manual)
|
||||
|
@ -95,7 +95,9 @@ typedef enum
|
||||
_permittedArrowDirections = UIPopoverArrowDirectionDown;
|
||||
_requiuresDimView = true;
|
||||
|
||||
if (!dark && [[LegacyComponentsGlobals provider] respondsToSelector:@selector(menuSheetPallete)])
|
||||
if (dark && [[LegacyComponentsGlobals provider] respondsToSelector:@selector(darkMenuSheetPallete)])
|
||||
self.pallete = [[LegacyComponentsGlobals provider] darkMenuSheetPallete];
|
||||
else if (!dark && [[LegacyComponentsGlobals provider] respondsToSelector:@selector(menuSheetPallete)])
|
||||
self.pallete = [[LegacyComponentsGlobals provider] menuSheetPallete];
|
||||
|
||||
self.wantsFullScreenLayout = true;
|
||||
|
@ -301,7 +301,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
|
||||
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak legacyController] presentationData in
|
||||
if let legacyController = legacyController, let controller = legacyController.legacyController as? TGMenuSheetController {
|
||||
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme)
|
||||
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false)
|
||||
}
|
||||
})
|
||||
legacyController.disposables.add(presentationDisposable)
|
||||
@ -378,9 +378,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
return controller
|
||||
}
|
||||
|
||||
public func legacyMenuPaletteFromTheme(_ theme: PresentationTheme) -> TGMenuSheetPallete {
|
||||
let sheetTheme = theme.actionSheet
|
||||
return TGMenuSheetPallete(dark: theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor))
|
||||
public func legacyMenuPaletteFromTheme(_ theme: PresentationTheme, forceDark: Bool) -> TGMenuSheetPallete {
|
||||
let sheetTheme: PresentationThemeActionSheet
|
||||
if forceDark && !theme.overallDarkAppearance {
|
||||
sheetTheme = defaultDarkColorPresentationTheme.actionSheet
|
||||
} else {
|
||||
sheetTheme = theme.actionSheet
|
||||
}
|
||||
return TGMenuSheetPallete(dark: forceDark || theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor))
|
||||
}
|
||||
|
||||
public func presentLegacyPasteMenu(context: AccountContext, peer: Peer, chatLocation: ChatLocation, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, presentationData: PresentationData, images: [UIImage], presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, present: (ViewController, Any?) -> Void, initialLayout: ContainerViewLayout? = nil) -> ViewController {
|
||||
@ -441,7 +446,7 @@ public func presentLegacyPasteMenu(context: AccountContext, peer: Peer, chatLoca
|
||||
|
||||
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak legacyController] presentationData in
|
||||
if let legacyController = legacyController, let controller = legacyController.legacyController as? TGMenuSheetController {
|
||||
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme)
|
||||
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false)
|
||||
}
|
||||
})
|
||||
legacyController.disposables.add(presentationDisposable)
|
||||
|
@ -279,6 +279,22 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone
|
||||
return TGMenuSheetPallete(dark: theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor))
|
||||
}
|
||||
|
||||
func darkMenuSheetPallete() -> TGMenuSheetPallete! {
|
||||
let theme: PresentationTheme
|
||||
if let legacyContext = legacyContext {
|
||||
let presentationData = legacyContext.sharedContext.currentPresentationData.with { $0 }
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
theme = presentationData.theme
|
||||
} else {
|
||||
theme = defaultDarkColorPresentationTheme
|
||||
}
|
||||
} else {
|
||||
theme = defaultDarkColorPresentationTheme
|
||||
}
|
||||
let sheetTheme = theme.actionSheet
|
||||
return TGMenuSheetPallete(dark: theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor))
|
||||
}
|
||||
|
||||
func mediaAssetsPallete() -> TGMediaAssetsPallete! {
|
||||
let presentationTheme: PresentationTheme
|
||||
if let legacyContext = legacyContext {
|
||||
|
@ -69,6 +69,10 @@ public enum AvatarGalleryEntry: Equatable {
|
||||
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?)
|
||||
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, Int32?, GalleryItemIndexData?, MessageId?, Data?, String?)
|
||||
|
||||
public init(representation: TelegramMediaImageRepresentation, peer: Peer) {
|
||||
self = .topImage([ImageRepresentationWithReference(representation: representation, reference: MediaResourceReference.standalone(resource: representation.resource))], [], peer, nil, nil, nil)
|
||||
}
|
||||
|
||||
public var id: AvatarGalleryEntryId {
|
||||
switch self {
|
||||
case let .topImage(representations, _, _, _, _, _):
|
||||
|
@ -125,8 +125,9 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
private let statusNode: RadialStatusNode
|
||||
|
||||
private var playerStatus: MediaPlayerStatus?
|
||||
private var isLoading = ValuePromise<Bool>(false)
|
||||
private var loadingProgress = ValuePromise<Float?>(nil)
|
||||
private var isLoading = Promise<Bool>(false)
|
||||
private var loadingProgress = Promise<Float?>(nil)
|
||||
private var progress: Signal<Float?, NoError>?
|
||||
private var loadingProgressDisposable = MetaDisposable()
|
||||
private var hasProgress = false
|
||||
|
||||
@ -241,8 +242,11 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
bufferingProgress = nil
|
||||
}
|
||||
}
|
||||
self.loadingProgress.set(bufferingProgress)
|
||||
self.isLoading.set(bufferingProgress != nil)
|
||||
|
||||
if self.progress == nil {
|
||||
self.loadingProgress.set(.single(bufferingProgress))
|
||||
self.isLoading.set(.single(bufferingProgress != nil))
|
||||
}
|
||||
}
|
||||
|
||||
public func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -310,8 +314,16 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
self.isReady.set(videoNode.ready |> map { return true })
|
||||
}
|
||||
|
||||
func setup(item: PeerInfoAvatarListItem, synchronous: Bool, fullSizeOnly: Bool = false) {
|
||||
func setup(item: PeerInfoAvatarListItem, progress: Signal<Float?, NoError>? = nil, synchronous: Bool, fullSizeOnly: Bool = false) {
|
||||
self.item = item
|
||||
self.progress = progress
|
||||
|
||||
if let progress = progress {
|
||||
self.loadingProgress.set((progress
|
||||
|> beforeNext { [weak self] next in
|
||||
self?.isLoading.set(.single(next != nil))
|
||||
}))
|
||||
}
|
||||
|
||||
let representations: [ImageRepresentationWithReference]
|
||||
let videoRepresentations: [VideoRepresentationWithReference]
|
||||
@ -912,7 +924,8 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
return items.count == 0
|
||||
}
|
||||
|
||||
public func update(size: CGSize, peer: Peer?, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
|
||||
private var additionalEntryProgress: Signal<Float?, NoError>? = nil
|
||||
public func update(size: CGSize, peer: Peer?, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> = .single(nil), isExpanded: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = size
|
||||
let previousExpanded = self.isExpanded
|
||||
self.isExpanded = isExpanded
|
||||
@ -924,17 +937,23 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
|
||||
if let peer = peer, !self.initializedList {
|
||||
self.initializedList = true
|
||||
self.disposable.set((peerInfoProfilePhotosWithCache(context: self.context, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] (complete, entries) in
|
||||
|
||||
let entry = additionalEntry
|
||||
|> map { representation -> AvatarGalleryEntry? in
|
||||
return representation.flatMap { AvatarGalleryEntry(representation: $0.0, peer: peer) }
|
||||
}
|
||||
|
||||
self.disposable.set(combineLatest(queue: Queue.mainQueue(), peerInfoProfilePhotosWithCache(context: self.context, peerId: peer.id), entry).start(next: { [weak self] completeAndEntries, entry in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var (complete, entries) = completeAndEntries
|
||||
|
||||
if strongSelf.galleryEntries.count > 1, entries.count == 1 && !complete {
|
||||
return
|
||||
}
|
||||
|
||||
var entries = entries
|
||||
var synchronous = false
|
||||
if !strongSelf.galleryEntries.isEmpty, let updated = entries.first, case let .image(image) = updated, !image.3.isEmpty, let previous = strongSelf.galleryEntries.first, case let .topImage(topImage) = previous {
|
||||
let firstEntry = AvatarGalleryEntry.image(image.0, image.1, topImage.0, image.3, image.4, image.5, image.6, image.7, image.8, image.9)
|
||||
@ -943,6 +962,15 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
synchronous = true
|
||||
}
|
||||
|
||||
if let entry = entry {
|
||||
entries.insert(entry, at: 0)
|
||||
|
||||
strongSelf.additionalEntryProgress = additionalEntry
|
||||
|> map { value -> Float? in
|
||||
return value?.1
|
||||
}
|
||||
}
|
||||
|
||||
if strongSelf.ignoreNextProfilePhotoUpdate {
|
||||
if entries.count == 1, let first = entries.first, case .topImage = first {
|
||||
return
|
||||
@ -1067,7 +1095,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
wasAdded = true
|
||||
let addedItemNode = PeerInfoAvatarListItemNode(context: self.context, peer: peer)
|
||||
itemNode = addedItemNode
|
||||
addedItemNode.setup(item: self.items[i], synchronous: (i == 0 && i == self.currentIndex) || (synchronous && i == self.currentIndex), fullSizeOnly: self.firstFullSizeOnly && i == 0)
|
||||
addedItemNode.setup(item: self.items[i], progress: i == 0 ? self.additionalEntryProgress : nil, synchronous: (i == 0 && i == self.currentIndex) || (synchronous && i == self.currentIndex), fullSizeOnly: self.firstFullSizeOnly && i == 0)
|
||||
self.itemNodes[self.items[i].id] = addedItemNode
|
||||
self.contentNode.addSubnode(addedItemNode)
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ swift_library(
|
||||
"//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager",
|
||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||
"//submodules/WebSearchUI:WebSearchUI",
|
||||
"//submodules/MapResourceToAvatarSizes:MapResourceToAvatarSizes",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1401,13 +1401,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
)
|
||||
self.temporaryParticipantsContext = nil
|
||||
self.participantsContext = participantsContext
|
||||
let myPeer = self.accountContext.account.postbox.transaction { transaction -> (Peer, CachedPeerData?)? in
|
||||
if let peer = transaction.getPeer(myPeerId) {
|
||||
return (peer, transaction.getPeerCachedData(peerId: myPeerId))
|
||||
let myPeer = self.accountContext.account.postbox.peerView(id: myPeerId)
|
||||
|> map { view -> (Peer, CachedPeerData?)? in
|
||||
if let peer = peerViewMainPeer(view) {
|
||||
return (peer, view.cachedData)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
participantsContext.state,
|
||||
participantsContext.activeSpeakers,
|
||||
@ -1507,6 +1509,19 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
if participant.peer.id == strongSelf.joinAsPeerId {
|
||||
if let (myPeer, cachedData) = myPeerAndCachedData {
|
||||
let about: String?
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
about = cachedData.about
|
||||
} else if let cachedData = cachedData as? CachedChannelData {
|
||||
about = cachedData.about
|
||||
} else {
|
||||
about = nil
|
||||
}
|
||||
participant.peer = myPeer
|
||||
participant.about = about
|
||||
}
|
||||
|
||||
var filteredMuteState = participant.muteState
|
||||
if isReconnectingAsSpeaker || strongSelf.currentConnectionMode != .rtc {
|
||||
filteredMuteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: false)
|
||||
|
@ -28,6 +28,7 @@ import LegacyUI
|
||||
import LegacyComponents
|
||||
import LegacyMediaPickerUI
|
||||
import WebSearchUI
|
||||
import MapResourceToAvatarSizes
|
||||
|
||||
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||
@ -381,6 +382,8 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private var audioLevels: [PeerId: ValuePipe<Float>] = [:]
|
||||
|
||||
var updateAvatarPromise = Promise<(TelegramMediaImageRepresentation, Float)?>(nil)
|
||||
|
||||
init(
|
||||
updateIsMuted: @escaping (PeerId, Bool) -> Void,
|
||||
openPeer: @escaping (PeerId) -> Void,
|
||||
@ -652,7 +655,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
|
||||
|
||||
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, enabled: true, selectable: !peerEntry.isMyPeer || peerEntry.canManageCall || peerEntry.raisedHand, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
|
||||
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, enabled: true, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
|
||||
if let ssrc = peerEntry.ssrc {
|
||||
return interaction.getPeerVideo(ssrc)
|
||||
} else {
|
||||
@ -664,6 +667,8 @@ public final class VoiceChatController: ViewController {
|
||||
interaction.peerContextAction(peerEntry, node, nil)
|
||||
}, contextAction: nil, getIsExpanded: {
|
||||
return interaction.isExpanded
|
||||
}, getUpdatingAvatar: {
|
||||
return interaction.updateAvatarPromise.get()
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -787,6 +792,10 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private var currentDominantSpeakerWithVideo: (PeerId, UInt32)?
|
||||
|
||||
private var updateAvatarDisposable = MetaDisposable()
|
||||
private let updateAvatarPromise = Promise<(TelegramMediaImageRepresentation, Float)?>(nil)
|
||||
private var currentUpdatingAvatar: TelegramMediaImageRepresentation?
|
||||
|
||||
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
||||
self.controller = controller
|
||||
self.sharedContext = sharedContext
|
||||
@ -1239,6 +1248,12 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
if peer.id == strongSelf.callState?.myPeerId {
|
||||
let maxLength: Int
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
maxLength = 70
|
||||
} else {
|
||||
maxLength = 100
|
||||
}
|
||||
if entry.raisedHand {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_CancelSpeakRequest, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/RevokeSpeak"), color: theme.actionSheet.primaryTextColor)
|
||||
@ -1251,35 +1266,41 @@ public final class VoiceChatController: ViewController {
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_ChangePhoto, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Camera"), color: theme.actionSheet.primaryTextColor)
|
||||
// }, action: { _, f in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// f(.default)
|
||||
//
|
||||
// strongSelf.openAvatarForEditing(fromGallery: false, completion: {})
|
||||
// })))
|
||||
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EditBio, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.actionSheet.primaryTextColor)
|
||||
// }, action: { _, f in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// f(.default)
|
||||
//
|
||||
// let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, apply: { bio in
|
||||
// if let strongSelf = self {
|
||||
// let _ = updateAbout(account: strongSelf.context.account, about: bio)
|
||||
// |> `catch` { _ -> Signal<Void, NoError> in
|
||||
// return .complete()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// self?.controller?.present(controller, in: .window(.root))
|
||||
// })))
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_ChangePhoto, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Camera"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
f(.default)
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
strongSelf.openAvatarForEditing(fromGallery: false, completion: {})
|
||||
}
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EditBio, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, maxLength: maxLength, apply: { bio in
|
||||
if let strongSelf = self {
|
||||
let _ = (updateAbout(account: strongSelf.context.account, about: bio)
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}).start()
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .voiceChatFlag(text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
||||
}
|
||||
})
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
||||
if callState.adminIds.contains(peer.id) {
|
||||
@ -1355,8 +1376,8 @@ public final class VoiceChatController: ViewController {
|
||||
openTitle = strongSelf.presentationData.strings.VoiceChat_OpenChannel
|
||||
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Channels")
|
||||
} else {
|
||||
openTitle = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile
|
||||
openIcon = UIImage(bundleImageName: "Chat/Context Menu/User")
|
||||
openTitle = strongSelf.presentationData.strings.Conversation_ContextMenuSendMessage
|
||||
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Message")
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in
|
||||
return generateTintedImage(image: openIcon, color: theme.actionSheet.primaryTextColor)
|
||||
@ -1368,24 +1389,7 @@ public final class VoiceChatController: ViewController {
|
||||
let context = strongSelf.context
|
||||
strongSelf.controller?.dismiss(completion: {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
var expandAvatar = true
|
||||
if peer.smallProfileImage == nil {
|
||||
expandAvatar = false
|
||||
}
|
||||
if let (validLayout, _) = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet {
|
||||
expandAvatar = false
|
||||
}
|
||||
if let strongSelf = self, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar, fromChat: false) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer.id), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
})
|
||||
|
||||
@ -1473,6 +1477,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
self.itemInteraction?.updateAvatarPromise = self.updateAvatarPromise
|
||||
|
||||
self.topPanelNode.addSubnode(self.topPanelEdgeNode)
|
||||
self.topPanelNode.addSubnode(self.topPanelBackgroundNode)
|
||||
@ -1851,6 +1856,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.memberEventsDisposable.dispose()
|
||||
self.reconnectedAsEventsDisposable.dispose()
|
||||
self.voiceSourcesDisposable.dispose()
|
||||
self.updateAvatarDisposable.dispose()
|
||||
}
|
||||
|
||||
private func openContextMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||
@ -1911,7 +1917,7 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditTitleTitle, text: presentationData.strings.VoiceChat_EditTitleText, placeholder: chatPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), value: strongSelf.callState?.title, apply: { title in
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditTitleTitle, text: presentationData.strings.VoiceChat_EditTitleText, placeholder: chatPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), value: strongSelf.callState?.title, maxLength: 40, apply: { title in
|
||||
if let strongSelf = self, let title = title {
|
||||
strongSelf.call.updateTitle(title)
|
||||
|
||||
@ -1989,7 +1995,7 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_StartRecordingTitle, text: presentationData.strings.VoiceChat_StartRecordingText, placeholder: presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, apply: { title in
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_StartRecordingTitle, text: presentationData.strings.VoiceChat_StartRecordingText, placeholder: presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, maxLength: 40, apply: { title in
|
||||
if let strongSelf = self, let title = title {
|
||||
strongSelf.call.setShouldBeRecording(true, title: title)
|
||||
|
||||
@ -3251,8 +3257,13 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
var memberPeer = member.peer
|
||||
if member.peer.id == self.callState?.myPeerId, let user = memberPeer as? TelegramUser, let photo = self.currentUpdatingAvatar {
|
||||
memberPeer = user.withUpdatedPhoto([photo])
|
||||
}
|
||||
|
||||
entries.append(.peer(PeerEntry(
|
||||
peer: member.peer,
|
||||
peer: memberPeer,
|
||||
about: member.about,
|
||||
isMyPeer: self.callState?.myPeerId == member.peer.id,
|
||||
ssrc: member.ssrc,
|
||||
@ -3567,7 +3578,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
let legacyController = LegacyController(presentation: .custom, theme: strongSelf.darkTheme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
@ -3598,6 +3609,7 @@ public final class VoiceChatController: ViewController {
|
||||
// }
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && !fromGallery, hasViewButton: false, personalPhoto: peerId.namespace == Namespaces.Peer.CloudUser, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
mixin.forceDark = true
|
||||
mixin.stickersContext = paintStickersContext
|
||||
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { [weak self] assetsController in
|
||||
@ -3606,7 +3618,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
let controller = WebSearchController(context: strongSelf.context, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.id.namespace == Namespaces.Peer.CloudUser ? nil : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
|
||||
assetsController?.dismiss()
|
||||
// self?.updateProfilePhoto(result)
|
||||
self?.updateProfilePhoto(result)
|
||||
}))
|
||||
controller.navigationPresentation = .modal
|
||||
strongSelf.controller?.push(controller)
|
||||
@ -3618,7 +3630,7 @@ public final class VoiceChatController: ViewController {
|
||||
mixin.didFinishWithImage = { [weak self] image in
|
||||
if let image = image {
|
||||
completion()
|
||||
// self?.updateProfilePhoto(image)
|
||||
self?.updateProfilePhoto(image)
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in
|
||||
@ -3697,6 +3709,41 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func updateProfilePhoto(_ image: UIImage) {
|
||||
guard let data = image.jpegData(compressionQuality: 0.6), let peerId = self.callState?.myPeerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
self.call.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [])
|
||||
|
||||
self.currentUpdatingAvatar = representation
|
||||
self.updateAvatarPromise.set(.single((representation, 0.0)))
|
||||
|
||||
let postbox = self.call.account.postbox
|
||||
let signal = peerId.namespace == Namespaces.Peer.CloudUser ? updateAccountPhoto(account: self.call.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
|
||||
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
|
||||
}) : updatePeerPhoto(postbox: postbox, network: self.call.account.network, stateManager: self.call.account.stateManager, accountPeerId: self.context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: postbox, network: self.call.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in
|
||||
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
|
||||
})
|
||||
|
||||
self.updateAvatarDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch result {
|
||||
case .complete:
|
||||
strongSelf.updateAvatarPromise.set(.single(nil))
|
||||
case let .progress(value):
|
||||
strongSelf.updateAvatarPromise.set(.single((representation, value)))
|
||||
}
|
||||
}))
|
||||
|
||||
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||
}
|
||||
}
|
||||
|
||||
private let sharedContext: SharedAccountContext
|
||||
|
@ -79,8 +79,9 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
let action: ((ASDisplayNode) -> Void)?
|
||||
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
let getIsExpanded: () -> Bool
|
||||
let getUpdatingAvatar: () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>
|
||||
|
||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool) {
|
||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
||||
self.presentationData = presentationData
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -101,6 +102,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
self.action = action
|
||||
self.contextAction = contextAction
|
||||
self.getIsExpanded = getIsExpanded
|
||||
self.getUpdatingAvatar = getUpdatingAvatar
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
@ -408,7 +410,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode)
|
||||
avatarListWrapperNode.addSubnode(avatarListContainerNode)
|
||||
|
||||
avatarListNode.update(size: targetRect.size, peer: item.peer, isExpanded: true, transition: .immediate)
|
||||
avatarListNode.update(size: targetRect.size, peer: item.peer, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate)
|
||||
strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode)
|
||||
|
||||
strongSelf.audioLevelView?.alpha = 0.0
|
||||
|
@ -41,8 +41,11 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String) {
|
||||
private let maxLength: Int
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String, maxLength: Int) {
|
||||
self.theme = theme
|
||||
self.maxLength = maxLength
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
@ -135,7 +138,7 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
||||
|
||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
if updatedText.count > 40 {
|
||||
if updatedText.count > maxLength {
|
||||
self.textInputNode.layer.addShakeAnimation()
|
||||
return false
|
||||
}
|
||||
@ -205,7 +208,7 @@ private final class VoiceChatTitleEditAlertContentNode: AlertContentNode {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?) {
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int) {
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
self.text = text
|
||||
@ -215,7 +218,7 @@ private final class VoiceChatTitleEditAlertContentNode: AlertContentNode {
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 8
|
||||
|
||||
self.inputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: placeholder)
|
||||
self.inputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength)
|
||||
self.inputFieldNode.text = value ?? ""
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
@ -408,7 +411,7 @@ private final class VoiceChatTitleEditAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
func voiceChatTitleEditController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
func voiceChatTitleEditController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, maxLength: Int, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
var presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
if let forceTheme = forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
@ -423,7 +426,7 @@ func voiceChatTitleEditController(sharedContext: SharedAccountContext, account:
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = VoiceChatTitleEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value)
|
||||
let contentNode = VoiceChatTitleEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -8527,7 +8527,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let presentationDisposable = strongSelf.context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
if let controller = controller {
|
||||
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme)
|
||||
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false)
|
||||
}
|
||||
})
|
||||
legacyController.disposables.add(presentationDisposable)
|
||||
|
@ -166,6 +166,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
params.dismissInput()
|
||||
let _ = (gallery
|
||||
|> deliverOnMainQueue).start(next: { gallery in
|
||||
gallery.centralItemUpdated = { messageId in
|
||||
params.centralItemUpdated?(messageId)
|
||||
}
|
||||
params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in
|
||||
let selectedTransitionNode = params.transitionNode(messageId, media)
|
||||
if let selectedTransitionNode = selectedTransitionNode {
|
||||
|
@ -124,6 +124,9 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func ensureMessageIsVisible(id: MessageId) {
|
||||
}
|
||||
|
||||
func scrollToTop() -> Bool {
|
||||
if !self.listNode.scrollToOffsetFromTop(0.0) {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
@ -138,6 +138,10 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
self.playlistPreloadDisposable?.dispose()
|
||||
}
|
||||
|
||||
func ensureMessageIsVisible(id: MessageId) {
|
||||
|
||||
}
|
||||
|
||||
func scrollToTop() -> Bool {
|
||||
let offset = self.listNode.visibleContentOffset()
|
||||
switch offset {
|
||||
|
@ -168,6 +168,9 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
deinit {
|
||||
}
|
||||
|
||||
func ensureMessageIsVisible(id: MessageId) {
|
||||
}
|
||||
|
||||
func scrollToTop() -> Bool {
|
||||
if !self.listNode.scrollToOffsetFromTop(0.0) {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
@ -872,6 +872,21 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.animationTimer?.invalidate()
|
||||
}
|
||||
|
||||
func ensureMessageIsVisible(id: MessageId) {
|
||||
let activeRect = self.scrollNode.bounds
|
||||
for item in self.mediaItems {
|
||||
if item.message.id == id {
|
||||
if let itemNode = self.visibleMediaItems[item.message.stableId] {
|
||||
if !activeRect.contains(itemNode.frame) {
|
||||
let targetContentOffset = CGPoint(x: 0.0, y: max(-self.scrollNode.view.contentInset.top, itemNode.frame.minY - (self.scrollNode.frame.height - itemNode.frame.height) / 2.0))
|
||||
self.scrollNode.view.setContentOffset(targetContentOffset, animated: false)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func requestHistoryAroundVisiblePosition() {
|
||||
if self.isRequestingView {
|
||||
return
|
||||
|
@ -24,6 +24,7 @@ protocol PeerInfoPaneNode: ASDisplayNode {
|
||||
func addToTransitionSurface(view: UIView)
|
||||
func updateHiddenMedia()
|
||||
func updateSelectedMessages(animated: Bool)
|
||||
func ensureMessageIsVisible(id: MessageId)
|
||||
}
|
||||
|
||||
final class PeerInfoPaneWrapper {
|
||||
|
@ -3107,7 +3107,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
})
|
||||
}
|
||||
})
|
||||
})))
|
||||
}), centralItemUpdated: { [weak self] messageId in
|
||||
self?.paneContainerNode.requestExpandTabs?()
|
||||
self?.paneContainerNode.currentPane?.node.ensureMessageIsVisible(id: messageId)
|
||||
}))
|
||||
}
|
||||
private func openResolved(_ result: ResolvedUrl) {
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user