mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 05:26:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
d7162f3d37
@ -32,6 +32,7 @@
|
||||
<string>merchant.sberbank.test.ph.telegra.Telegraph</string>
|
||||
<string>merchant.privatbank.test.telergramios</string>
|
||||
<string>merchant.privatbank.prod.telergram</string>
|
||||
<string>merchant.telegram.tranzzo.test</string>
|
||||
</array>
|
||||
<key>com.apple.developer.pushkit.unrestricted-voip</key>
|
||||
<true/>
|
||||
|
||||
@ -5246,6 +5246,8 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"Map.NoPlacesNearby" = "There are no known places nearby.\nTry a different location.";
|
||||
|
||||
"CreatePoll.QuizTitle" = "New Quiz";
|
||||
"CreatePoll.QuizOptionsHeader" = "QUIZ OPTIONS";
|
||||
"CreatePoll.Anonymous" = "Anonymous Voting";
|
||||
"CreatePoll.MultipleChoice" = "Multiple Choice";
|
||||
"CreatePoll.MultipleChoiceQuizAlert" = "A quiz has one correct answer.";
|
||||
|
||||
@ -372,7 +372,7 @@ private struct CreatePollControllerState: Equatable {
|
||||
var isQuiz: Bool = false
|
||||
}
|
||||
|
||||
private func createPollControllerEntries(presentationData: PresentationData, peer: Peer, state: CreatePollControllerState, limitsConfiguration: LimitsConfiguration) -> [CreatePollEntry] {
|
||||
private func createPollControllerEntries(presentationData: PresentationData, peer: Peer, state: CreatePollControllerState, limitsConfiguration: LimitsConfiguration, defaultIsQuiz: Bool?) -> [CreatePollEntry] {
|
||||
var entries: [CreatePollEntry] = []
|
||||
|
||||
var textLimitText = ItemListSectionHeaderAccessoryText(value: "", color: .generic)
|
||||
@ -382,7 +382,13 @@ private func createPollControllerEntries(presentationData: PresentationData, pee
|
||||
}
|
||||
entries.append(.textHeader(presentationData.strings.CreatePoll_TextHeader, textLimitText))
|
||||
entries.append(.text(presentationData.strings.CreatePoll_TextPlaceholder, state.text, Int(limitsConfiguration.maxMediaCaptionLength)))
|
||||
entries.append(.optionsHeader(presentationData.strings.CreatePoll_OptionsHeader))
|
||||
let optionsHeaderTitle: String
|
||||
if let defaultIsQuiz = defaultIsQuiz, defaultIsQuiz {
|
||||
optionsHeaderTitle = presentationData.strings.CreatePoll_QuizOptionsHeader
|
||||
} else {
|
||||
optionsHeaderTitle = presentationData.strings.CreatePoll_OptionsHeader
|
||||
}
|
||||
entries.append(.optionsHeader(optionsHeaderTitle))
|
||||
for i in 0 ..< state.options.count {
|
||||
let isSecondLast = state.options.count == 2 && i == 0
|
||||
let isLast = i == state.options.count - 1
|
||||
@ -403,16 +409,26 @@ private func createPollControllerEntries(presentationData: PresentationData, pee
|
||||
if canBePublic {
|
||||
entries.append(.anonymousVotes(presentationData.strings.CreatePoll_Anonymous, state.isAnonymous))
|
||||
}
|
||||
entries.append(.multipleChoice(presentationData.strings.CreatePoll_MultipleChoice, state.isMultipleChoice && !state.isQuiz, !state.isQuiz))
|
||||
entries.append(.quiz(presentationData.strings.CreatePoll_Quiz, state.isQuiz))
|
||||
entries.append(.quizInfo(presentationData.strings.CreatePoll_QuizInfo))
|
||||
if let defaultIsQuiz = defaultIsQuiz {
|
||||
if !defaultIsQuiz {
|
||||
entries.append(.multipleChoice(presentationData.strings.CreatePoll_MultipleChoice, state.isMultipleChoice && !state.isQuiz, !state.isQuiz))
|
||||
}
|
||||
} else {
|
||||
entries.append(.multipleChoice(presentationData.strings.CreatePoll_MultipleChoice, state.isMultipleChoice && !state.isQuiz, !state.isQuiz))
|
||||
entries.append(.quiz(presentationData.strings.CreatePoll_Quiz, state.isQuiz))
|
||||
entries.append(.quizInfo(presentationData.strings.CreatePoll_QuizInfo))
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public func createPollController(context: AccountContext, peer: Peer, completion: @escaping (EnqueueMessage) -> Void) -> ViewController {
|
||||
let statePromise = ValuePromise(CreatePollControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: CreatePollControllerState())
|
||||
public func createPollController(context: AccountContext, peer: Peer, isQuiz: Bool? = nil, completion: @escaping (EnqueueMessage) -> Void) -> ViewController {
|
||||
var initialState = CreatePollControllerState()
|
||||
if let isQuiz = isQuiz {
|
||||
initialState.isQuiz = isQuiz
|
||||
}
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((CreatePollControllerState) -> CreatePollControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
@ -738,8 +754,15 @@ public func createPollController(context: AccountContext, peer: Peer, completion
|
||||
ensureVisibleItemTag = focusItemTag
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.CreatePoll_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createPollControllerEntries(presentationData: presentationData, peer: peer, state: state, limitsConfiguration: limitsConfiguration), style: .blocks, focusItemTag: focusItemTag, ensureVisibleItemTag: ensureVisibleItemTag, animateChanges: previousIds != nil)
|
||||
let title: String
|
||||
if let isQuiz = isQuiz, isQuiz {
|
||||
title = presentationData.strings.CreatePoll_QuizTitle
|
||||
} else {
|
||||
title = presentationData.strings.CreatePoll_Title
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createPollControllerEntries(presentationData: presentationData, peer: peer, state: state, limitsConfiguration: limitsConfiguration, defaultIsQuiz: isQuiz), style: .blocks, focusItemTag: focusItemTag, ensureVisibleItemTag: ensureVisibleItemTag, animateChanges: previousIds != nil)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
@ -182,6 +182,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
||||
}
|
||||
|
||||
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.editableTextNodeDidUpdateText(editableTextNode)
|
||||
self.item?.focused(false)
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
- (void)appendVideoPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime;
|
||||
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer;
|
||||
|
||||
- (void)finishRecording;
|
||||
- (void)finishRecording:(void(^)())completed;
|
||||
|
||||
- (NSTimeInterval)videoDuration;
|
||||
|
||||
|
||||
@ -105,7 +105,7 @@ typedef enum {
|
||||
if (_status != TGMovieRecorderStatusIdle)
|
||||
return;
|
||||
|
||||
[self transitionToStatus:TGMovieRecorderStatusPreparingToRecord error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusPreparingToRecord error:nil completed:nil];
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^
|
||||
@ -138,9 +138,9 @@ typedef enum {
|
||||
@synchronized (self)
|
||||
{
|
||||
if (error || !succeed)
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:nil];
|
||||
else
|
||||
[self transitionToStatus:TGMovieRecorderStatusRecording error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusRecording error:nil completed:nil];
|
||||
}
|
||||
}
|
||||
} );
|
||||
@ -169,8 +169,9 @@ typedef enum {
|
||||
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
|
||||
}
|
||||
|
||||
- (void)finishRecording
|
||||
- (void)finishRecording:(void(^)())completed
|
||||
{
|
||||
printf("finishRecording %d\n", _status);
|
||||
@synchronized (self)
|
||||
{
|
||||
bool shouldFinishRecording = false;
|
||||
@ -190,9 +191,10 @@ typedef enum {
|
||||
}
|
||||
|
||||
if (shouldFinishRecording)
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingWaiting error:nil];
|
||||
else
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingWaiting error:nil completed:completed];
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(_writingQueue, ^
|
||||
@ -201,10 +203,14 @@ typedef enum {
|
||||
{
|
||||
@synchronized (self)
|
||||
{
|
||||
if (_status != TGMovieRecorderStatusFinishingWaiting)
|
||||
if (_status != TGMovieRecorderStatusFinishingWaiting) {
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingCommiting error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinishingCommiting error:nil completed:nil];
|
||||
}
|
||||
|
||||
[_assetWriter finishWritingWithCompletionHandler:^
|
||||
@ -213,9 +219,9 @@ typedef enum {
|
||||
{
|
||||
NSError *error = _assetWriter.error;
|
||||
if (error)
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:completed];
|
||||
else
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinished error:nil];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFinished error:nil completed:completed];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@ -340,7 +346,7 @@ typedef enum {
|
||||
NSError *error = _assetWriter.error;
|
||||
@synchronized (self)
|
||||
{
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error];
|
||||
[self transitionToStatus:TGMovieRecorderStatusFailed error:error completed:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -349,8 +355,10 @@ typedef enum {
|
||||
});
|
||||
}
|
||||
|
||||
- (void)transitionToStatus:(TGMovieRecorderStatus)newStatus error:(NSError *)error
|
||||
- (void)transitionToStatus:(TGMovieRecorderStatus)newStatus error:(NSError *)error completed:(void(^)())completed
|
||||
{
|
||||
printf("recorder transitionToStatus %d\n", newStatus);
|
||||
|
||||
bool shouldNotifyDelegate = false;
|
||||
|
||||
if (newStatus != _status)
|
||||
@ -389,6 +397,7 @@ typedef enum {
|
||||
break;
|
||||
|
||||
case TGMovieRecorderStatusFinished:
|
||||
printf("TGMovieRecorderStatusFinished _delegate == nil = %d\n", (int)(_delegate == nil));
|
||||
[_delegate movieRecorderDidFinishRecording:self];
|
||||
break;
|
||||
|
||||
@ -399,9 +408,16 @@ typedef enum {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
- (void)stopRunning;
|
||||
|
||||
- (void)startRecording:(NSURL *)url preset:(TGMediaVideoConversionPreset)preset liveUpload:(bool)liveUpload;
|
||||
- (void)stopRecording;
|
||||
- (void)stopRecording:(void (^)())completed;
|
||||
|
||||
- (CGAffineTransform)transformForOrientation:(AVCaptureVideoOrientation)orientation;
|
||||
|
||||
|
||||
@ -111,6 +111,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
printf("Camera pipeline dealloc\n");
|
||||
[self destroyCaptureSession];
|
||||
}
|
||||
|
||||
@ -134,7 +135,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
{
|
||||
_running = false;
|
||||
|
||||
[self stopRecording];
|
||||
[self stopRecording:^{}];
|
||||
|
||||
[_captureSession stopRunning];
|
||||
[self captureSessionDidStopRunning];
|
||||
@ -285,7 +286,7 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)captureSessionDidStopRunning
|
||||
{
|
||||
[self stopRecording];
|
||||
[self stopRecording:^{}];
|
||||
[self destroyVideoPipeline];
|
||||
}
|
||||
|
||||
@ -684,20 +685,29 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
[recorder prepareToRecord];
|
||||
}
|
||||
|
||||
- (void)stopRecording
|
||||
- (void)stopRecording:(void (^)())completed
|
||||
{
|
||||
[[TGVideoCameraPipeline cameraQueue] dispatch:^
|
||||
{
|
||||
@synchronized (self)
|
||||
{
|
||||
if (_recordingStatus != TGVideoCameraRecordingStatusRecording)
|
||||
if (_recordingStatus != TGVideoCameraRecordingStatusRecording) {
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self transitionToRecordingStatus:TGVideoCameraRecordingStatusStoppingRecording error:nil];
|
||||
}
|
||||
|
||||
_resultDuration = _recorder.videoDuration;
|
||||
[_recorder finishRecording];
|
||||
[_recorder finishRecording:^{
|
||||
__unused __auto_type description = [self description];
|
||||
if (completed) {
|
||||
completed();
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@ -734,6 +744,8 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)movieRecorderDidFinishRecording:(TGVideoCameraMovieRecorder *)__unused recorder
|
||||
{
|
||||
printf("movieRecorderDidFinishRecording\n");
|
||||
|
||||
@synchronized (self)
|
||||
{
|
||||
if (_recordingStatus != TGVideoCameraRecordingStatusStoppingRecording)
|
||||
@ -750,6 +762,8 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
|
||||
- (void)transitionToRecordingStatus:(TGVideoCameraRecordingStatus)newStatus error:(NSError *)error
|
||||
{
|
||||
printf("transitionToRecordingStatus %d\n", newStatus);
|
||||
|
||||
TGVideoCameraRecordingStatus oldStatus = _recordingStatus;
|
||||
_recordingStatus = newStatus;
|
||||
|
||||
@ -763,12 +777,16 @@ const NSInteger TGVideoCameraRetainedBufferCount = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
__strong id<TGVideoCameraPipelineDelegate> delegate = _delegate;
|
||||
if ((oldStatus == TGVideoCameraRecordingStatusStartingRecording) && (newStatus == TGVideoCameraRecordingStatusRecording))
|
||||
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingDidStart:self]; };
|
||||
delegateCallbackBlock = ^{ [delegate capturePipelineRecordingDidStart:self]; };
|
||||
else if ((oldStatus == TGVideoCameraRecordingStatusRecording) && (newStatus == TGVideoCameraRecordingStatusStoppingRecording))
|
||||
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingWillStop:self]; };
|
||||
delegateCallbackBlock = ^{ [delegate capturePipelineRecordingWillStop:self]; };
|
||||
else if ((oldStatus == TGVideoCameraRecordingStatusStoppingRecording) && (newStatus == TGVideoCameraRecordingStatusIdle))
|
||||
delegateCallbackBlock = ^{ [_delegate capturePipelineRecordingDidStop:self duration:_resultDuration liveUploadData:_liveUploadData thumbnailImage:_recordingThumbnail thumbnails:_thumbnails]; };
|
||||
delegateCallbackBlock = ^{
|
||||
printf("transitionToRecordingStatus delegateCallbackBlock _delegate == nil = %d\n", (int)(delegate == nil));
|
||||
[delegate capturePipelineRecordingDidStop:self duration:_resultDuration liveUploadData:_liveUploadData thumbnailImage:_recordingThumbnail thumbnails:_thumbnails];
|
||||
};
|
||||
}
|
||||
|
||||
if (delegateCallbackBlock != nil)
|
||||
|
||||
@ -201,6 +201,7 @@ typedef enum
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
printf("Video controller dealloc\n");
|
||||
[_thumbnailsDisposable dispose];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_didEnterBackgroundObserver];
|
||||
[_activityDisposable dispose];
|
||||
@ -649,9 +650,11 @@ typedef enum
|
||||
return;
|
||||
|
||||
[_activityDisposable dispose];
|
||||
[self stopRecording];
|
||||
|
||||
[self dismiss:false];
|
||||
[self stopRecording:^{
|
||||
TGDispatchOnMainThread(^{
|
||||
[self dismiss:false];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)buttonInteractionUpdate:(CGPoint)value
|
||||
@ -684,7 +687,7 @@ typedef enum
|
||||
_switchButton.userInteractionEnabled = false;
|
||||
|
||||
[_activityDisposable dispose];
|
||||
[self stopRecording];
|
||||
[self stopRecording:^{}];
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -939,9 +942,9 @@ typedef enum
|
||||
[self startRecordingTimer];
|
||||
}
|
||||
|
||||
- (void)stopRecording
|
||||
- (void)stopRecording:(void (^)())completed
|
||||
{
|
||||
[_capturePipeline stopRecording];
|
||||
[_capturePipeline stopRecording:completed];
|
||||
[_buttonHandler ignoreEventsFor:1.0f andDisable:true];
|
||||
}
|
||||
|
||||
|
||||
@ -301,6 +301,8 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
|
||||
peerSupportsPolls = true
|
||||
} else if let user = peer as? TelegramUser, let _ = user.botInfo {
|
||||
peerSupportsPolls = true
|
||||
} else if peer.id == context.account.peerId {
|
||||
peerSupportsPolls = true
|
||||
}
|
||||
if peerSupportsPolls && canSendMessagesToPeer(peer) && canSendPolls {
|
||||
let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
|
||||
|
||||
@ -87,12 +87,13 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(bounds.size)
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
if !prefixSize.width.isZero {
|
||||
let prefixOffset = prefixSize.width
|
||||
rect.origin.x += prefixOffset
|
||||
rect.size.width -= prefixOffset
|
||||
}
|
||||
rect.size.width = max(rect.size.width, 10.0)
|
||||
return rect
|
||||
}
|
||||
|
||||
@ -117,7 +118,7 @@ private class SearchBarTextField: UITextField {
|
||||
let labelSize = self.placeholderLabel.measure(textRect.size)
|
||||
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX, y: textRect.minY + textOffset), size: labelSize)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(bounds.size)
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset), size: prefixSize)
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
||||
case openWebApp
|
||||
case payment
|
||||
case urlAuth(url: String, buttonId: Int32)
|
||||
case setupPoll(isQuiz: Bool?)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
||||
@ -31,6 +32,8 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
||||
self = .payment
|
||||
case 8:
|
||||
self = .urlAuth(url: decoder.decodeStringForKey("u", orElse: ""), buttonId: decoder.decodeInt32ForKey("b", orElse: 0))
|
||||
case 9:
|
||||
self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 })
|
||||
default:
|
||||
self = .text
|
||||
}
|
||||
@ -38,30 +41,37 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
switch self {
|
||||
case .text:
|
||||
encoder.encodeInt32(0, forKey: "v")
|
||||
case let .url(url):
|
||||
encoder.encodeInt32(1, forKey: "v")
|
||||
encoder.encodeString(url, forKey: "u")
|
||||
case let .callback(data):
|
||||
encoder.encodeInt32(2, forKey: "v")
|
||||
encoder.encodeBytes(data, forKey: "d")
|
||||
case .requestPhone:
|
||||
encoder.encodeInt32(3, forKey: "v")
|
||||
case .requestMap:
|
||||
encoder.encodeInt32(4, forKey: "v")
|
||||
case let .switchInline(samePeer, query):
|
||||
encoder.encodeInt32(5, forKey: "v")
|
||||
encoder.encodeInt32(samePeer ? 1 : 0, forKey: "s")
|
||||
encoder.encodeString(query, forKey: "q")
|
||||
case .openWebApp:
|
||||
encoder.encodeInt32(6, forKey: "v")
|
||||
case .payment:
|
||||
encoder.encodeInt32(7, forKey: "v")
|
||||
case let .urlAuth(url, buttonId):
|
||||
encoder.encodeInt32(8, forKey: "v")
|
||||
encoder.encodeString(url, forKey: "u")
|
||||
encoder.encodeInt32(buttonId, forKey: "b")
|
||||
case .text:
|
||||
encoder.encodeInt32(0, forKey: "v")
|
||||
case let .url(url):
|
||||
encoder.encodeInt32(1, forKey: "v")
|
||||
encoder.encodeString(url, forKey: "u")
|
||||
case let .callback(data):
|
||||
encoder.encodeInt32(2, forKey: "v")
|
||||
encoder.encodeBytes(data, forKey: "d")
|
||||
case .requestPhone:
|
||||
encoder.encodeInt32(3, forKey: "v")
|
||||
case .requestMap:
|
||||
encoder.encodeInt32(4, forKey: "v")
|
||||
case let .switchInline(samePeer, query):
|
||||
encoder.encodeInt32(5, forKey: "v")
|
||||
encoder.encodeInt32(samePeer ? 1 : 0, forKey: "s")
|
||||
encoder.encodeString(query, forKey: "q")
|
||||
case .openWebApp:
|
||||
encoder.encodeInt32(6, forKey: "v")
|
||||
case .payment:
|
||||
encoder.encodeInt32(7, forKey: "v")
|
||||
case let .urlAuth(url, buttonId):
|
||||
encoder.encodeInt32(8, forKey: "v")
|
||||
encoder.encodeString(url, forKey: "u")
|
||||
encoder.encodeInt32(buttonId, forKey: "b")
|
||||
case let .setupPoll(isQuiz):
|
||||
encoder.encodeInt32(9, forKey: "v")
|
||||
if let isQuiz = isQuiz {
|
||||
encoder.encodeInt32(isQuiz ? 1 : 0, forKey: "isq")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "isq")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +273,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1344716869] = { return Api.KeyboardButton.parse_keyboardButtonBuy($0) }
|
||||
dict[280464681] = { return Api.KeyboardButton.parse_keyboardButtonUrlAuth($0) }
|
||||
dict[-802258988] = { return Api.KeyboardButton.parse_inputKeyboardButtonUrlAuth($0) }
|
||||
dict[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
|
||||
dict[-748155807] = { return Api.ContactStatus.parse_contactStatus($0) }
|
||||
dict[1679398724] = { return Api.SecureFile.parse_secureFileEmpty($0) }
|
||||
dict[-534283678] = { return Api.SecureFile.parse_secureFile($0) }
|
||||
|
||||
@ -8428,6 +8428,7 @@ public extension Api {
|
||||
case keyboardButtonBuy(text: String)
|
||||
case keyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, buttonId: Int32)
|
||||
case inputKeyboardButtonUrlAuth(flags: Int32, text: String, fwdText: String?, url: String, bot: Api.InputUser)
|
||||
case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -8503,6 +8504,14 @@ public extension Api {
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
bot.serialize(buffer, true)
|
||||
break
|
||||
case .keyboardButtonRequestPoll(let flags, let quiz, let text):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1144565411)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {quiz!.serialize(buffer, true)}
|
||||
serializeString(text, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -8528,6 +8537,8 @@ public extension Api {
|
||||
return ("keyboardButtonUrlAuth", [("flags", flags), ("text", text), ("fwdText", fwdText), ("url", url), ("buttonId", buttonId)])
|
||||
case .inputKeyboardButtonUrlAuth(let flags, let text, let fwdText, let url, let bot):
|
||||
return ("inputKeyboardButtonUrlAuth", [("flags", flags), ("text", text), ("fwdText", fwdText), ("url", url), ("bot", bot)])
|
||||
case .keyboardButtonRequestPoll(let flags, let quiz, let text):
|
||||
return ("keyboardButtonRequestPoll", [("flags", flags), ("quiz", quiz), ("text", text)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -8679,6 +8690,25 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.Bool?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
} }
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ContactStatus: TypeConstructorDescription {
|
||||
|
||||
@ -30,6 +30,15 @@ extension ReplyMarkupButton {
|
||||
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: buttonId))
|
||||
case let .inputKeyboardButtonUrlAuth(_, text, fwdText, url, _):
|
||||
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: 0))
|
||||
case let .keyboardButtonRequestPoll(_, quiz, text):
|
||||
var isQuiz: Bool? = quiz.flatMap { quiz in
|
||||
if case .boolTrue = quiz {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
self.init(title: text, titleWhenForwarded: nil, action: .setupPoll(isQuiz: isQuiz))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -211,6 +211,8 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
|
||||
if let message = self.message {
|
||||
self.controllerInteraction.requestMessageActionUrlAuth(url, message.id, buttonId)
|
||||
}
|
||||
case let .setupPoll(isQuiz):
|
||||
self.controllerInteraction.openPollCreation(isQuiz)
|
||||
}
|
||||
if dismissIfOnce {
|
||||
if let message = self.message {
|
||||
|
||||
@ -1852,6 +1852,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
})
|
||||
}, openPollCreation: { [weak self] isQuiz in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.presentPollCreation(isQuiz: isQuiz)
|
||||
}, requestMessageUpdate: { [weak self] id in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||
@ -6115,9 +6120,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}))
|
||||
}
|
||||
|
||||
private func presentPollCreation() {
|
||||
private func presentPollCreation(isQuiz: Bool? = nil) {
|
||||
if case .peer = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.peer {
|
||||
self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, completion: { [weak self] message in
|
||||
self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, isQuiz: isQuiz, completion: { [weak self] message in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -6703,7 +6708,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) {
|
||||
self.navigateToMessage(from: nil, to: messageLocation, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, animated: animated, completion: completion, customPresentProgress: customPresentProgress)
|
||||
let scrollPosition: ListViewScrollPosition
|
||||
if case .upperBound = messageLocation {
|
||||
scrollPosition = .top(0.0)
|
||||
} else {
|
||||
scrollPosition = .center(.bottom)
|
||||
}
|
||||
self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, animated: animated, completion: completion, customPresentProgress: customPresentProgress)
|
||||
}
|
||||
|
||||
private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) {
|
||||
|
||||
@ -105,6 +105,7 @@ public final class ChatControllerInteraction {
|
||||
let displaySwipeToReplyHint: () -> Void
|
||||
let dismissReplyMarkupMessage: (Message) -> Void
|
||||
let openMessagePollResults: (MessageId, Data) -> Void
|
||||
let openPollCreation: (Bool?) -> Void
|
||||
|
||||
let requestMessageUpdate: (MessageId) -> Void
|
||||
let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -119,7 +120,7 @@ public final class ChatControllerInteraction {
|
||||
var searchTextHighightState: (String, [MessageIndex])?
|
||||
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
||||
|
||||
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
|
||||
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
|
||||
self.openMessage = openMessage
|
||||
self.openPeer = openPeer
|
||||
self.openPeerMention = openPeerMention
|
||||
@ -164,6 +165,7 @@ public final class ChatControllerInteraction {
|
||||
self.rateCall = rateCall
|
||||
self.requestSelectMessagePollOptions = requestSelectMessagePollOptions
|
||||
self.requestOpenMessagePollResults = requestOpenMessagePollResults
|
||||
self.openPollCreation = openPollCreation
|
||||
self.openAppStorePage = openAppStorePage
|
||||
self.displayMessageTooltip = displayMessageTooltip
|
||||
self.seekToTimecode = seekToTimecode
|
||||
@ -216,6 +218,7 @@ public final class ChatControllerInteraction {
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, openPollCreation: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
||||
@ -799,6 +799,8 @@ public class ChatMessageItemView: ListViewItemNode {
|
||||
item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
|
||||
case let .urlAuth(url, buttonId):
|
||||
item.controllerInteraction.requestMessageActionUrlAuth(url, item.message.id, buttonId)
|
||||
case let .setupPoll(isQuiz):
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import CheckNode
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import AvatarNode
|
||||
import TelegramPresentationData
|
||||
|
||||
private struct PercentCounterItem: Comparable {
|
||||
var index: Int = 0
|
||||
@ -373,6 +374,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
private(set) var currentSelection: ChatMessagePollOptionSelection?
|
||||
var pressed: (() -> Void)?
|
||||
var selectionUpdated: (() -> Void)?
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override init() {
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
@ -430,6 +432,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
let makeTitleLayout = TextNode.asyncLayout(maybeNode?.titleNode)
|
||||
let currentResult = maybeNode?.currentResult
|
||||
let currentSelection = maybeNode?.currentSelection
|
||||
let currentTheme = maybeNode?.theme
|
||||
|
||||
return { accountPeerId, presentationData, message, poll, option, optionResult, constrainedWidth in
|
||||
let leftInset: CGFloat = 50.0
|
||||
@ -449,8 +452,10 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
isSelectable = false
|
||||
}
|
||||
|
||||
let themeUpdated = presentationData.theme.theme !== currentTheme
|
||||
|
||||
var updatedPercentageImage: UIImage?
|
||||
if currentResult != optionResult {
|
||||
if currentResult != optionResult || themeUpdated {
|
||||
let value = optionResult?.percent ?? 0
|
||||
updatedPercentageImage = generatePercentageImage(presentationData: presentationData, incoming: incoming, value: value, targetValue: value)
|
||||
}
|
||||
@ -471,7 +476,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
if selection != currentSelection {
|
||||
if selection != currentSelection || themeUpdated {
|
||||
updatedResultIcon = true
|
||||
if let selection = selection {
|
||||
var isQuiz = false
|
||||
@ -537,6 +542,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
let previousResult = node.currentResult
|
||||
node.currentResult = optionResult
|
||||
node.currentSelection = selection
|
||||
node.theme = presentationData.theme.theme
|
||||
|
||||
node.highlightedBackgroundNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.highlight : presentationData.theme.theme.chat.message.outgoing.polls.highlight
|
||||
|
||||
@ -568,7 +574,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
||||
}
|
||||
let radioSize: CGFloat = 22.0
|
||||
radioNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: radioSize, height: radioSize))
|
||||
radioNode.update(staticColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioButton : presentationData.theme.theme.chat.message.outgoing.polls.radioButton, animatedColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioProgress : presentationData.theme.theme.chat.message.outgoing.polls.radioProgress, foregroundColor: presentationData.theme.theme.list.itemCheckColors.foregroundColor, isSelectable: isSelectable, isAnimating: inProgress)
|
||||
radioNode.update(staticColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioButton : presentationData.theme.theme.chat.message.outgoing.polls.radioButton, animatedColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.radioProgress : presentationData.theme.theme.chat.message.outgoing.polls.radioProgress, foregroundColor: incoming ? presentationData.theme.theme.chat.message.incoming.polls.barIconForeground : presentationData.theme.theme.chat.message.outgoing.polls.barIconForeground, isSelectable: isSelectable, isAnimating: inProgress)
|
||||
} else if let radioNode = node.radioNode {
|
||||
node.radioNode = nil
|
||||
if animated {
|
||||
@ -1071,7 +1077,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
if Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
|
||||
canVote = true
|
||||
canVote = false
|
||||
}
|
||||
|
||||
return (boundingSize.width, { boundingWidth in
|
||||
@ -1275,6 +1281,10 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var hasResults = false
|
||||
if poll.isClosed {
|
||||
hasResults = true
|
||||
hasSelection = false
|
||||
if let totalVoters = poll.results.totalVoters, totalVoters == 0 {
|
||||
hasResults = false
|
||||
}
|
||||
} else {
|
||||
if let totalVoters = poll.results.totalVoters, totalVoters != 0 {
|
||||
if let voters = poll.results.voters {
|
||||
@ -1351,12 +1361,43 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if optionNode.frame.contains(point), case .tap = gesture {
|
||||
if optionNode.isUserInteractionEnabled {
|
||||
return .ignore
|
||||
} else if let result = optionNode.currentResult, let item = self.item, let poll = self.poll, case .public = poll.publicity, let option = optionNode.option {
|
||||
if !isEstimating {
|
||||
item.controllerInteraction.openMessagePollResults(item.message.id, option.opaqueIdentifier)
|
||||
return .ignore
|
||||
} else if let result = optionNode.currentResult, let item = self.item, let poll = self.poll, let option = optionNode.option {
|
||||
switch poll.publicity {
|
||||
case .anonymous:
|
||||
let string: String
|
||||
switch poll.kind {
|
||||
case .poll:
|
||||
if result.count == 0 {
|
||||
string = item.presentationData.strings.MessagePoll_NoVotes
|
||||
} else {
|
||||
string = item.presentationData.strings.MessagePoll_VotedCount(result.count)
|
||||
}
|
||||
case .quiz:
|
||||
if result.count == 0 {
|
||||
string = item.presentationData.strings.MessagePoll_QuizNoUsers
|
||||
} else {
|
||||
string = item.presentationData.strings.MessagePoll_QuizCount(result.count)
|
||||
}
|
||||
}
|
||||
return .tooltip(string, optionNode, optionNode.bounds.offsetBy(dx: 0.0, dy: 10.0))
|
||||
case .public:
|
||||
var hasNonZeroVoters = false
|
||||
if let voters = poll.results.voters {
|
||||
for voter in voters {
|
||||
if voter.count != 0 {
|
||||
hasNonZeroVoters = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasNonZeroVoters {
|
||||
if !isEstimating {
|
||||
item.controllerInteraction.openMessagePollResults(item.message.id, option.opaqueIdentifier)
|
||||
return .ignore
|
||||
}
|
||||
return .openMessage
|
||||
}
|
||||
}
|
||||
return .openMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,6 +420,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, openPollCreation: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
||||
@ -19,11 +19,14 @@ private final class ParticleLayer: CALayer {
|
||||
var velocity: Vector2
|
||||
var angularVelocity: Float
|
||||
var rotationAngle: Float = 0.0
|
||||
var localTime: Float = 0.0
|
||||
var type: Int
|
||||
|
||||
init(image: CGImage, size: CGSize, position: CGPoint, mass: Float, velocity: Vector2, angularVelocity: Float) {
|
||||
init(image: CGImage, size: CGSize, position: CGPoint, mass: Float, velocity: Vector2, angularVelocity: Float, type: Int) {
|
||||
self.mass = mass
|
||||
self.velocity = velocity
|
||||
self.angularVelocity = angularVelocity
|
||||
self.type = type
|
||||
|
||||
super.init()
|
||||
|
||||
@ -80,42 +83,53 @@ final class ConfettiView: UIView {
|
||||
|
||||
let originXRange = 0 ..< Int(frame.width)
|
||||
let originYRange = Int(-frame.height) ..< Int(0)
|
||||
let topMassRange: Range<Float> = 20.0 ..< 30.0
|
||||
let topMassRange: Range<Float> = 40.0 ..< 50.0
|
||||
let velocityYRange = Float(3.0) ..< Float(5.0)
|
||||
let angularVelocityRange = Float(1.0) ..< Float(6.0)
|
||||
let sizeVariation = Float(0.8) ..< Float(1.6)
|
||||
let topDelayRange = Float(0.0) ..< Float(0.0)
|
||||
|
||||
for i in 0 ..< 70 {
|
||||
let (image, size) = images[i % imageCount]
|
||||
let sizeScale = CGFloat(Float.random(in: sizeVariation))
|
||||
let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: CGFloat(Int.random(in: originXRange)), y: CGFloat(Int.random(in: originYRange))), mass: Float.random(in: topMassRange), velocity: Vector2(x: 0.0, y: Float.random(in: velocityYRange)), angularVelocity: Float.random(in: angularVelocityRange))
|
||||
let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: CGFloat(Int.random(in: originXRange)), y: CGFloat(Int.random(in: originYRange))), mass: Float.random(in: topMassRange), velocity: Vector2(x: 0.0, y: Float.random(in: velocityYRange)), angularVelocity: Float.random(in: angularVelocityRange), type: 0)
|
||||
self.particles.append(particle)
|
||||
self.layer.addSublayer(particle)
|
||||
}
|
||||
|
||||
let sideMassRange: Range<Float> = 80.0 ..< 90.0
|
||||
let sideOriginYBase: Float = Float(frame.size.height * 8.5 / 10.0)
|
||||
let sideMassRange: Range<Float> = 110.0 ..< 120.0
|
||||
let sideOriginYBase: Float = Float(frame.size.height * 9.0 / 10.0)
|
||||
let sideOriginYVariation: Float = Float(frame.size.height / 12.0)
|
||||
let sideOriginYRange = Float(sideOriginYBase - sideOriginYVariation) ..< Float(sideOriginYBase + sideOriginYVariation)
|
||||
let sideOriginXRange = Float(0.0) ..< Float(100.0)
|
||||
let sideOriginVelocityValueRange = Float(1.1) ..< Float(1.6)
|
||||
let sideOriginVelocityValueScaling: Float = 900.0
|
||||
let sideOriginVelocityBase: Float = Float.pi / 2.0 + atanf(Float(CGFloat(sideOriginYBase) / (frame.size.width * 0.5)))
|
||||
let sideOriginVelocityVariation: Float = 0.25
|
||||
let sideOriginVelocityValueRange = Float(1.1) ..< Float(1.3)
|
||||
let sideOriginVelocityValueScaling: Float = 2400.0 * Float(frame.height) / 896.0
|
||||
let sideOriginVelocityBase: Float = Float.pi / 2.0 + atanf(Float(CGFloat(sideOriginYBase) / (frame.size.width * 0.8)))
|
||||
let sideOriginVelocityVariation: Float = 0.09
|
||||
let sideOriginVelocityAngleRange = Float(sideOriginVelocityBase - sideOriginVelocityVariation) ..< Float(sideOriginVelocityBase + sideOriginVelocityVariation)
|
||||
let originAngleRange = Float(0.0) ..< (Float.pi * 2.0)
|
||||
let originAmplitudeDiameter: CGFloat = 230.0
|
||||
let originAmplitudeRange = Float(0.0) ..< Float(originAmplitudeDiameter / 2.0)
|
||||
|
||||
let sideTypes: [Int] = [0, 1, 2]
|
||||
|
||||
for sideIndex in 0 ..< 2 {
|
||||
let sideSign: Float = sideIndex == 0 ? 1.0 : -1.0
|
||||
let originX: CGFloat = sideIndex == 0 ? -5.0 : (frame.width + 5.0)
|
||||
let baseOriginX: CGFloat = sideIndex == 0 ? -originAmplitudeDiameter / 2.0 : (frame.width + originAmplitudeDiameter / 2.0)
|
||||
|
||||
for i in 0 ..< 40 {
|
||||
let offsetX = CGFloat(Float.random(in: sideOriginXRange) * (-sideSign))
|
||||
let originAngle = Float.random(in: originAngleRange)
|
||||
let originAmplitude = Float.random(in: originAmplitudeRange)
|
||||
let originX = baseOriginX + CGFloat(cosf(originAngle) * originAmplitude)
|
||||
let originY = CGFloat(sideOriginYBase + sinf(originAngle) * originAmplitude)
|
||||
|
||||
let velocityValue = Float.random(in: sideOriginVelocityValueRange) * sideOriginVelocityValueScaling
|
||||
let velocityAngle = Float.random(in: sideOriginVelocityAngleRange)
|
||||
let velocityX = sideSign * velocityValue * sinf(velocityAngle)
|
||||
let velocityY = velocityValue * cosf(velocityAngle)
|
||||
let (image, size) = images[i % imageCount]
|
||||
let sizeScale = CGFloat(Float.random(in: sizeVariation))
|
||||
let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: originX + offsetX, y: CGFloat(Float.random(in: sideOriginYRange))), mass: Float.random(in: sideMassRange), velocity: Vector2(x: velocityX, y: velocityY), angularVelocity: Float.random(in: angularVelocityRange))
|
||||
let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: originX, y: originY), mass: Float.random(in: sideMassRange), velocity: Vector2(x: velocityX, y: velocityY), angularVelocity: Float.random(in: angularVelocityRange), type: sideTypes[i % 3])
|
||||
self.particles.append(particle)
|
||||
self.layer.addSublayer(particle)
|
||||
}
|
||||
@ -132,57 +146,89 @@ final class ConfettiView: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private var slowdownStartTimestamps: [Float?] = [nil, nil, nil]
|
||||
|
||||
private func step() {
|
||||
self.slowdownStartTimestamps[0] = 0.33
|
||||
|
||||
var haveParticlesAboveGround = false
|
||||
let minPositionY: CGFloat = 0.0
|
||||
let maxPositionY = self.bounds.height + 30.0
|
||||
let minDampingX: CGFloat = 40.0
|
||||
let maxDampingX: CGFloat = self.bounds.width - 40.0
|
||||
let centerX: CGFloat = self.bounds.width / 2.0
|
||||
let currentTime = self.localTime
|
||||
let dt: Float = 1.0 / 60.0
|
||||
let slowdownDt: Float
|
||||
let slowdownStart: Float = 0.2
|
||||
let slowdownDuration: Float = 1.0
|
||||
let damping: Float
|
||||
if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration {
|
||||
let slowdownTimestamp: Float = currentTime - slowdownStart
|
||||
|
||||
let slowdownRampInDuration: Float = 0.15
|
||||
let slowdownRampOutDuration: Float = 0.6
|
||||
let slowdownTransition: Float
|
||||
if slowdownTimestamp < slowdownRampInDuration {
|
||||
slowdownTransition = slowdownTimestamp / slowdownRampInDuration
|
||||
} else if slowdownTimestamp >= slowdownDuration - slowdownRampOutDuration {
|
||||
let reverseTransition = (slowdownTimestamp - (slowdownDuration - slowdownRampOutDuration)) / slowdownRampOutDuration
|
||||
slowdownTransition = 1.0 - reverseTransition
|
||||
let dt: Float = 1.0 * 1.0 / 60.0
|
||||
|
||||
let typeDelays: [Float] = [0.0, 0.01, 0.08]
|
||||
var dtAndDamping: [(Float, Float)] = []
|
||||
|
||||
for i in 0 ..< 3 {
|
||||
let typeDelay = typeDelays[i]
|
||||
let currentTime = self.localTime - typeDelay
|
||||
if currentTime < 0.0 {
|
||||
dtAndDamping.append((0.0, 1.0))
|
||||
} else if let slowdownStart = self.slowdownStartTimestamps[i] {
|
||||
let slowdownDt: Float
|
||||
let slowdownDuration: Float = 0.5
|
||||
let damping: Float
|
||||
if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration {
|
||||
let slowdownTimestamp: Float = currentTime - slowdownStart
|
||||
|
||||
let slowdownRampInDuration: Float = 0.05
|
||||
let slowdownRampOutDuration: Float = 0.2
|
||||
let rawSlowdownT: Float
|
||||
if slowdownTimestamp < slowdownRampInDuration {
|
||||
rawSlowdownT = slowdownTimestamp / slowdownRampInDuration
|
||||
} else if slowdownTimestamp >= slowdownDuration - slowdownRampOutDuration {
|
||||
let reverseTransition = (slowdownTimestamp - (slowdownDuration - slowdownRampOutDuration)) / slowdownRampOutDuration
|
||||
rawSlowdownT = 1.0 - reverseTransition
|
||||
} else {
|
||||
rawSlowdownT = 1.0
|
||||
}
|
||||
|
||||
let slowdownTransition = rawSlowdownT * rawSlowdownT
|
||||
|
||||
let slowdownFactor: Float = 0.8 * slowdownTransition + 1.0 * (1.0 - slowdownTransition)
|
||||
slowdownDt = dt * slowdownFactor
|
||||
let dampingFactor: Float = 0.937 * slowdownTransition + 1.0 * (1.0 - slowdownTransition)
|
||||
|
||||
damping = dampingFactor
|
||||
} else {
|
||||
slowdownDt = dt
|
||||
damping = 1.0
|
||||
}
|
||||
if i == 1 {
|
||||
//print("type 1 dt = \(slowdownDt), slowdownStart = \(slowdownStart), currentTime = \(currentTime)")
|
||||
}
|
||||
dtAndDamping.append((slowdownDt, damping))
|
||||
} else {
|
||||
slowdownTransition = 1.0
|
||||
dtAndDamping.append((dt, 1.0))
|
||||
}
|
||||
|
||||
let slowdownFactor: Float = 0.3 * slowdownTransition + 1.0 * (1.0 - slowdownTransition)
|
||||
slowdownDt = dt * slowdownFactor
|
||||
let dampingFactor: Float = 0.97 * slowdownTransition + 1.0 * (1.0 - slowdownTransition)
|
||||
damping = dampingFactor
|
||||
} else {
|
||||
slowdownDt = dt
|
||||
damping = 1.0
|
||||
}
|
||||
self.localTime += 1.0 / 60.0
|
||||
self.localTime += dt
|
||||
|
||||
let g: Vector2 = Vector2(x: 0.0, y: 9.8)
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
var turbulenceVariation: [Float] = []
|
||||
for _ in 0 ..< 20 {
|
||||
turbulenceVariation.append(Float.random(in: -9.0 ..< 9.0))
|
||||
turbulenceVariation.append(Float.random(in: -16.0 ..< 16.0) * 60.0)
|
||||
}
|
||||
let turbulenceVariationCount = turbulenceVariation.count
|
||||
var index = 0
|
||||
|
||||
var typesWithPositiveVelocity: [Bool] = [false, false, false]
|
||||
|
||||
for particle in self.particles {
|
||||
var position = particle.position
|
||||
let (localDt, damping_) = dtAndDamping[particle.type]
|
||||
if localDt.isZero {
|
||||
continue
|
||||
}
|
||||
let damping: Float = 0.93
|
||||
|
||||
let localDt: Float = slowdownDt
|
||||
particle.localTime += localDt
|
||||
|
||||
var position = particle.position
|
||||
|
||||
position.x += CGFloat(particle.velocity.x * localDt)
|
||||
position.y += CGFloat(particle.velocity.y * localDt)
|
||||
@ -196,10 +242,12 @@ final class ConfettiView: UIView {
|
||||
var velocity = particle.velocity
|
||||
velocity.x += acceleration.x * particle.mass * localDt
|
||||
velocity.y += acceleration.y * particle.mass * localDt
|
||||
velocity.x += turbulenceVariation[index % turbulenceVariationCount]
|
||||
if position.y > minPositionY {
|
||||
if velocity.y < 0.0 {
|
||||
velocity.x *= damping
|
||||
velocity.y *= damping
|
||||
} else {
|
||||
velocity.x += turbulenceVariation[index % turbulenceVariationCount] * localDt
|
||||
typesWithPositiveVelocity[particle.type] = true
|
||||
}
|
||||
particle.velocity = velocity
|
||||
|
||||
@ -209,6 +257,11 @@ final class ConfettiView: UIView {
|
||||
haveParticlesAboveGround = true
|
||||
}
|
||||
}
|
||||
for i in 0 ..< 3 {
|
||||
if typesWithPositiveVelocity[i] && self.slowdownStartTimestamps[i] == nil {
|
||||
self.slowdownStartTimestamps[i] = max(0.0, self.localTime - typeDelays[i])
|
||||
}
|
||||
}
|
||||
CATransaction.commit()
|
||||
if !haveParticlesAboveGround {
|
||||
self.displayLink?.isPaused = true
|
||||
|
||||
@ -135,16 +135,11 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect,
|
||||
done?(time)
|
||||
}
|
||||
}
|
||||
controller.finishedWithVideo = { [weak controller] videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp in
|
||||
controller.finishedWithVideo = { videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp in
|
||||
guard let videoUrl = videoUrl else {
|
||||
return
|
||||
}
|
||||
|
||||
let strongController = controller
|
||||
Queue.mainQueue().after(4.0, {
|
||||
strongController?.resignFirstResponder()
|
||||
})
|
||||
|
||||
var finalDimensions: CGSize = dimensions
|
||||
var finalDuration: Double = duration
|
||||
|
||||
|
||||
@ -121,6 +121,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, openPollCreation: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
|
||||
@ -426,6 +426,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, openPollCreation: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
||||
@ -155,7 +155,7 @@ private enum PollResultsEntry: ItemListNodeEntry {
|
||||
}
|
||||
|
||||
private struct PollResultsControllerState: Equatable {
|
||||
var expandedOptions = Set<Data>()
|
||||
var expandedOptions: [Data: Int] = [:]
|
||||
}
|
||||
|
||||
private func pollResultsControllerEntries(presentationData: PresentationData, poll: TelegramMediaPoll, state: PollResultsControllerState, resultsState: PollResultsState) -> [PollResultsEntry] {
|
||||
@ -215,24 +215,40 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po
|
||||
} else {
|
||||
if let optionState = resultsState.options[option.opaqueIdentifier], !optionState.peers.isEmpty {
|
||||
var hasMore = false
|
||||
let optionExpanded = state.expandedOptions.contains(option.opaqueIdentifier)
|
||||
let optionExpandedAtCount = state.expandedOptions[option.opaqueIdentifier]
|
||||
|
||||
let peers = optionState.peers
|
||||
let count = optionState.count
|
||||
|
||||
let displayCount: Int
|
||||
if peers.count > collapsedInitialLimit {
|
||||
displayCount = collapsedResultCount
|
||||
if peers.count > collapsedInitialLimit + 1 {
|
||||
if optionExpandedAtCount != nil {
|
||||
displayCount = peers.count
|
||||
} else {
|
||||
displayCount = collapsedResultCount
|
||||
}
|
||||
} else {
|
||||
displayCount = peers.count
|
||||
if let optionExpandedAtCount = optionExpandedAtCount {
|
||||
if optionExpandedAtCount == collapsedInitialLimit + 1 && optionState.canLoadMore {
|
||||
displayCount = collapsedResultCount
|
||||
} else {
|
||||
displayCount = peers.count
|
||||
}
|
||||
} else {
|
||||
if !optionState.canLoadMore {
|
||||
displayCount = peers.count
|
||||
} else {
|
||||
displayCount = collapsedResultCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var peerIndex = 0
|
||||
inner: for peer in peers {
|
||||
if !optionExpanded && peerIndex >= displayCount {
|
||||
if peerIndex >= displayCount {
|
||||
break inner
|
||||
}
|
||||
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionCount: Int32(count), optionExpanded: optionExpanded, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: nil, isFirstInOption: peerIndex == 0))
|
||||
entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionCount: Int32(count), optionExpanded: optionExpandedAtCount != nil, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: nil, isFirstInOption: peerIndex == 0))
|
||||
peerIndex += 1
|
||||
}
|
||||
|
||||
@ -265,20 +281,21 @@ public func pollResultsController(context: AccountContext, messageId: MessageId,
|
||||
collapseOption: { optionId in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.expandedOptions.remove(optionId)
|
||||
state.expandedOptions.removeValue(forKey: optionId)
|
||||
return state
|
||||
}
|
||||
}, expandOption: { optionId in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.expandedOptions.insert(optionId)
|
||||
return state
|
||||
}
|
||||
let _ = (resultsContext.state
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak resultsContext] state in
|
||||
if let optionState = state.options[optionId] {
|
||||
if optionState.canLoadMore && optionState.peers.count <= collapsedResultCount {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.expandedOptions[optionId] = optionState.peers.count
|
||||
return state
|
||||
}
|
||||
|
||||
if optionState.canLoadMore {
|
||||
resultsContext?.loadMore(optionOpaqueIdentifier: optionId)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -1099,41 +1099,42 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
if tapMessage != nil || clickThroughMessage != nil {
|
||||
controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: { message in
|
||||
tapMessage?(message)
|
||||
}, clickThroughMessage: {
|
||||
clickThroughMessage?()
|
||||
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
|
||||
}, canSetupReply: { _ in
|
||||
return false
|
||||
}, navigateToFirstDateMessage: { _ in
|
||||
}, requestRedeliveryOfFailedMessages: { _ in
|
||||
}, addContact: { _ in
|
||||
}, rateCall: { _, _ in
|
||||
}, requestSelectMessagePollOptions: { _, _ in
|
||||
}, requestOpenMessagePollResults: { _, _ in
|
||||
}, openAppStorePage: {
|
||||
}, displayMessageTooltip: { _, _, _, _ in
|
||||
}, seekToTimecode: { _, _, _ in
|
||||
}, scheduleCurrentMessage: {
|
||||
}, sendScheduledMessagesNow: { _ in
|
||||
}, editScheduledMessagesTime: { _ in
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
tapMessage?(message)
|
||||
}, clickThroughMessage: {
|
||||
clickThroughMessage?()
|
||||
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
return nil
|
||||
}, reactionContainerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
|
||||
}, canSetupReply: { _ in
|
||||
return false
|
||||
}, navigateToFirstDateMessage: { _ in
|
||||
}, requestRedeliveryOfFailedMessages: { _ in
|
||||
}, addContact: { _ in
|
||||
}, rateCall: { _, _ in
|
||||
}, requestSelectMessagePollOptions: { _, _ in
|
||||
}, requestOpenMessagePollResults: { _, _ in
|
||||
}, openAppStorePage: {
|
||||
}, displayMessageTooltip: { _, _, _, _ in
|
||||
}, seekToTimecode: { _, _, _ in
|
||||
}, scheduleCurrentMessage: {
|
||||
}, sendScheduledMessagesNow: { _ in
|
||||
}, editScheduledMessagesTime: { _ in
|
||||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
}, openPollCreation: { _ in
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
} else {
|
||||
controllerInteraction = defaultChatControllerInteraction
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user