mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
d1f2b29370
@ -719,14 +719,19 @@ private final class NotificationServiceHandler {
|
|||||||
|
|
||||||
let semaphore = DispatchSemaphore(value: 0)
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
var loggingSettings = LoggingSettings.defaultSettings
|
var loggingSettings = LoggingSettings.defaultSettings
|
||||||
let _ = (self.accountManager.transaction { transaction -> LoggingSettings in
|
if buildConfig.isInternalBuild {
|
||||||
|
loggingSettings = LoggingSettings(logToFile: true, logToConsole: false, redactSensitiveData: true)
|
||||||
|
}
|
||||||
|
let _ = (self.accountManager.transaction { transaction -> LoggingSettings? in
|
||||||
if let value = transaction.getSharedData(SharedDataKeys.loggingSettings)?.get(LoggingSettings.self) {
|
if let value = transaction.getSharedData(SharedDataKeys.loggingSettings)?.get(LoggingSettings.self) {
|
||||||
return value
|
return value
|
||||||
} else {
|
} else {
|
||||||
return LoggingSettings.defaultSettings
|
return nil
|
||||||
}
|
}
|
||||||
}).start(next: { value in
|
}).start(next: { value in
|
||||||
loggingSettings = value
|
if let value {
|
||||||
|
loggingSettings = value
|
||||||
|
}
|
||||||
semaphore.signal()
|
semaphore.signal()
|
||||||
})
|
})
|
||||||
semaphore.wait()
|
semaphore.wait()
|
||||||
|
@ -70,6 +70,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case redactSensitiveData(PresentationTheme, Bool)
|
case redactSensitiveData(PresentationTheme, Bool)
|
||||||
case keepChatNavigationStack(PresentationTheme, Bool)
|
case keepChatNavigationStack(PresentationTheme, Bool)
|
||||||
case skipReadHistory(PresentationTheme, Bool)
|
case skipReadHistory(PresentationTheme, Bool)
|
||||||
|
case unidirectionalSwipeToReply(Bool)
|
||||||
case crashOnSlowQueries(PresentationTheme, Bool)
|
case crashOnSlowQueries(PresentationTheme, Bool)
|
||||||
case crashOnMemoryPressure(PresentationTheme, Bool)
|
case crashOnMemoryPressure(PresentationTheme, Bool)
|
||||||
case clearTips(PresentationTheme)
|
case clearTips(PresentationTheme)
|
||||||
@ -118,7 +119,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logs.rawValue
|
return DebugControllerSection.logs.rawValue
|
||||||
case .logToFile, .logToConsole, .redactSensitiveData:
|
case .logToFile, .logToConsole, .redactSensitiveData:
|
||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries, .crashOnMemoryPressure:
|
case .keepChatNavigationStack, .skipReadHistory, .unidirectionalSwipeToReply, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases:
|
case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
@ -165,46 +166,48 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 14
|
return 14
|
||||||
case .skipReadHistory:
|
case .skipReadHistory:
|
||||||
return 15
|
return 15
|
||||||
case .crashOnSlowQueries:
|
case .unidirectionalSwipeToReply:
|
||||||
return 16
|
return 16
|
||||||
case .crashOnMemoryPressure:
|
case .crashOnSlowQueries:
|
||||||
return 17
|
return 17
|
||||||
case .clearTips:
|
case .crashOnMemoryPressure:
|
||||||
return 18
|
return 18
|
||||||
case .resetNotifications:
|
case .clearTips:
|
||||||
return 19
|
return 19
|
||||||
case .crash:
|
case .resetNotifications:
|
||||||
return 20
|
return 20
|
||||||
case .resetData:
|
case .crash:
|
||||||
return 21
|
return 21
|
||||||
case .resetDatabase:
|
case .resetData:
|
||||||
return 22
|
return 22
|
||||||
case .resetDatabaseAndCache:
|
case .resetDatabase:
|
||||||
return 23
|
return 23
|
||||||
case .resetHoles:
|
case .resetDatabaseAndCache:
|
||||||
return 24
|
return 24
|
||||||
case .reindexUnread:
|
case .resetHoles:
|
||||||
return 25
|
return 25
|
||||||
case .resetCacheIndex:
|
case .reindexUnread:
|
||||||
return 26
|
return 26
|
||||||
case .reindexCache:
|
case .resetCacheIndex:
|
||||||
return 27
|
return 27
|
||||||
case .resetBiometricsData:
|
case .reindexCache:
|
||||||
return 28
|
return 28
|
||||||
case .resetWebViewCache:
|
case .resetBiometricsData:
|
||||||
return 29
|
return 29
|
||||||
case .optimizeDatabase:
|
case .resetWebViewCache:
|
||||||
return 30
|
return 30
|
||||||
case .photoPreview:
|
case .optimizeDatabase:
|
||||||
return 31
|
return 31
|
||||||
case .knockoutWallpaper:
|
case .photoPreview:
|
||||||
return 32
|
return 32
|
||||||
case .experimentalCompatibility:
|
case .knockoutWallpaper:
|
||||||
return 33
|
return 33
|
||||||
case .enableDebugDataDisplay:
|
case .experimentalCompatibility:
|
||||||
return 34
|
return 34
|
||||||
case .acceleratedStickers:
|
case .enableDebugDataDisplay:
|
||||||
return 35
|
return 35
|
||||||
|
case .acceleratedStickers:
|
||||||
|
return 36
|
||||||
case .inlineForums:
|
case .inlineForums:
|
||||||
return 37
|
return 37
|
||||||
case .localTranscription:
|
case .localTranscription:
|
||||||
@ -928,6 +931,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return settings
|
return settings
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
|
case let .unidirectionalSwipeToReply(value):
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, title: "Legacy swipe to reply", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||||
|
var settings = settings
|
||||||
|
settings.unidirectionalSwipeToReply = value
|
||||||
|
return settings
|
||||||
|
}).start()
|
||||||
|
})
|
||||||
case let .crashOnSlowQueries(_, value):
|
case let .crashOnSlowQueries(_, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||||
@ -1375,6 +1386,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
|
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
|
||||||
#endif
|
#endif
|
||||||
|
entries.append(.unidirectionalSwipeToReply(experimentalSettings.unidirectionalSwipeToReply))
|
||||||
}
|
}
|
||||||
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
|
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
|
||||||
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))
|
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))
|
||||||
|
@ -10,6 +10,9 @@ objc_library(
|
|||||||
hdrs = glob([
|
hdrs = glob([
|
||||||
"Public/**/*.h",
|
"Public/**/*.h",
|
||||||
]),
|
]),
|
||||||
|
copts = [
|
||||||
|
"-Werror",
|
||||||
|
],
|
||||||
includes = [
|
includes = [
|
||||||
"Public",
|
"Public",
|
||||||
],
|
],
|
||||||
|
@ -4,6 +4,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
#define FFMPEG_AVSEEK_SIZE 0x10000
|
#define FFMPEG_AVSEEK_SIZE 0x10000
|
||||||
|
|
||||||
|
extern int FFMPEG_CONSTANT_AVERROR_EOF;
|
||||||
|
|
||||||
@interface FFMpegAVIOContext : NSObject
|
@interface FFMpegAVIOContext : NSObject
|
||||||
|
|
||||||
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek isSeekable:(bool)isSeekable;
|
- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek isSeekable:(bool)isSeekable;
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
#import "libavcodec/avcodec.h"
|
#import "libavcodec/avcodec.h"
|
||||||
|
|
||||||
@interface FFMpegAVCodec () {
|
@interface FFMpegAVCodec () {
|
||||||
AVCodec *_impl;
|
AVCodec const *_impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation FFMpegAVCodec
|
@implementation FFMpegAVCodec
|
||||||
|
|
||||||
- (instancetype)initWithImpl:(AVCodec *)impl {
|
- (instancetype)initWithImpl:(AVCodec const *)impl {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_impl = impl;
|
_impl = impl;
|
||||||
@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
+ (FFMpegAVCodec * _Nullable)findForId:(int)codecId {
|
+ (FFMpegAVCodec * _Nullable)findForId:(int)codecId {
|
||||||
AVCodec *codec = avcodec_find_decoder(codecId);
|
AVCodec const *codec = avcodec_find_decoder(codecId);
|
||||||
if (codec) {
|
if (codec) {
|
||||||
return [[FFMpegAVCodec alloc] initWithImpl:codec];
|
return [[FFMpegAVCodec alloc] initWithImpl:codec];
|
||||||
} else {
|
} else {
|
||||||
@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void *)impl {
|
- (void *)impl {
|
||||||
return _impl;
|
return (void *)_impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -35,7 +35,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (int32_t)channels {
|
- (int32_t)channels {
|
||||||
|
#if LIBAVFORMAT_VERSION_MAJOR >= 59
|
||||||
|
return (int32_t)_impl->ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
return (int32_t)_impl->channels;
|
return (int32_t)_impl->channels;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int32_t)sampleRate {
|
- (int32_t)sampleRate {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#import <FFMpegBinding/FFMpegPacket.h>
|
#import <FFMpegBinding/FFMpegPacket.h>
|
||||||
#import <FFMpegBinding/FFMpegAVCodecContext.h>
|
#import <FFMpegBinding/FFMpegAVCodecContext.h>
|
||||||
|
|
||||||
|
#import "libavcodec/avcodec.h"
|
||||||
#import "libavformat/avformat.h"
|
#import "libavformat/avformat.h"
|
||||||
|
|
||||||
int FFMpegCodecIdH264 = AV_CODEC_ID_H264;
|
int FFMpegCodecIdH264 = AV_CODEC_ID_H264;
|
||||||
@ -115,9 +116,9 @@ int FFMpegCodecIdVP9 = AV_CODEC_ID_VP9;
|
|||||||
|
|
||||||
if (stream->time_base.den != 0 && stream->time_base.num != 0) {
|
if (stream->time_base.den != 0 && stream->time_base.num != 0) {
|
||||||
timebase = CMTimeMake((int64_t)stream->time_base.num, stream->time_base.den);
|
timebase = CMTimeMake((int64_t)stream->time_base.num, stream->time_base.den);
|
||||||
} else if (stream->codec->time_base.den != 0 && stream->codec->time_base.num != 0) {
|
}/* else if (stream->codec->time_base.den != 0 && stream->codec->time_base.num != 0) {
|
||||||
timebase = CMTimeMake((int64_t)stream->codec->time_base.num, stream->codec->time_base.den);
|
timebase = CMTimeMake((int64_t)stream->codec->time_base.num, stream->codec->time_base.den);
|
||||||
} else {
|
}*/ else {
|
||||||
timebase = defaultTimeBase;
|
timebase = defaultTimeBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
if (_impl) {
|
if (_impl) {
|
||||||
av_frame_unref(_impl);
|
av_frame_free(&_impl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (int64_t)duration {
|
- (int64_t)duration {
|
||||||
|
#if LIBAVFORMAT_VERSION_MAJOR >= 59
|
||||||
|
return _impl->duration;
|
||||||
|
#else
|
||||||
return _impl->pkt_duration;
|
return _impl->pkt_duration;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (FFMpegAVFrameColorRange)colorRange {
|
- (FFMpegAVFrameColorRange)colorRange {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#import "libavformat/avformat.h"
|
#import "libavformat/avformat.h"
|
||||||
|
|
||||||
|
int FFMPEG_CONSTANT_AVERROR_EOF = AVERROR_EOF;
|
||||||
|
|
||||||
@interface FFMpegAVIOContext () {
|
@interface FFMpegAVIOContext () {
|
||||||
AVIOContext *_impl;
|
AVIOContext *_impl;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
#else
|
#else
|
||||||
av_log_set_level(AV_LOG_QUIET);
|
av_log_set_level(AV_LOG_QUIET);
|
||||||
#endif
|
#endif
|
||||||
av_register_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
#import <FFMpegBinding/FFMpegAVCodecContext.h>
|
#import <FFMpegBinding/FFMpegAVCodecContext.h>
|
||||||
|
|
||||||
|
#import "libavcodec/avcodec.h"
|
||||||
#import "libavformat/avformat.h"
|
#import "libavformat/avformat.h"
|
||||||
|
|
||||||
@interface FFMpegPacket () {
|
@interface FFMpegPacket () {
|
||||||
AVPacket _impl;
|
AVPacket *_impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -15,49 +16,49 @@
|
|||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
av_init_packet(&_impl);
|
_impl = av_packet_alloc();
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
av_packet_unref(&_impl);
|
av_packet_free(&_impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void *)impl {
|
- (void *)impl {
|
||||||
return &_impl;
|
return _impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int64_t)pts {
|
- (int64_t)pts {
|
||||||
if (_impl.pts == 0x8000000000000000) {
|
if (_impl->pts == 0x8000000000000000) {
|
||||||
return _impl.dts;
|
return _impl->dts;
|
||||||
} else {
|
} else {
|
||||||
return _impl.pts;
|
return _impl->pts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int64_t)dts {
|
- (int64_t)dts {
|
||||||
return _impl.dts;
|
return _impl->dts;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int64_t)duration {
|
- (int64_t)duration {
|
||||||
return _impl.duration;
|
return _impl->duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int32_t)streamIndex {
|
- (int32_t)streamIndex {
|
||||||
return (int32_t)_impl.stream_index;
|
return (int32_t)_impl->stream_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int32_t)size {
|
- (int32_t)size {
|
||||||
return (int32_t)_impl.size;
|
return (int32_t)_impl->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (uint8_t *)data {
|
- (uint8_t *)data {
|
||||||
return _impl.data;
|
return _impl->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int32_t)sendToDecoder:(FFMpegAVCodecContext *)codecContext {
|
- (int32_t)sendToDecoder:(FFMpegAVCodecContext *)codecContext {
|
||||||
return avcodec_send_packet((AVCodecContext *)[codecContext impl], &_impl);
|
return avcodec_send_packet((AVCodecContext *)[codecContext impl], _impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -34,18 +34,18 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static int readPacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
|
/*static int readPacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
|
||||||
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
||||||
context->_offset += length;
|
context->_offset += length;
|
||||||
printf("read %lld bytes (offset is now %lld)\n", length, context->_offset);
|
printf("read %lld bytes (offset is now %lld)\n", (int64_t)length, context->_offset);
|
||||||
return read(context->_fd, buffer, length);
|
return (int)read(context->_fd, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int writePacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
|
static int writePacketImpl(void * _Nullable opaque, uint8_t * _Nullable buffer, int length) {
|
||||||
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
FFMpegRemuxerContext *context = (__bridge FFMpegRemuxerContext *)opaque;
|
||||||
context->_offset += length;
|
context->_offset += length;
|
||||||
printf("write %lld bytes (offset is now %lld)\n", length, context->_offset);
|
printf("write %lld bytes (offset is now %lld)\n", (int64_t)length, context->_offset);
|
||||||
return write(context->_fd, buffer, length);
|
return (int)write(context->_fd, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t seekImpl(void * _Nullable opaque, int64_t offset, int whence) {
|
static int64_t seekImpl(void * _Nullable opaque, int64_t offset, int whence) {
|
||||||
@ -57,7 +57,7 @@ static int64_t seekImpl(void * _Nullable opaque, int64_t offset, int whence) {
|
|||||||
context->_offset = offset;
|
context->_offset = offset;
|
||||||
return lseek(context->_fd, offset, SEEK_SET);
|
return lseek(context->_fd, offset, SEEK_SET);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
@implementation FFMpegRemuxer
|
@implementation FFMpegRemuxer
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ static int64_t seekImpl(void * _Nullable opaque, int64_t offset, int whence) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
number_of_streams = input_format_context->nb_streams;
|
number_of_streams = input_format_context->nb_streams;
|
||||||
streams_list = av_mallocz_array(number_of_streams, sizeof(*streams_list));
|
streams_list = av_malloc_array(number_of_streams, sizeof(*streams_list));
|
||||||
|
|
||||||
if (!streams_list) {
|
if (!streams_list) {
|
||||||
ret = AVERROR(ENOMEM);
|
ret = AVERROR(ENOMEM);
|
||||||
|
@ -52,7 +52,9 @@
|
|||||||
swr_free(&_context);
|
swr_free(&_context);
|
||||||
_context = NULL;
|
_context = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
_context = swr_alloc_set_opts(NULL,
|
_context = swr_alloc_set_opts(NULL,
|
||||||
av_get_default_channel_layout((int)_destinationChannelCount),
|
av_get_default_channel_layout((int)_destinationChannelCount),
|
||||||
(enum AVSampleFormat)_destinationSampleFormat,
|
(enum AVSampleFormat)_destinationSampleFormat,
|
||||||
@ -62,6 +64,7 @@
|
|||||||
(int)_sourceSampleRate,
|
(int)_sourceSampleRate,
|
||||||
0,
|
0,
|
||||||
NULL);
|
NULL);
|
||||||
|
#pragma clang diagnostic pop
|
||||||
_currentSourceChannelCount = channelCount;
|
_currentSourceChannelCount = channelCount;
|
||||||
_ratio = MAX(1, _destinationSampleRate / MAX(_sourceSampleRate, 1)) * MAX(1, _destinationChannelCount / channelCount) * 2;
|
_ratio = MAX(1, _destinationSampleRate / MAX(_sourceSampleRate, 1)) * MAX(1, _destinationChannelCount / channelCount) * 2;
|
||||||
if (_context) {
|
if (_context) {
|
||||||
@ -72,7 +75,11 @@
|
|||||||
- (NSData * _Nullable)resample:(FFMpegAVFrame *)frame {
|
- (NSData * _Nullable)resample:(FFMpegAVFrame *)frame {
|
||||||
AVFrame *frameImpl = (AVFrame *)[frame impl];
|
AVFrame *frameImpl = (AVFrame *)[frame impl];
|
||||||
|
|
||||||
|
#if LIBAVFORMAT_VERSION_MAJOR >= 59
|
||||||
|
int numChannels = frameImpl->ch_layout.nb_channels;
|
||||||
|
#else
|
||||||
int numChannels = frameImpl->channels;
|
int numChannels = frameImpl->channels;
|
||||||
|
#endif
|
||||||
if (numChannels != _currentSourceChannelCount) {
|
if (numChannels != _currentSourceChannelCount) {
|
||||||
[self resetContextForChannelCount:numChannels];
|
[self resetContextForChannelCount:numChannels];
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
while true {
|
while true {
|
||||||
let result = self.codecContext.receive(into: self.audioFrame)
|
let result = self.codecContext.receive(into: self.audioFrame)
|
||||||
if case .success = result {
|
if case .success = result {
|
||||||
if let convertedFrame = convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration) {
|
if let convertedFrame = convertAudioFrame(self.audioFrame, pts: frame.pts) {
|
||||||
self.delayedFrames.append(convertedFrame)
|
self.delayedFrames.append(convertedFrame)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -121,7 +121,7 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? {
|
private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime) -> MediaTrackFrame? {
|
||||||
guard let data = self.swrContext.resample(frame) else {
|
guard let data = self.swrContext.resample(frame) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -135,18 +135,12 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//var timingInfo = CMSampleTimingInfo(duration: duration, presentationTimeStamp: pts, decodeTimeStamp: pts)
|
|
||||||
var sampleBuffer: CMSampleBuffer?
|
var sampleBuffer: CMSampleBuffer?
|
||||||
//var sampleSize = data.count
|
|
||||||
|
|
||||||
guard CMAudioSampleBufferCreateReadyWithPacketDescriptions(allocator: nil, dataBuffer: blockBuffer!, formatDescription: self.formatDescription, sampleCount: Int(data.count / 2), presentationTimeStamp: pts, packetDescriptions: nil, sampleBufferOut: &sampleBuffer) == noErr else {
|
guard CMAudioSampleBufferCreateReadyWithPacketDescriptions(allocator: nil, dataBuffer: blockBuffer!, formatDescription: self.formatDescription, sampleCount: Int(data.count / 2), presentationTimeStamp: pts, packetDescriptions: nil, sampleBufferOut: &sampleBuffer) == noErr else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*guard CMSampleBufferCreate(allocator: nil, dataBuffer: blockBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: self.formatDescription, sampleCount: Int(frame.duration), sampleTimingEntryCount: 1, sampleTimingArray: &timingInfo, sampleSizeEntryCount: 1, sampleSizeArray: &sampleSize, sampleBufferOut: &sampleBuffer) == noErr else {
|
|
||||||
return nil
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let resetDecoder = self.resetDecoderOnNextFrame
|
let resetDecoder = self.resetDecoderOnNextFrame
|
||||||
self.resetDecoderOnNextFrame = false
|
self.resetDecoderOnNextFrame = false
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
|||||||
}
|
}
|
||||||
if totalCount > maximumFetchSize {
|
if totalCount > maximumFetchSize {
|
||||||
context.readingError = true
|
context.readingError = true
|
||||||
return 0
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
|||||||
disposable.dispose()
|
disposable.dispose()
|
||||||
if !completedRequest {
|
if !completedRequest {
|
||||||
context.readingError = true
|
context.readingError = true
|
||||||
return 0
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,7 +176,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
|||||||
disposable.dispose()
|
disposable.dispose()
|
||||||
if !completedRequest {
|
if !completedRequest {
|
||||||
context.readingError = true
|
context.readingError = true
|
||||||
return 0
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
|||||||
|
|
||||||
if context.closed {
|
if context.closed {
|
||||||
context.readingError = true
|
context.readingError = true
|
||||||
return 0
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
}
|
}
|
||||||
return fetchedCount
|
return fetchedCount
|
||||||
}
|
}
|
||||||
@ -725,7 +725,7 @@ private func videoFrameFromPacket(_ packet: FFMpegPacket, videoStream: StreamCon
|
|||||||
if frameDuration != 0 {
|
if frameDuration != 0 {
|
||||||
duration = CMTimeMake(value: frameDuration * videoStream.timebase.value, timescale: videoStream.timebase.timescale)
|
duration = CMTimeMake(value: frameDuration * videoStream.timebase.value, timescale: videoStream.timebase.timescale)
|
||||||
} else {
|
} else {
|
||||||
duration = videoStream.fps
|
duration = CMTimeMake(value: Int64(videoStream.fps.timescale), timescale: Int32(videoStream.fps.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
return MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration)
|
return MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration)
|
||||||
|
@ -11,9 +11,13 @@ import FFMpegBinding
|
|||||||
private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer<UInt8>?, bufferSize: Int32) -> Int32 {
|
private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer<UInt8>?, bufferSize: Int32) -> Int32 {
|
||||||
let context = Unmanaged<SoftwareVideoSource>.fromOpaque(userData!).takeUnretainedValue()
|
let context = Unmanaged<SoftwareVideoSource>.fromOpaque(userData!).takeUnretainedValue()
|
||||||
if let fd = context.fd {
|
if let fd = context.fd {
|
||||||
return Int32(read(fd, buffer, Int(bufferSize)))
|
let result = read(fd, buffer, Int(bufferSize))
|
||||||
|
if result == 0 {
|
||||||
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
|
}
|
||||||
|
return Int32(result)
|
||||||
}
|
}
|
||||||
return 0
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 {
|
private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 {
|
||||||
|
@ -71,7 +71,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
|||||||
context.readingOffset += Int64(fetchedCount)
|
context.readingOffset += Int64(fetchedCount)
|
||||||
return fetchedCount
|
return fetchedCount
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return FFMPEG_CONSTANT_AVERROR_EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,13 +98,13 @@ static void MTNetworkAvailabilityContextRelease(const void *info)
|
|||||||
MTTimer *timer = _timer;
|
MTTimer *timer = _timer;
|
||||||
_timer = nil;
|
_timer = nil;
|
||||||
|
|
||||||
[[MTNetworkAvailability networkAvailabilityQueue] dispatchOnQueue:^
|
[[MTNetworkAvailability networkAvailabilityQueue] dispatchOnQueue:^{
|
||||||
{
|
[timer invalidate];
|
||||||
[timer invalidate];
|
|
||||||
|
SCNetworkReachabilitySetCallback(reachability, NULL, NULL);
|
||||||
SCNetworkReachabilitySetCallback(reachability, NULL, NULL);
|
SCNetworkReachabilitySetDispatchQueue(reachability, NULL);
|
||||||
SCNetworkReachabilitySetDispatchQueue(reachability, NULL);
|
CFRelease(reachability);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (MTQueue *)networkAvailabilityQueue
|
+ (MTQueue *)networkAvailabilityQueue
|
||||||
|
@ -270,7 +270,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
let moreButtonTag = local.moreButtonTag
|
let moreButtonTag = local.moreButtonTag
|
||||||
let moreAnimationTag = local.moreAnimationTag
|
let moreAnimationTag = local.moreAnimationTag
|
||||||
|
|
||||||
func makeBody() -> CGSize {
|
let makeBody: () -> CGSize = { [weak local] in
|
||||||
let canEnforceOrientation = UIDevice.current.model != "iPad"
|
let canEnforceOrientation = UIDevice.current.model != "iPad"
|
||||||
var forceFullScreenInLandscape: Bool { canEnforceOrientation && true }
|
var forceFullScreenInLandscape: Bool { canEnforceOrientation && true }
|
||||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
@ -289,13 +289,20 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
let state = context.state
|
let state = context.state
|
||||||
let controller = environment.controller
|
let controller = environment.controller
|
||||||
|
|
||||||
context.state.deactivatePictureInPictureIfVisible.connect {
|
context.state.deactivatePictureInPictureIfVisible.connect { [weak state] in
|
||||||
guard let controller = controller(), controller.view.window != nil else {
|
guard let controller = controller(), controller.view.window != nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
state.updated(transition: .easeInOut(duration: 3))
|
state.updated(transition: .easeInOut(duration: 3))
|
||||||
deactivatePictureInPicture.invoke(Void())
|
|
||||||
|
guard let local else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
local.deactivatePictureInPicture.invoke(Void())
|
||||||
}
|
}
|
||||||
let isFullscreen: Bool
|
let isFullscreen: Bool
|
||||||
let isLandscape = context.availableSize.width > context.availableSize.height
|
let isLandscape = context.availableSize.width > context.availableSize.height
|
||||||
@ -347,8 +354,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
availableSize: CGSize(width: context.availableSize.width, height: dismissTapAreaHeight),
|
availableSize: CGSize(width: context.availableSize.width, height: dismissTapAreaHeight),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
// (controller() as? MediaStreamComponentController)?.prefersOnScreenNavigationHidden = isFullscreen
|
|
||||||
// (controller() as? MediaStreamComponentController)?.window?.invalidatePrefersOnScreenNavigationHidden()
|
|
||||||
let video = video.update(
|
let video = video.update(
|
||||||
component: MediaStreamVideoComponent(
|
component: MediaStreamVideoComponent(
|
||||||
call: context.component.call,
|
call: context.component.call,
|
||||||
@ -832,6 +838,8 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
UIColor(red: 0.314, green: 0.161, blue: 0.197, alpha: 1).cgColor
|
UIColor(red: 0.314, green: 0.161, blue: 0.197, alpha: 1).cgColor
|
||||||
],
|
],
|
||||||
image: generateImage(CGSize(width: 44.0 * imageRenderScale, height: 44 * imageRenderScale), opaque: false, rotatedContext: { size, context in
|
image: generateImage(CGSize(width: 44.0 * imageRenderScale, height: 44 * imageRenderScale), opaque: false, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.translateBy(x: size.width / 2, y: size.height / 2)
|
context.translateBy(x: size.width / 2, y: size.height / 2)
|
||||||
context.scaleBy(x: 0.4, y: 0.4)
|
context.scaleBy(x: 0.4, y: 0.4)
|
||||||
context.translateBy(x: -size.width / 2, y: -size.height / 2)
|
context.translateBy(x: -size.width / 2, y: -size.height / 2)
|
||||||
|
@ -753,12 +753,18 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
|
|
||||||
public func joinGroupCall(context: AccountContext, peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, initialCall: EngineGroupCallDescription, endCurrentIfAny: Bool) -> JoinGroupCallManagerResult {
|
public func joinGroupCall(context: AccountContext, peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, initialCall: EngineGroupCallDescription, endCurrentIfAny: Bool) -> JoinGroupCallManagerResult {
|
||||||
let begin: () -> Void = { [weak self] in
|
let begin: () -> Void = { [weak self] in
|
||||||
if let requestJoinAsPeerId = requestJoinAsPeerId {
|
if let requestJoinAsPeerId = requestJoinAsPeerId, (initialCall.isStream == nil || initialCall.isStream == false) {
|
||||||
requestJoinAsPeerId({ joinAsPeerId in
|
requestJoinAsPeerId({ joinAsPeerId in
|
||||||
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, invite: invite, joinAsPeerId: joinAsPeerId, initialCall: initialCall).start()
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.startCallDisposable.set(self.startGroupCall(accountContext: context, peerId: peerId, invite: invite, joinAsPeerId: joinAsPeerId, initialCall: initialCall).startStrict())
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, invite: invite, joinAsPeerId: nil, initialCall: initialCall).start()
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.startCallDisposable.set(self.startGroupCall(accountContext: context, peerId: peerId, invite: invite, joinAsPeerId: nil, initialCall: initialCall).startStrict())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,6 +811,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
let accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in
|
let accessEnabledSignal: Signal<Bool, NoError> = Signal { subscriber in
|
||||||
if let isStream = initialCall.isStream, isStream {
|
if let isStream = initialCall.isStream, isStream {
|
||||||
subscriber.putNext(true)
|
subscriber.putNext(true)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1165,37 +1165,37 @@ public class Account {
|
|||||||
let extractedExpr1: [Signal<AccountRunningImportantTasks, NoError>] = [
|
let extractedExpr1: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { inputStates in
|
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { inputStates in
|
||||||
if inputStates {
|
if inputStates {
|
||||||
print("inputStates: true")
|
//print("inputStates: true")
|
||||||
}
|
}
|
||||||
return inputStates ? AccountRunningImportantTasks.other : []
|
return inputStates ? AccountRunningImportantTasks.other : []
|
||||||
},
|
},
|
||||||
self.pendingMessageManager.hasPendingMessages |> map { hasPendingMessages in
|
self.pendingMessageManager.hasPendingMessages |> map { hasPendingMessages in
|
||||||
if !hasPendingMessages.isEmpty {
|
if !hasPendingMessages.isEmpty {
|
||||||
print("hasPendingMessages: true")
|
//print("hasPendingMessages: true")
|
||||||
}
|
}
|
||||||
return !hasPendingMessages.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
|
return !hasPendingMessages.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
|
||||||
},
|
},
|
||||||
(self.pendingStoryManager?.hasPending ?? .single(false)) |> map { hasPending in
|
(self.pendingStoryManager?.hasPending ?? .single(false)) |> map { hasPending in
|
||||||
if hasPending {
|
if hasPending {
|
||||||
print("hasPending: true")
|
//print("hasPending: true")
|
||||||
}
|
}
|
||||||
return hasPending ? AccountRunningImportantTasks.pendingMessages : []
|
return hasPending ? AccountRunningImportantTasks.pendingMessages : []
|
||||||
},
|
},
|
||||||
self.pendingUpdateMessageManager.updatingMessageMedia |> map { updatingMessageMedia in
|
self.pendingUpdateMessageManager.updatingMessageMedia |> map { updatingMessageMedia in
|
||||||
if !updatingMessageMedia.isEmpty {
|
if !updatingMessageMedia.isEmpty {
|
||||||
print("updatingMessageMedia: true")
|
//print("updatingMessageMedia: true")
|
||||||
}
|
}
|
||||||
return !updatingMessageMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
|
return !updatingMessageMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
|
||||||
},
|
},
|
||||||
self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map { uploadingPeerMedia in
|
self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map { uploadingPeerMedia in
|
||||||
if !uploadingPeerMedia.isEmpty {
|
if !uploadingPeerMedia.isEmpty {
|
||||||
print("uploadingPeerMedia: true")
|
//print("uploadingPeerMedia: true")
|
||||||
}
|
}
|
||||||
return !uploadingPeerMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
|
return !uploadingPeerMedia.isEmpty ? AccountRunningImportantTasks.pendingMessages : []
|
||||||
},
|
},
|
||||||
self.accountPresenceManager.isPerformingUpdate() |> map { presenceUpdate in
|
self.accountPresenceManager.isPerformingUpdate() |> map { presenceUpdate in
|
||||||
if presenceUpdate {
|
if presenceUpdate {
|
||||||
print("accountPresenceManager isPerformingUpdate: true")
|
//print("accountPresenceManager isPerformingUpdate: true")
|
||||||
//return []
|
//return []
|
||||||
}
|
}
|
||||||
return presenceUpdate ? AccountRunningImportantTasks.other : []
|
return presenceUpdate ? AccountRunningImportantTasks.other : []
|
||||||
|
@ -1757,7 +1757,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
|> mapToSignal { finalState in
|
|> mapToSignal { finalState in
|
||||||
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
||||||
return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: finalState)
|
return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: resultingState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
||||||
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
||||||
|> map { resultingState, resolveError -> AccountFinalState in
|
|> map { resultingState, resolveError -> AccountFinalState in
|
||||||
|
@ -311,11 +311,12 @@ public final class AccountStateManager {
|
|||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { value -> Signal<Never, NoError> in
|
|> mapToSignal { value -> Signal<Never, NoError> in
|
||||||
if isMaxMessageId {
|
if isMaxMessageId {
|
||||||
return network.request(Api.functions.messages.receivedMessages(maxId: value))
|
return .complete()
|
||||||
|
/*return network.request(Api.functions.messages.receivedMessages(maxId: value))
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
|> `catch` { _ -> Signal<Never, NoError> in
|
|> `catch` { _ -> Signal<Never, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}*/
|
||||||
} else {
|
} else {
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
return .complete()
|
return .complete()
|
||||||
|
@ -284,6 +284,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
private var appliedForwardInfo: (Peer?, String?)?
|
private var appliedForwardInfo: (Peer?, String?)?
|
||||||
|
|
||||||
|
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
||||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||||
|
|
||||||
private var wasPending: Bool = false
|
private var wasPending: Bool = false
|
||||||
@ -450,6 +451,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
self.view.addGestureRecognizer(recognizer)
|
self.view.addGestureRecognizer(recognizer)
|
||||||
|
|
||||||
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
||||||
|
if let item = self.item {
|
||||||
|
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
}
|
||||||
replyRecognizer.shouldBegin = { [weak self] in
|
replyRecognizer.shouldBegin = { [weak self] in
|
||||||
if let strongSelf = self, let item = strongSelf.item {
|
if let strongSelf = self, let item = strongSelf.item {
|
||||||
if strongSelf.selectionNode != nil {
|
if strongSelf.selectionNode != nil {
|
||||||
@ -470,6 +475,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
self.replyRecognizer = replyRecognizer
|
||||||
self.view.addGestureRecognizer(replyRecognizer)
|
self.view.addGestureRecognizer(replyRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,6 +516,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
private var setupTimestamp: Double?
|
private var setupTimestamp: Double?
|
||||||
private func setupNode(item: ChatMessageItem) {
|
private func setupNode(item: ChatMessageItem) {
|
||||||
|
self.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
|
||||||
guard self.animationNode == nil else {
|
guard self.animationNode == nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2419,11 +2428,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
private var playedSwipeToReplyHaptic = false
|
private var playedSwipeToReplyHaptic = false
|
||||||
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
||||||
var offset: CGFloat = 0.0
|
var offset: CGFloat = 0.0
|
||||||
|
var leftOffset: CGFloat = 0.0
|
||||||
var swipeOffset: CGFloat = 45.0
|
var swipeOffset: CGFloat = 45.0
|
||||||
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
||||||
offset = -24.0
|
offset = -24.0
|
||||||
|
leftOffset = -10.0
|
||||||
} else {
|
} else {
|
||||||
offset = 10.0
|
offset = 10.0
|
||||||
|
leftOffset = -10.0
|
||||||
swipeOffset = 60.0
|
swipeOffset = 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2451,7 +2463,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
if translation.x < 0.0 {
|
if translation.x < 0.0 {
|
||||||
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
} else {
|
} else {
|
||||||
translation.x = 0.0
|
if recognizer.allowBothDirections {
|
||||||
|
translation.x = -max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
|
} else {
|
||||||
|
translation.x = 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = self.item, self.swipeToReplyNode == nil {
|
if let item = self.item, self.swipeToReplyNode == nil {
|
||||||
@ -2469,7 +2485,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
if let swipeToReplyNode = self.swipeToReplyNode {
|
if let swipeToReplyNode = self.swipeToReplyNode {
|
||||||
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
if translation.x < 0.0 {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
} else {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
||||||
@ -2488,7 +2510,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
self.swipeToReplyFeedback = nil
|
self.swipeToReplyFeedback = nil
|
||||||
|
|
||||||
let translation = recognizer.translation(in: self.view)
|
let translation = recognizer.translation(in: self.view)
|
||||||
if case .ended = recognizer.state, translation.x < -swipeOffset {
|
|
||||||
|
let gestureRecognized: Bool
|
||||||
|
if recognizer.allowBothDirections {
|
||||||
|
gestureRecognized = abs(translation.x) > swipeOffset
|
||||||
|
} else {
|
||||||
|
gestureRecognized = translation.x < -swipeOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .ended = recognizer.state, gestureRecognized {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
if let currentSwipeAction = currentSwipeAction {
|
if let currentSwipeAction = currentSwipeAction {
|
||||||
switch currentSwipeAction {
|
switch currentSwipeAction {
|
||||||
|
@ -588,6 +588,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
|
|
||||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||||
|
|
||||||
|
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
||||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||||
|
|
||||||
//private let debugNode: ASDisplayNode
|
//private let debugNode: ASDisplayNode
|
||||||
@ -1080,6 +1081,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
self.view.isExclusiveTouch = true
|
self.view.isExclusiveTouch = true
|
||||||
|
|
||||||
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
|
||||||
|
if let item = self.item {
|
||||||
|
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
}
|
||||||
replyRecognizer.shouldBegin = { [weak self] in
|
replyRecognizer.shouldBegin = { [weak self] in
|
||||||
if let strongSelf = self, let item = strongSelf.item {
|
if let strongSelf = self, let item = strongSelf.item {
|
||||||
if strongSelf.selectionNode != nil {
|
if strongSelf.selectionNode != nil {
|
||||||
@ -1111,6 +1116,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
self.replyRecognizer = replyRecognizer
|
||||||
self.view.addGestureRecognizer(replyRecognizer)
|
self.view.addGestureRecognizer(replyRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2671,6 +2677,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
strongSelf.updateAccessibilityData(accessibilityData)
|
strongSelf.updateAccessibilityData(accessibilityData)
|
||||||
strongSelf.disablesComments = disablesComments
|
strongSelf.disablesComments = disablesComments
|
||||||
|
|
||||||
|
strongSelf.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
strongSelf.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
|
||||||
var animation = animation
|
var animation = animation
|
||||||
if strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
if strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
||||||
animation = .System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .easeInOut, interactive: false))
|
animation = .System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .easeInOut, interactive: false))
|
||||||
@ -4389,11 +4398,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
private var playedSwipeToReplyHaptic = false
|
private var playedSwipeToReplyHaptic = false
|
||||||
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
||||||
var offset: CGFloat = 0.0
|
var offset: CGFloat = 0.0
|
||||||
|
var leftOffset: CGFloat = 0.0
|
||||||
var swipeOffset: CGFloat = 45.0
|
var swipeOffset: CGFloat = 45.0
|
||||||
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
||||||
offset = -24.0
|
offset = -24.0
|
||||||
|
leftOffset = -10.0
|
||||||
} else {
|
} else {
|
||||||
offset = 10.0
|
offset = 10.0
|
||||||
|
leftOffset = -10.0
|
||||||
swipeOffset = 60.0
|
swipeOffset = 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4421,7 +4433,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if translation.x < 0.0 {
|
if translation.x < 0.0 {
|
||||||
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
} else {
|
} else {
|
||||||
translation.x = 0.0
|
if recognizer.allowBothDirections {
|
||||||
|
translation.x = -max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
|
} else {
|
||||||
|
translation.x = 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = self.item, self.swipeToReplyNode == nil {
|
if let item = self.item, self.swipeToReplyNode == nil {
|
||||||
@ -4441,8 +4457,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
self.updateAttachedAvatarNodeOffset(offset: translation.x, transition: .immediate)
|
self.updateAttachedAvatarNodeOffset(offset: translation.x, transition: .immediate)
|
||||||
|
|
||||||
if let swipeToReplyNode = self.swipeToReplyNode {
|
if let swipeToReplyNode = self.swipeToReplyNode {
|
||||||
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
if translation.x < 0.0 {
|
||||||
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
} else {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
||||||
@ -4461,7 +4482,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
self.swipeToReplyFeedback = nil
|
self.swipeToReplyFeedback = nil
|
||||||
|
|
||||||
let translation = recognizer.translation(in: self.view)
|
let translation = recognizer.translation(in: self.view)
|
||||||
if case .ended = recognizer.state, translation.x < -swipeOffset {
|
let gestureRecognized: Bool
|
||||||
|
if recognizer.allowBothDirections {
|
||||||
|
gestureRecognized = abs(translation.x) > swipeOffset
|
||||||
|
} else {
|
||||||
|
gestureRecognized = translation.x < -swipeOffset
|
||||||
|
}
|
||||||
|
if case .ended = recognizer.state, gestureRecognized {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
if let currentSwipeAction = currentSwipeAction {
|
if let currentSwipeAction = currentSwipeAction {
|
||||||
switch currentSwipeAction {
|
switch currentSwipeAction {
|
||||||
|
@ -57,7 +57,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
var currentSwipeToReplyTranslation: CGFloat = 0.0
|
var currentSwipeToReplyTranslation: CGFloat = 0.0
|
||||||
|
|
||||||
var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||||
|
|
||||||
|
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
||||||
var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||||
|
|
||||||
override var visibility: ListViewItemNodeVisibility {
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
@ -220,7 +221,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let item = self.item {
|
||||||
|
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
}
|
||||||
|
self.replyRecognizer = replyRecognizer
|
||||||
self.view.addGestureRecognizer(replyRecognizer)
|
self.view.addGestureRecognizer(replyRecognizer)
|
||||||
|
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
|
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
|
||||||
@ -617,6 +625,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
strongSelf.appliedCurrentlyPlaying = isPlaying
|
strongSelf.appliedCurrentlyPlaying = isPlaying
|
||||||
strongSelf.appliedAutomaticDownload = automaticDownload
|
strongSelf.appliedAutomaticDownload = automaticDownload
|
||||||
|
|
||||||
|
strongSelf.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
strongSelf.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
|
||||||
strongSelf.updateAccessibilityData(accessibilityData)
|
strongSelf.updateAccessibilityData(accessibilityData)
|
||||||
|
|
||||||
let videoLayoutData: ChatMessageInstantVideoItemLayoutData
|
let videoLayoutData: ChatMessageInstantVideoItemLayoutData
|
||||||
@ -1006,11 +1017,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
private var playedSwipeToReplyHaptic = false
|
private var playedSwipeToReplyHaptic = false
|
||||||
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
||||||
var offset: CGFloat = 0.0
|
var offset: CGFloat = 0.0
|
||||||
|
var leftOffset: CGFloat = 0.0
|
||||||
var swipeOffset: CGFloat = 45.0
|
var swipeOffset: CGFloat = 45.0
|
||||||
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
||||||
offset = -24.0
|
offset = -24.0
|
||||||
|
leftOffset = -10.0
|
||||||
} else {
|
} else {
|
||||||
offset = 10.0
|
offset = 10.0
|
||||||
|
leftOffset = -10.0
|
||||||
swipeOffset = 60.0
|
swipeOffset = 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1024,8 +1038,26 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
self.item?.controllerInteraction.cancelInteractiveKeyboardGestures()
|
self.item?.controllerInteraction.cancelInteractiveKeyboardGestures()
|
||||||
case .changed:
|
case .changed:
|
||||||
|
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
|
||||||
|
let bandedOffset = offset - bandingStart
|
||||||
|
if offset < bandingStart {
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
let range: CGFloat = 100.0
|
||||||
|
let coefficient: CGFloat = 0.4
|
||||||
|
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
|
||||||
|
}
|
||||||
|
|
||||||
var translation = recognizer.translation(in: self.view)
|
var translation = recognizer.translation(in: self.view)
|
||||||
translation.x = max(-80.0, min(0.0, translation.x))
|
if translation.x < 0.0 {
|
||||||
|
translation.x = max(-80.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
|
} else {
|
||||||
|
if recognizer.allowBothDirections {
|
||||||
|
translation.x = -max(-80.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
|
} else {
|
||||||
|
translation.x = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let item = self.item, self.swipeToReplyNode == nil {
|
if let item = self.item, self.swipeToReplyNode == nil {
|
||||||
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
|
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
|
||||||
@ -1042,7 +1074,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
|
|
||||||
if let swipeToReplyNode = self.swipeToReplyNode {
|
if let swipeToReplyNode = self.swipeToReplyNode {
|
||||||
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
if translation.x < 0.0 {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
} else {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
||||||
@ -1061,7 +1099,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
self.swipeToReplyFeedback = nil
|
self.swipeToReplyFeedback = nil
|
||||||
|
|
||||||
let translation = recognizer.translation(in: self.view)
|
let translation = recognizer.translation(in: self.view)
|
||||||
if case .ended = recognizer.state, translation.x < -swipeOffset {
|
let gestureRecognized: Bool
|
||||||
|
if recognizer.allowBothDirections {
|
||||||
|
gestureRecognized = abs(translation.x) > swipeOffset
|
||||||
|
} else {
|
||||||
|
gestureRecognized = translation.x < -swipeOffset
|
||||||
|
}
|
||||||
|
if case .ended = recognizer.state, gestureRecognized {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
if let currentSwipeAction = currentSwipeAction {
|
if let currentSwipeAction = currentSwipeAction {
|
||||||
switch currentSwipeAction {
|
switch currentSwipeAction {
|
||||||
|
@ -55,6 +55,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
private var currentSwipeToReplyTranslation: CGFloat = 0.0
|
private var currentSwipeToReplyTranslation: CGFloat = 0.0
|
||||||
|
|
||||||
|
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
||||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||||
|
|
||||||
private var appliedForwardInfo: (Peer?, String?)?
|
private var appliedForwardInfo: (Peer?, String?)?
|
||||||
@ -254,12 +255,22 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let item = self.item {
|
||||||
|
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
}
|
||||||
|
self.replyRecognizer = replyRecognizer
|
||||||
self.view.addGestureRecognizer(replyRecognizer)
|
self.view.addGestureRecognizer(replyRecognizer)
|
||||||
|
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
|
override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
|
||||||
super.setupItem(item, synchronousLoad: synchronousLoad)
|
super.setupItem(item, synchronousLoad: synchronousLoad)
|
||||||
|
|
||||||
|
self.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
|
||||||
|
|
||||||
for media in item.message.media {
|
for media in item.message.media {
|
||||||
if let telegramFile = media as? TelegramMediaFile {
|
if let telegramFile = media as? TelegramMediaFile {
|
||||||
if self.telegramFile != telegramFile {
|
if self.telegramFile != telegramFile {
|
||||||
@ -1393,11 +1404,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
private var playedSwipeToReplyHaptic = false
|
private var playedSwipeToReplyHaptic = false
|
||||||
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
||||||
var offset: CGFloat = 0.0
|
var offset: CGFloat = 0.0
|
||||||
|
var leftOffset: CGFloat = 0.0
|
||||||
var swipeOffset: CGFloat = 45.0
|
var swipeOffset: CGFloat = 45.0
|
||||||
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
|
||||||
offset = -24.0
|
offset = -24.0
|
||||||
|
leftOffset = -10.0
|
||||||
} else {
|
} else {
|
||||||
offset = 10.0
|
offset = 10.0
|
||||||
|
leftOffset = -10.0
|
||||||
swipeOffset = 60.0
|
swipeOffset = 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1425,7 +1439,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
if translation.x < 0.0 {
|
if translation.x < 0.0 {
|
||||||
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
} else {
|
} else {
|
||||||
translation.x = 0.0
|
if recognizer.allowBothDirections {
|
||||||
|
translation.x = -max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
|
||||||
|
} else {
|
||||||
|
translation.x = 0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = self.item, self.swipeToReplyNode == nil {
|
if let item = self.item, self.swipeToReplyNode == nil {
|
||||||
@ -1443,7 +1461,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
if let swipeToReplyNode = self.swipeToReplyNode {
|
if let swipeToReplyNode = self.swipeToReplyNode {
|
||||||
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
if translation.x < 0.0 {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
} else {
|
||||||
|
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
|
||||||
|
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
|
||||||
@ -1462,7 +1486,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.swipeToReplyFeedback = nil
|
self.swipeToReplyFeedback = nil
|
||||||
|
|
||||||
let translation = recognizer.translation(in: self.view)
|
let translation = recognizer.translation(in: self.view)
|
||||||
if case .ended = recognizer.state, translation.x < -swipeOffset {
|
let gestureRecognized: Bool
|
||||||
|
if recognizer.allowBothDirections {
|
||||||
|
gestureRecognized = abs(translation.x) > swipeOffset
|
||||||
|
} else {
|
||||||
|
gestureRecognized = translation.x < -swipeOffset
|
||||||
|
}
|
||||||
|
if case .ended = recognizer.state, gestureRecognized {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
if let currentSwipeAction = currentSwipeAction {
|
if let currentSwipeAction = currentSwipeAction {
|
||||||
switch currentSwipeAction {
|
switch currentSwipeAction {
|
||||||
|
@ -4,6 +4,7 @@ import UIKit
|
|||||||
class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer {
|
class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer {
|
||||||
var validatedGesture = false
|
var validatedGesture = false
|
||||||
var firstLocation: CGPoint = CGPoint()
|
var firstLocation: CGPoint = CGPoint()
|
||||||
|
var allowBothDirections: Bool = true
|
||||||
|
|
||||||
var shouldBegin: (() -> Bool)?
|
var shouldBegin: (() -> Bool)?
|
||||||
|
|
||||||
@ -37,17 +38,17 @@ class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer {
|
|||||||
let absTranslationX: CGFloat = abs(translation.x)
|
let absTranslationX: CGFloat = abs(translation.x)
|
||||||
let absTranslationY: CGFloat = abs(translation.y)
|
let absTranslationY: CGFloat = abs(translation.y)
|
||||||
|
|
||||||
if !validatedGesture {
|
if !self.validatedGesture {
|
||||||
if translation.x > 0.0 {
|
if !self.allowBothDirections && translation.x > 0.0 {
|
||||||
self.state = .failed
|
self.state = .failed
|
||||||
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
|
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
|
||||||
self.state = .failed
|
self.state = .failed
|
||||||
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
|
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
|
||||||
validatedGesture = true
|
self.validatedGesture = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if validatedGesture {
|
if self.validatedGesture {
|
||||||
super.touchesMoved(touches, with: event)
|
super.touchesMoved(touches, with: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var storiesExperiment: Bool
|
public var storiesExperiment: Bool
|
||||||
public var storiesJpegExperiment: Bool
|
public var storiesJpegExperiment: Bool
|
||||||
public var crashOnMemoryPressure: Bool
|
public var crashOnMemoryPressure: Bool
|
||||||
|
public var unidirectionalSwipeToReply: Bool
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -83,7 +84,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
logLanguageRecognition: false,
|
logLanguageRecognition: false,
|
||||||
storiesExperiment: false,
|
storiesExperiment: false,
|
||||||
storiesJpegExperiment: false,
|
storiesJpegExperiment: false,
|
||||||
crashOnMemoryPressure: false
|
crashOnMemoryPressure: false,
|
||||||
|
unidirectionalSwipeToReply: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +117,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
logLanguageRecognition: Bool,
|
logLanguageRecognition: Bool,
|
||||||
storiesExperiment: Bool,
|
storiesExperiment: Bool,
|
||||||
storiesJpegExperiment: Bool,
|
storiesJpegExperiment: Bool,
|
||||||
crashOnMemoryPressure: Bool
|
crashOnMemoryPressure: Bool,
|
||||||
|
unidirectionalSwipeToReply: Bool
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -145,6 +148,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.storiesExperiment = storiesExperiment
|
self.storiesExperiment = storiesExperiment
|
||||||
self.storiesJpegExperiment = storiesJpegExperiment
|
self.storiesJpegExperiment = storiesJpegExperiment
|
||||||
self.crashOnMemoryPressure = crashOnMemoryPressure
|
self.crashOnMemoryPressure = crashOnMemoryPressure
|
||||||
|
self.unidirectionalSwipeToReply = unidirectionalSwipeToReply
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -178,6 +182,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.storiesExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesExperiment") ?? false
|
self.storiesExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesExperiment") ?? false
|
||||||
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
|
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
|
||||||
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
|
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
|
||||||
|
self.unidirectionalSwipeToReply = try container.decodeIfPresent(Bool.self, forKey: "unidirectionalSwipeToReply") ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -211,6 +216,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
try container.encode(self.storiesExperiment, forKey: "storiesExperiment")
|
try container.encode(self.storiesExperiment, forKey: "storiesExperiment")
|
||||||
try container.encode(self.storiesJpegExperiment, forKey: "storiesJpegExperiment")
|
try container.encode(self.storiesJpegExperiment, forKey: "storiesJpegExperiment")
|
||||||
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
|
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
|
||||||
|
try container.encode(self.unidirectionalSwipeToReply, forKey: "unidirectionalSwipeToReply")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 8031d485599404c7edf51337ab27ade5aaec9724
|
Subproject commit 272dfe03416f1022dfc5d73875dbe8ace9ec9cfd
|
Loading…
x
Reference in New Issue
Block a user