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
# Conflicts: # submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift # submodules/TgVoipWebrtc/tgcalls
This commit is contained in:
commit
abe8862864
5
.gitignore
vendored
5
.gitignore
vendored
@ -61,7 +61,4 @@ bazel-testlogs
|
||||
bazel-testlogs/*
|
||||
*/*.swp
|
||||
*.swp
|
||||
build-input/data
|
||||
build-input/data/*
|
||||
build-input/gen
|
||||
build-input/gen/*
|
||||
build-input/*
|
||||
|
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -7,7 +7,7 @@
|
||||
url=https://github.com/bazelbuild/rules_apple.git
|
||||
[submodule "build-system/bazel-rules/rules_swift"]
|
||||
path = build-system/bazel-rules/rules_swift
|
||||
url=https://github.com/ali-fareed/rules_swift.git
|
||||
url=https://github.com/bazelbuild/rules_swift.git
|
||||
[submodule "build-system/bazel-rules/apple_support"]
|
||||
path = build-system/bazel-rules/apple_support
|
||||
url = https://github.com/bazelbuild/apple_support.git
|
||||
@ -16,7 +16,7 @@ url=https://github.com/ali-fareed/rules_swift.git
|
||||
url = https://github.com/telegramdesktop/libtgvoip.git
|
||||
[submodule "build-system/tulsi"]
|
||||
path = build-system/tulsi
|
||||
url=https://github.com/ali-fareed/tulsi.git
|
||||
url=https://github.com/bazelbuild/tulsi.git
|
||||
[submodule "submodules/TgVoipWebrtc/tgcalls"]
|
||||
path = submodules/TgVoipWebrtc/tgcalls
|
||||
url=../tgcalls.git
|
||||
|
25
README.md
25
README.md
@ -20,23 +20,7 @@ There are several things we require from **all developers** for the moment.
|
||||
git clone --recursive -j8 https://github.com/TelegramMessenger/Telegram-iOS.git
|
||||
```
|
||||
|
||||
3. Download Bazel 4.0.0
|
||||
|
||||
```
|
||||
mkdir -p $HOME/bazel-dist
|
||||
cd $HOME/bazel-dist
|
||||
curl -O -L https://github.com/bazelbuild/bazel/releases/download/4.0.0/bazel-4.0.0-darwin-x86_64
|
||||
mv bazel-* bazel
|
||||
```
|
||||
|
||||
Verify that it's working
|
||||
|
||||
```
|
||||
chmod +x bazel
|
||||
./bazel --version
|
||||
```
|
||||
|
||||
4. Adjust configuration parameters
|
||||
3. Adjust configuration parameters
|
||||
|
||||
```
|
||||
mkdir -p $HOME/telegram-configuration
|
||||
@ -46,7 +30,7 @@ cp -R build-system/example-configuration/* $HOME/telegram-configuration/
|
||||
- Modify the values in `variables.bzl`
|
||||
- Replace the provisioning profiles in `provisioning` with valid files
|
||||
|
||||
5. (Optional) Create a build cache directory to speed up rebuilds
|
||||
4. (Optional) Create a build cache directory to speed up rebuilds
|
||||
|
||||
```
|
||||
mkdir -p "$HOME/telegram-bazel-cache"
|
||||
@ -56,7 +40,6 @@ mkdir -p "$HOME/telegram-bazel-cache"
|
||||
|
||||
```
|
||||
python3 build-system/Make/Make.py \
|
||||
--bazel="$HOME/bazel-dist/bazel" \
|
||||
--cacheDir="$HOME/telegram-bazel-cache" \
|
||||
build \
|
||||
--configurationPath="$HOME/telegram-configuration" \
|
||||
@ -68,7 +51,6 @@ python3 build-system/Make/Make.py \
|
||||
|
||||
```
|
||||
python3 build-system/Make/Make.py \
|
||||
--bazel="$HOME/bazel-dist/bazel" \
|
||||
--cacheDir="$HOME/telegram-bazel-cache" \
|
||||
generateProject \
|
||||
--configurationPath="$HOME/telegram-configuration" \
|
||||
@ -78,7 +60,6 @@ python3 build-system/Make/Make.py \
|
||||
It is possible to generate a project that does not require any codesigning certificates to be installed: add `--disableProvisioningProfiles` flag:
|
||||
```
|
||||
python3 build-system/Make/Make.py \
|
||||
--bazel="$HOME/bazel-dist/bazel" \
|
||||
--cacheDir="$HOME/telegram-bazel-cache" \
|
||||
generateProject \
|
||||
--configurationPath="$HOME/telegram-configuration" \
|
||||
@ -100,6 +81,8 @@ python3 build-system/Make/Make.py build --help
|
||||
python3 build-system/Make/Make.py generateProject --help
|
||||
```
|
||||
|
||||
Bazel is automatically downloaded when running Make.py for the first time. If you wish to use your own build of Bazel, pass `--bazel=path-to-bazel`. If your Bazel version differs from that in `versions.json`, you may use `--overrideBazelVersion` to skip the version check.
|
||||
|
||||
Each release is built using specific Xcode and Bazel versions (see `versions.json`). The helper script checks the versions of installed software and reports an error if they don't match the ones specified in `versions.json`. There are flags that allow to bypass these checks:
|
||||
|
||||
```
|
||||
|
@ -7239,3 +7239,6 @@ Sorry for the inconvenience.";
|
||||
"Share.ShareMessage" = "Share Message";
|
||||
|
||||
"Conversation.UserSendMessage" = "SEND MESSAGE";
|
||||
|
||||
"Conversation.CopyProtectionForwardingDisabledBot" = "Forwards from this bot are restricted";
|
||||
"Conversation.CopyProtectionSavingDisabledBot" = "Saving from this bot is restricted";
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
@interface TGBridgeUserCache : NSObject
|
||||
|
||||
- (TGBridgeUser *)userWithId:(int32_t)userId;
|
||||
- (NSDictionary *)usersWithIndexSet:(NSIndexSet *)indexSet;
|
||||
- (TGBridgeUser *)userWithId:(int64_t)userId;
|
||||
- (NSDictionary *)usersWithIds:(NSArray<NSNumber *> *)indexSet;
|
||||
- (void)storeUser:(TGBridgeUser *)user;
|
||||
- (void)storeUsers:(NSArray *)users;
|
||||
- (NSArray *)applyUserChanges:(NSArray *)userChanges;
|
||||
|
@ -33,7 +33,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (TGBridgeUser *)userWithId:(int32_t)userId
|
||||
- (TGBridgeUser *)userWithId:(int64_t)userId
|
||||
{
|
||||
__block TGBridgeUser *user = nil;
|
||||
|
||||
@ -44,26 +44,28 @@
|
||||
return user;
|
||||
}
|
||||
|
||||
- (NSDictionary *)usersWithIndexSet:(NSIndexSet *)indexSet
|
||||
- (NSDictionary *)usersWithIds:(NSArray<NSNumber *> *)indexSet
|
||||
{
|
||||
NSMutableDictionary *users = [[NSMutableDictionary alloc] init];
|
||||
NSMutableIndexSet *neededUsers = [indexSet mutableCopy];
|
||||
NSMutableSet<NSNumber *> *neededUsers = [indexSet mutableCopy];
|
||||
|
||||
NSMutableIndexSet *foundUsers = [[NSMutableIndexSet alloc] init];
|
||||
NSMutableSet<NSNumber *> *foundUsers = [[NSMutableSet alloc] init];
|
||||
|
||||
OSSpinLockLock(&_userByUidLock);
|
||||
[neededUsers enumerateIndexesUsingBlock:^(NSUInteger index, BOOL * _Nonnull stop)
|
||||
{
|
||||
for (NSNumber *nId in neededUsers) {
|
||||
int64_t index = [nId longLongValue];
|
||||
TGBridgeUser *user = _userByUid[@(index)];
|
||||
if (user != nil)
|
||||
{
|
||||
users[@(index)] = user;
|
||||
[foundUsers addIndex:index];
|
||||
[foundUsers addObject:@(index)];
|
||||
}
|
||||
}];
|
||||
}
|
||||
OSSpinLockUnlock(&_userByUidLock);
|
||||
|
||||
[neededUsers removeIndexes:foundUsers];
|
||||
for (NSNumber *nId in foundUsers) {
|
||||
[neededUsers removeObject:nId];
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ NSString *const TGNeoChatRowIdentifier = @"TGNeoChatRow";
|
||||
_currentChat = chat;
|
||||
|
||||
NSDictionary *oldUsers = _currentUsers;
|
||||
_currentUsers = [[TGBridgeUserCache instance] usersWithIndexSet:[chat involvedUserIds]];
|
||||
_currentUsers = [[TGBridgeUserCache instance] usersWithIds:[chat involvedUserIds]];
|
||||
|
||||
bool shouldUpdate = [self shouldUpdateContentFrom:oldChat oldUsers:oldUsers to:_currentChat newUsers:_currentUsers];
|
||||
if (shouldUpdate)
|
||||
|
@ -703,9 +703,10 @@ const NSInteger TGNeoConversationControllerInitialRenderCount = 4;
|
||||
NSMutableArray *botInfoSignals = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *botUsers = [[NSMutableArray alloc] init];
|
||||
NSMutableArray *initialStates = [[NSMutableArray alloc] init];
|
||||
[_chatModel.participantsUserIds enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop)
|
||||
{
|
||||
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:(int32_t)idx];
|
||||
|
||||
for (NSNumber *nId in _chatModel.participantsUserIds) {
|
||||
int64_t idx = [nId longLongValue];
|
||||
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:idx];
|
||||
if ([user isBot])
|
||||
{
|
||||
[botUsers addObject:user];
|
||||
@ -718,7 +719,7 @@ const NSInteger TGNeoConversationControllerInitialRenderCount = 4;
|
||||
return botInfo.commandList;
|
||||
}]];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return [[SSignal combineSignals:botInfoSignals withInitialStates:initialStates] map:^id(NSArray *commandLists)
|
||||
{
|
||||
@ -767,15 +768,14 @@ const NSInteger TGNeoConversationControllerInitialRenderCount = 4;
|
||||
|
||||
if ([self peerIsAnyGroup])
|
||||
{
|
||||
[_chatModel.participantsUserIds enumerateIndexesUsingBlock:^(NSUInteger userId, BOOL * _Nonnull stop)
|
||||
{
|
||||
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:(int32_t)userId];
|
||||
if ([user isBot])
|
||||
{
|
||||
for (NSNumber *nId in _chatModel.participantsUserIds) {
|
||||
int64_t userId = [nId longLongValue];
|
||||
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:userId];
|
||||
if ([user isBot]) {
|
||||
_hasBots = true;
|
||||
*stop = true;
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -144,7 +144,7 @@ NSString *const TGNeoMessageAudioAnimatedIcon = @"animatedIcon";
|
||||
}
|
||||
|
||||
NSMutableDictionary *users = [NSMutableDictionary dictionaryWithDictionary:additionalPeers];
|
||||
[users addEntriesFromDictionary:[[TGBridgeUserCache instance] usersWithIndexSet:[message involvedUserIds]]];
|
||||
[users addEntriesFromDictionary:[[TGBridgeUserCache instance] usersWithIds:[message involvedUserIds]]];
|
||||
|
||||
return [[viewModelClass alloc] initWithMessage:message type:type users:users context:context];
|
||||
}
|
||||
|
@ -37,8 +37,8 @@
|
||||
@property (nonatomic) int32_t participantsCount;
|
||||
@property (nonatomic, strong) NSArray *participants;
|
||||
|
||||
- (NSIndexSet *)involvedUserIds;
|
||||
- (NSIndexSet *)participantsUserIds;
|
||||
- (NSArray<NSNumber *> *)involvedUserIds;
|
||||
- (NSArray<NSNumber *> *)participantsUserIds;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -94,13 +94,13 @@ NSString *const TGBridgeChatsArrayKey = @"chats";
|
||||
[aCoder encodeObject:self.participants forKey:TGBridgeChatGroupParticipantsKey];
|
||||
}
|
||||
|
||||
- (NSIndexSet *)involvedUserIds
|
||||
- (NSArray<NSNumber *> *)involvedUserIds
|
||||
{
|
||||
NSMutableIndexSet *userIds = [[NSMutableIndexSet alloc] init];
|
||||
NSMutableSet<NSNumber *> *userIds = [[NSMutableSet alloc] init];
|
||||
if (!self.isGroup && !self.isChannel && self.identifier != 0)
|
||||
[userIds addIndex:(int32_t)self.identifier];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:self.identifier]];
|
||||
if ((!self.isChannel || self.isChannelGroup) && self.fromUid != self.identifier && self.fromUid != 0 && !TGPeerIdIsChannel(self.fromUid) && self.fromUid > 0)
|
||||
[userIds addIndex:(int32_t)self.fromUid];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:self.fromUid]];
|
||||
|
||||
for (TGBridgeMediaAttachment *attachment in self.media)
|
||||
{
|
||||
@ -108,21 +108,30 @@ NSString *const TGBridgeChatsArrayKey = @"chats";
|
||||
{
|
||||
TGBridgeActionMediaAttachment *actionAttachment = (TGBridgeActionMediaAttachment *)attachment;
|
||||
if (actionAttachment.actionData[@"uid"] != nil)
|
||||
[userIds addIndex:[actionAttachment.actionData[@"uid"] integerValue]];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:[actionAttachment.actionData[@"uid"] longLongValue]]];
|
||||
}
|
||||
}
|
||||
|
||||
return userIds;
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
for (NSNumber *object in userIds) {
|
||||
[result addObject:object];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)participantsUserIds
|
||||
- (NSArray<NSNumber *> *)participantsUserIds
|
||||
{
|
||||
NSMutableIndexSet *userIds = [[NSMutableIndexSet alloc] init];
|
||||
NSMutableSet<NSNumber *> *userIds = [[NSMutableSet alloc] init];
|
||||
|
||||
for (NSNumber *uid in self.participants)
|
||||
[userIds addIndex:uid.unsignedIntegerValue];
|
||||
for (NSNumber *uid in self.participants) {
|
||||
[userIds addObject:[NSNumber numberWithLongLong:uid.longLongValue]];
|
||||
}
|
||||
|
||||
return userIds;
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
for (NSNumber *object in userIds) {
|
||||
[result addObject:object];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
|
@ -50,7 +50,7 @@ typedef NS_ENUM(NSUInteger, TGBridgeMessageDeliveryState) {
|
||||
@property (nonatomic, strong) NSArray *media;
|
||||
@property (nonatomic) bool forceReply;
|
||||
|
||||
- (NSIndexSet *)involvedUserIds;
|
||||
- (NSArray<NSNumber *> *)involvedUserIds;
|
||||
- (NSArray *)textCheckingResults;
|
||||
|
||||
+ (instancetype)temporaryNewMessageForText:(NSString *)text userId:(int32_t)userId;
|
||||
|
@ -60,11 +60,11 @@ NSString *const TGBridgeMessagesArrayKey = @"messages";
|
||||
[aCoder encodeBool:self.forceReply forKey:TGBridgeMessageForceReplyKey];
|
||||
}
|
||||
|
||||
- (NSIndexSet *)involvedUserIds
|
||||
- (NSArray<NSNumber *> *)involvedUserIds
|
||||
{
|
||||
NSMutableIndexSet *userIds = [[NSMutableIndexSet alloc] init];
|
||||
NSMutableSet<NSNumber *> *userIds = [[NSMutableSet alloc] init];
|
||||
if (!TGPeerIdIsChannel(self.fromUid))
|
||||
[userIds addIndex:(int32_t)self.fromUid];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:self.fromUid]];
|
||||
|
||||
for (TGBridgeMediaAttachment *attachment in self.media)
|
||||
{
|
||||
@ -72,29 +72,33 @@ NSString *const TGBridgeMessagesArrayKey = @"messages";
|
||||
{
|
||||
TGBridgeContactMediaAttachment *contactAttachment = (TGBridgeContactMediaAttachment *)attachment;
|
||||
if (contactAttachment.uid != 0)
|
||||
[userIds addIndex:contactAttachment.uid];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:contactAttachment.uid]];
|
||||
}
|
||||
else if ([attachment isKindOfClass:[TGBridgeForwardedMessageMediaAttachment class]])
|
||||
{
|
||||
TGBridgeForwardedMessageMediaAttachment *forwardAttachment = (TGBridgeForwardedMessageMediaAttachment *)attachment;
|
||||
if (forwardAttachment.peerId != 0 && !TGPeerIdIsChannel(forwardAttachment.peerId))
|
||||
[userIds addIndex:(int32_t)forwardAttachment.peerId];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:forwardAttachment.peerId]];
|
||||
}
|
||||
else if ([attachment isKindOfClass:[TGBridgeReplyMessageMediaAttachment class]])
|
||||
{
|
||||
TGBridgeReplyMessageMediaAttachment *replyAttachment = (TGBridgeReplyMessageMediaAttachment *)attachment;
|
||||
if (replyAttachment.message != nil && !TGPeerIdIsChannel(replyAttachment.message.fromUid))
|
||||
[userIds addIndex:(int32_t)replyAttachment.message.fromUid];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:replyAttachment.message.fromUid]];
|
||||
}
|
||||
else if ([attachment isKindOfClass:[TGBridgeActionMediaAttachment class]])
|
||||
{
|
||||
TGBridgeActionMediaAttachment *actionAttachment = (TGBridgeActionMediaAttachment *)attachment;
|
||||
if (actionAttachment.actionData[@"uid"] != nil)
|
||||
[userIds addIndex:(int32_t)[actionAttachment.actionData[@"uid"] intValue]];
|
||||
[userIds addObject:[NSNumber numberWithLongLong:[actionAttachment.actionData[@"uid"] intValue]]];
|
||||
}
|
||||
}
|
||||
|
||||
return userIds;
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
for (NSNumber *object in userIds) {
|
||||
[result addObject:object];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray *)textCheckingResults
|
||||
|
32
build-system/Make/BazelLocation.py
Normal file
32
build-system/Make/BazelLocation.py
Normal file
@ -0,0 +1,32 @@
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
|
||||
from BuildEnvironment import is_apple_silicon, resolve_executable, call_executable, BuildEnvironmentVersions
|
||||
|
||||
def locate_bazel(base_path):
|
||||
versions = BuildEnvironmentVersions(base_path=os.getcwd())
|
||||
if is_apple_silicon():
|
||||
arch = 'darwin-arm64'
|
||||
else:
|
||||
arch = 'x86_64'
|
||||
bazel_name = 'bazel-{version}-{arch}'.format(version=versions.bazel_version, arch=arch)
|
||||
bazel_path = '{}/build-input/{}'.format(base_path, bazel_name)
|
||||
|
||||
if not os.path.isfile(bazel_path):
|
||||
call_executable([
|
||||
'curl',
|
||||
'-L',
|
||||
'https://github.com/bazelbuild/bazel/releases/download/{version}/{name}'.format(
|
||||
version=versions.bazel_version,
|
||||
name=bazel_name
|
||||
),
|
||||
'--output',
|
||||
bazel_path
|
||||
])
|
||||
|
||||
if not os.access(bazel_path, os.X_OK):
|
||||
st = os.stat(bazel_path)
|
||||
os.chmod(bazel_path, st.st_mode | stat.S_IEXEC)
|
||||
|
||||
return bazel_path
|
@ -93,18 +93,12 @@ def get_xcode_version():
|
||||
exit(1)
|
||||
|
||||
|
||||
class BuildEnvironment:
|
||||
class BuildEnvironmentVersions:
|
||||
def __init__(
|
||||
self,
|
||||
base_path,
|
||||
bazel_path,
|
||||
override_bazel_version,
|
||||
override_xcode_version
|
||||
base_path
|
||||
):
|
||||
self.base_path = os.path.expanduser(base_path)
|
||||
self.bazel_path = os.path.expanduser(bazel_path)
|
||||
|
||||
configuration_path = os.path.join(self.base_path, 'versions.json')
|
||||
configuration_path = os.path.join(base_path, 'versions.json')
|
||||
with open(configuration_path) as file:
|
||||
configuration_dict = json.load(file)
|
||||
if configuration_dict['app'] is None:
|
||||
@ -120,24 +114,41 @@ class BuildEnvironment:
|
||||
else:
|
||||
self.xcode_version = configuration_dict['xcode']
|
||||
|
||||
class BuildEnvironment:
|
||||
def __init__(
|
||||
self,
|
||||
base_path,
|
||||
bazel_path,
|
||||
override_bazel_version,
|
||||
override_xcode_version
|
||||
):
|
||||
self.base_path = os.path.expanduser(base_path)
|
||||
self.bazel_path = os.path.expanduser(bazel_path)
|
||||
|
||||
versions = BuildEnvironmentVersions(base_path=self.base_path)
|
||||
|
||||
actual_bazel_version = get_bazel_version(self.bazel_path)
|
||||
if actual_bazel_version != self.bazel_version:
|
||||
if actual_bazel_version != versions.bazel_version:
|
||||
if override_bazel_version:
|
||||
print('Overriding the required bazel version {} with {} as reported by {}'.format(
|
||||
self.bazel_version, actual_bazel_version, self.bazel_path))
|
||||
versions.bazel_version, actual_bazel_version, self.bazel_path))
|
||||
self.bazel_version = actual_bazel_version
|
||||
else:
|
||||
print('Required bazel version is "{}", but "{}"" is reported by {}'.format(
|
||||
self.bazel_version, actual_bazel_version, self.bazel_path))
|
||||
versions.bazel_version, actual_bazel_version, self.bazel_path))
|
||||
exit(1)
|
||||
|
||||
actual_xcode_version = get_xcode_version()
|
||||
if actual_xcode_version != self.xcode_version:
|
||||
if actual_xcode_version != versions.xcode_version:
|
||||
if override_xcode_version:
|
||||
print('Overriding the required Xcode version {} with {} as reported by \'xcode-select -p\''.format(
|
||||
self.xcode_version, actual_xcode_version, self.bazel_path))
|
||||
self.xcode_version = actual_xcode_version
|
||||
versions.xcode_version, actual_xcode_version, self.bazel_path))
|
||||
versions.xcode_version = actual_xcode_version
|
||||
else:
|
||||
print('Required Xcode version is {}, but {} is reported by \'xcode-select -p\''.format(
|
||||
self.xcode_version, actual_xcode_version, self.bazel_path))
|
||||
versions.xcode_version, actual_xcode_version, self.bazel_path))
|
||||
exit(1)
|
||||
|
||||
self.app_version = versions.app_version
|
||||
self.xcode_version = versions.xcode_version
|
||||
self.bazel_version = versions.bazel_version
|
||||
|
@ -7,15 +7,15 @@ import sys
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
from BuildEnvironment import is_apple_silicon, resolve_executable, call_executable, BuildEnvironment
|
||||
from BuildEnvironment import resolve_executable, call_executable, BuildEnvironment
|
||||
from ProjectGeneration import generate
|
||||
|
||||
from BazelLocation import locate_bazel
|
||||
|
||||
class BazelCommandLine:
|
||||
def __init__(self, bazel_path, override_bazel_version, override_xcode_version, bazel_user_root):
|
||||
def __init__(self, bazel, override_bazel_version, override_xcode_version, bazel_user_root):
|
||||
self.build_environment = BuildEnvironment(
|
||||
base_path=os.getcwd(),
|
||||
bazel_path=bazel_path,
|
||||
bazel_path=bazel,
|
||||
override_bazel_version=override_bazel_version,
|
||||
override_xcode_version=override_xcode_version
|
||||
)
|
||||
@ -27,6 +27,9 @@ class BazelCommandLine:
|
||||
self.configuration_args = None
|
||||
self.configuration_path = None
|
||||
self.split_submodules = False
|
||||
self.custom_target = None
|
||||
self.continue_on_error = False
|
||||
self.enable_sandbox = False
|
||||
|
||||
self.common_args = [
|
||||
# https://docs.bazel.build/versions/master/command-line-reference.html
|
||||
@ -314,9 +317,9 @@ class BazelCommandLine:
|
||||
call_executable(combined_arguments)
|
||||
|
||||
|
||||
def clean(arguments):
|
||||
def clean(bazel, arguments):
|
||||
bazel_command_line = BazelCommandLine(
|
||||
bazel_path=arguments.bazel,
|
||||
bazel=bazel,
|
||||
override_bazel_version=arguments.overrideBazelVersion,
|
||||
override_xcode_version=arguments.overrideXcodeVersion,
|
||||
bazel_user_root=arguments.bazelUserRoot
|
||||
@ -355,9 +358,9 @@ def resolve_configuration(bazel_command_line: BazelCommandLine, arguments):
|
||||
raise Exception('Neither configurationPath nor configurationGenerator are set')
|
||||
|
||||
|
||||
def generate_project(arguments):
|
||||
def generate_project(bazel, arguments):
|
||||
bazel_command_line = BazelCommandLine(
|
||||
bazel_path=arguments.bazel,
|
||||
bazel=bazel,
|
||||
override_bazel_version=arguments.overrideBazelVersion,
|
||||
override_xcode_version=arguments.overrideXcodeVersion,
|
||||
bazel_user_root=arguments.bazelUserRoot
|
||||
@ -401,9 +404,9 @@ def generate_project(arguments):
|
||||
)
|
||||
|
||||
|
||||
def build(arguments):
|
||||
def build(bazel, arguments):
|
||||
bazel_command_line = BazelCommandLine(
|
||||
bazel_path=arguments.bazel,
|
||||
bazel=bazel,
|
||||
override_bazel_version=arguments.overrideBazelVersion,
|
||||
override_xcode_version=arguments.overrideXcodeVersion,
|
||||
bazel_user_root=arguments.bazelUserRoot
|
||||
@ -463,7 +466,7 @@ if __name__ == '__main__':
|
||||
|
||||
parser.add_argument(
|
||||
'--bazel',
|
||||
required=True,
|
||||
required=False,
|
||||
help='Use custom bazel binary',
|
||||
metavar='path'
|
||||
)
|
||||
@ -596,7 +599,8 @@ if __name__ == '__main__':
|
||||
'--disableParallelSwiftmoduleGeneration',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Generate .swiftmodule files in parallel to building modules, can speed up compilation on multi-core systems.'
|
||||
help='Generate .swiftmodule files in parallel to building modules, can speed up compilation on multi-core '
|
||||
'systems. '
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--target',
|
||||
@ -629,13 +633,19 @@ if __name__ == '__main__':
|
||||
if args.commandName is None:
|
||||
exit(0)
|
||||
|
||||
bazel_path = None
|
||||
if args.bazel is None:
|
||||
bazel_path = locate_bazel(base_path=os.getcwd())
|
||||
else:
|
||||
bazel_path = args.bazel
|
||||
|
||||
try:
|
||||
if args.commandName == 'clean':
|
||||
clean(arguments=args)
|
||||
clean(bazel=bazel_path, arguments=args)
|
||||
elif args.commandName == 'generateProject':
|
||||
generate_project(arguments=args)
|
||||
generate_project(bazel=bazel_path, arguments=args)
|
||||
elif args.commandName == 'build':
|
||||
build(arguments=args)
|
||||
build(bazel=bazel_path, arguments=args)
|
||||
else:
|
||||
raise Exception('Unknown command')
|
||||
except KeyboardInterrupt:
|
||||
|
@ -5,7 +5,7 @@ set -e
|
||||
BUILD_TELEGRAM_VERSION="1"
|
||||
|
||||
MACOS_VERSION="11"
|
||||
XCODE_VERSION="13.1"
|
||||
XCODE_VERSION="13.2.1"
|
||||
GUEST_SHELL="bash"
|
||||
|
||||
VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)"
|
||||
|
@ -293,10 +293,11 @@ public final class NavigateToChatControllerParams {
|
||||
public let options: NavigationAnimationOptions
|
||||
public let parentGroupId: PeerGroupId?
|
||||
public let chatListFilter: Int32?
|
||||
public let chatNavigationStack: [PeerId]
|
||||
public let changeColors: Bool
|
||||
public let completion: (ChatController) -> Void
|
||||
|
||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, changeColors: Bool = false, completion: @escaping (ChatController) -> Void = { _ in }) {
|
||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, completion: @escaping (ChatController) -> Void = { _ in }) {
|
||||
self.navigationController = navigationController
|
||||
self.chatController = chatController
|
||||
self.chatLocationContextHolder = chatLocationContextHolder
|
||||
@ -318,6 +319,7 @@ public final class NavigateToChatControllerParams {
|
||||
self.options = options
|
||||
self.parentGroupId = parentGroupId
|
||||
self.chatListFilter = chatListFilter
|
||||
self.chatNavigationStack = chatNavigationStack
|
||||
self.changeColors = changeColors
|
||||
self.completion = completion
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
|
||||
}
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
break
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
|
@ -595,6 +595,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -274,7 +274,7 @@ public final class ChatInterfaceState: Codable, Equatable {
|
||||
public let inputLanguage: String?
|
||||
|
||||
public var synchronizeableInputState: SynchronizeableChatInputState? {
|
||||
if self.composeInputState.inputText.length == 0 {
|
||||
if self.composeInputState.inputText.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && self.replyMessageId == nil {
|
||||
return nil
|
||||
} else {
|
||||
return SynchronizeableChatInputState(replyToMessageId: self.replyMessageId, text: self.composeInputState.inputText.string, entities: generateChatInputTextEntities(self.composeInputState.inputText), timestamp: self.timestamp, textSelection: self.composeInputState.selectionRange)
|
||||
|
@ -540,7 +540,17 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
})
|
||||
|
||||
if self.controlsHistoryPreload {
|
||||
self.context.account.viewTracker.chatListPreloadItems.set(itemNode.listNode.preloadItems.get())
|
||||
self.context.account.viewTracker.chatListPreloadItems.set(combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.hasOngoingCall.get(),
|
||||
itemNode.listNode.preloadItems.get()
|
||||
)
|
||||
|> map { hasOngoingCall, preloadItems -> [ChatHistoryPreloadItem] in
|
||||
if hasOngoingCall {
|
||||
return []
|
||||
} else {
|
||||
return preloadItems
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,6 +332,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -343,6 +343,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -294,6 +294,7 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -618,19 +618,30 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { messages in
|
||||
if let strongSelf = self, !messages.isEmpty {
|
||||
var isChannel = false
|
||||
enum PeerType {
|
||||
case group
|
||||
case channel
|
||||
case bot
|
||||
}
|
||||
var type: PeerType = .group
|
||||
for message in messages {
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
||||
isChannel = true
|
||||
if let user = message.author?._asPeer() as? TelegramUser, user.botInfo != nil {
|
||||
type = .bot
|
||||
break
|
||||
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
||||
type = .channel
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let text: String
|
||||
if save {
|
||||
text = isChannel ? strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledChannel : strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledGroup
|
||||
} else {
|
||||
text = isChannel ? strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledChannel : strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledGroup
|
||||
switch type {
|
||||
case .group:
|
||||
text = save ? strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledGroup : strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledGroup
|
||||
case .channel:
|
||||
text = save ? strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledChannel : strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledChannel
|
||||
case .bot:
|
||||
text = save ? strongSelf.presentationData.strings.Conversation_CopyProtectionSavingDisabledBot : strongSelf.presentationData.strings.Conversation_CopyProtectionForwardingDisabledBot
|
||||
}
|
||||
|
||||
strongSelf.copyProtectionTooltipController?.dismiss()
|
||||
|
@ -1146,7 +1146,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
break inner
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
|
||||
if file.isVideo, !file.isInstantVideo && !file.isVideoSticker, let _ = file.dimensions {
|
||||
let fitSize = contentImageSize
|
||||
contentImageSpecs.append((message, .file(file), fitSize))
|
||||
}
|
||||
|
@ -179,13 +179,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
||||
|
||||
if previousComponent != component {
|
||||
componentAlpha = animationFraction
|
||||
componentVerticalOffset = (1.0 - animationFraction) * 8.0
|
||||
componentVerticalOffset = -(1.0 - animationFraction) * 8.0
|
||||
if previousComponent.string < component.string {
|
||||
componentVerticalOffset = -componentVerticalOffset
|
||||
}
|
||||
|
||||
let previousComponentAlpha = 1.0 - componentAlpha
|
||||
var previousComponentVerticalOffset = -animationFraction * 8.0
|
||||
var previousComponentVerticalOffset = animationFraction * 8.0
|
||||
if previousComponent.string < component.string {
|
||||
previousComponentVerticalOffset = -previousComponentVerticalOffset
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public final class ReactionImageNode: ASDisplayNode {
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
public init(context: AccountContext, availableReactions: AvailableReactions?, reaction: String) {
|
||||
public init(context: AccountContext, availableReactions: AvailableReactions?, reaction: String, displayPixelSize: CGSize) {
|
||||
self.iconNode = ASImageNode()
|
||||
|
||||
var file: TelegramMediaFile?
|
||||
@ -80,8 +80,8 @@ public final class ReactionImageNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if let animationFile = animationFile {
|
||||
self.size = animationFile.dimensions?.cgSize ?? CGSize(width: 32.0, height: 32.0)
|
||||
var displaySize = self.size.aspectFitted(CGSize(width: 20.0, height: 20.0))
|
||||
self.size = animationFile.dimensions?.cgSize ?? displayPixelSize
|
||||
var displaySize = self.size.aspectFitted(displayPixelSize)
|
||||
displaySize.width = floor(displaySize.width * 2.0)
|
||||
displaySize.height = floor(displaySize.height * 2.0)
|
||||
self.isAnimation = true
|
||||
@ -101,7 +101,7 @@ public final class ReactionImageNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
} else if let file = file {
|
||||
self.size = file.dimensions?.cgSize ?? CGSize(width: 32.0, height: 32.0)
|
||||
self.size = file.dimensions?.cgSize ?? displayPixelSize
|
||||
self.isAnimation = false
|
||||
|
||||
super.init()
|
||||
@ -119,7 +119,7 @@ public final class ReactionImageNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.size = CGSize(width: 32.0, height: 32.0)
|
||||
self.size = displayPixelSize
|
||||
self.isAnimation = false
|
||||
super.init()
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
self.titleLabelNode.isUserInteractionEnabled = false
|
||||
|
||||
if let reaction = reaction {
|
||||
self.reactionIconNode = ReactionImageNode(context: context, availableReactions: availableReactions, reaction: reaction)
|
||||
self.reactionIconNode = ReactionImageNode(context: context, availableReactions: availableReactions, reaction: reaction, displayPixelSize: CGSize(width: 30.0 * UIScreenScale, height: 30.0 * UIScreenScale))
|
||||
self.reactionIconNode?.isUserInteractionEnabled = false
|
||||
self.iconNode = nil
|
||||
} else {
|
||||
@ -354,7 +354,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
let reaction: String? = item.reaction
|
||||
if let reaction = reaction {
|
||||
if self.reactionIconNode == nil {
|
||||
let reactionIconNode = ReactionImageNode(context: self.context, availableReactions: self.availableReactions, reaction: reaction)
|
||||
let reactionIconNode = ReactionImageNode(context: self.context, availableReactions: self.availableReactions, reaction: reaction, displayPixelSize: CGSize(width: 30.0 * UIScreenScale, height: 30.0 * UIScreenScale))
|
||||
self.reactionIconNode = reactionIconNode
|
||||
self.addSubnode(reactionIconNode)
|
||||
}
|
||||
|
@ -203,6 +203,7 @@ class CreatePollOptionActionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -416,6 +416,7 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -1564,6 +1564,6 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func scrollToTop() {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(-50.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
@ -210,6 +210,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
private var originalProjectedContentViewFrame: (CGRect, CGRect)?
|
||||
private var contentAreaInScreenSpace: CGRect?
|
||||
private var customPosition: CGPoint?
|
||||
private let contentContainerNode: ContextContentContainerNode
|
||||
private var actionsContainerNode: ContextActionsContainerNode
|
||||
private var reactionContextNode: ReactionContextNode?
|
||||
@ -546,7 +547,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
let referenceNode = transitionInfo.referenceNode
|
||||
self.contentContainerNode.contentNode = .reference(node: referenceNode)
|
||||
self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
|
||||
let projectedFrame = convertFrame(referenceNode.view.bounds, from: referenceNode.view, to: self.view)
|
||||
self.customPosition = transitionInfo.customPosition
|
||||
var projectedFrame = convertFrame(referenceNode.view.bounds, from: referenceNode.view, to: self.view)
|
||||
projectedFrame.origin.x += transitionInfo.insets.left
|
||||
projectedFrame.size.width -= transitionInfo.insets.left + transitionInfo.insets.right
|
||||
projectedFrame.origin.y += transitionInfo.insets.top
|
||||
projectedFrame.size.width -= transitionInfo.insets.top + transitionInfo.insets.bottom
|
||||
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
|
||||
}
|
||||
case let .extracted(source):
|
||||
@ -1461,7 +1467,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
|
||||
let actionsSideInset: CGFloat = layout.safeInsets.left + 12.0
|
||||
var contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
|
||||
|
||||
if let _ = self.reactionContextNode {
|
||||
@ -1539,6 +1545,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
contentHeight -= offsetDelta
|
||||
}
|
||||
|
||||
if let customPosition = self.customPosition {
|
||||
originalActionsFrame.origin.x = floor(originalContentFrame.center.x - originalActionsFrame.width / 2.0) + customPosition.x
|
||||
originalActionsFrame.origin.y = floor(originalContentFrame.center.y - originalActionsFrame.height / 2.0) + customPosition.y
|
||||
}
|
||||
|
||||
let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||
if self.scrollNode.view.contentSize != scrollContentSize {
|
||||
self.scrollNode.view.contentSize = scrollContentSize
|
||||
@ -2018,10 +2029,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
public final class ContextControllerReferenceViewInfo {
|
||||
public let referenceNode: ContextReferenceContentNode
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
public let insets: UIEdgeInsets
|
||||
public let customPosition: CGPoint?
|
||||
|
||||
public init(referenceNode: ContextReferenceContentNode, contentAreaInScreenSpace: CGRect) {
|
||||
public init(referenceNode: ContextReferenceContentNode, contentAreaInScreenSpace: CGRect, insets: UIEdgeInsets = UIEdgeInsets(), customPosition: CGPoint? = nil) {
|
||||
self.referenceNode = referenceNode
|
||||
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
self.insets = insets
|
||||
self.customPosition = customPosition
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,6 +504,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
)
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
let reactionsPositionDeltaYDistance = -animationInContentDistance
|
||||
reactionContextNode.layer.animateSpring(
|
||||
from: NSValue(cgPoint: CGPoint(x: 0.0, y: reactionsPositionDeltaYDistance)),
|
||||
to: NSValue(cgPoint: CGPoint()),
|
||||
keyPath: "position",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
initialVelocity: 0.0,
|
||||
damping: springDamping,
|
||||
additive: true
|
||||
)
|
||||
reactionContextNode.animateIn(from: currentContentScreenFrame)
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public final class ContextReferenceContentNode: ASDisplayNode {
|
||||
open class ContextReferenceContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public final class ContextExtractedContentContainingNode: ASDisplayNode {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
open class ContextControllerSourceNode: ASDisplayNode {
|
||||
open class ContextControllerSourceNode: ContextReferenceContentNode {
|
||||
public private(set) var contextGesture: ContextGesture?
|
||||
|
||||
public var isGestureEnabled: Bool = true {
|
||||
|
@ -1575,7 +1575,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
|
||||
private func async(_ f: @escaping () -> Void) {
|
||||
DispatchQueue.main.async(execute: f)
|
||||
DispatchQueue.global(qos: .userInteractive).async(execute: f)
|
||||
}
|
||||
|
||||
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
|
@ -325,7 +325,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
|
||||
}
|
||||
|
||||
|
||||
public final class NavigationButtonNode: ASDisplayNode {
|
||||
public final class NavigationButtonNode: ContextControllerSourceNode {
|
||||
private var nodes: [NavigationButtonItemNode] = []
|
||||
|
||||
public var singleCustomNode: ASDisplayNode? {
|
||||
@ -379,6 +379,7 @@ public final class NavigationButtonNode: ASDisplayNode {
|
||||
super.init()
|
||||
|
||||
self.isAccessibilityElement = false
|
||||
self.isGestureEnabled = false
|
||||
}
|
||||
|
||||
var manualText: String {
|
||||
|
@ -208,7 +208,13 @@ public final class TextNodeLayout: NSObject {
|
||||
hasRTL = true
|
||||
}
|
||||
|
||||
let lineFrame = displayLineFrame(frame: line.frame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: size), cutout: cutout)
|
||||
let lineFrame: CGRect
|
||||
switch self.resolvedAlignment {
|
||||
case .center:
|
||||
lineFrame = CGRect(origin: CGPoint(x: floor((size.width - line.frame.size.width) / 2.0), y: line.frame.minY), size: line.frame.size)
|
||||
default:
|
||||
lineFrame = displayLineFrame(frame: line.frame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: size), cutout: cutout)
|
||||
}
|
||||
|
||||
spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
||||
spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
||||
@ -1073,7 +1079,6 @@ public class TextNode: ASDisplayNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
// brokenLineRange.length = CTLineGetGlyphCount(coreTextLine) - 1
|
||||
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
||||
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||
}
|
||||
@ -1338,7 +1343,7 @@ public class TextNode: ASDisplayNode {
|
||||
context.saveGState()
|
||||
var clipRects: [CGRect] = []
|
||||
for spoiler in line.spoilerWords {
|
||||
var spoilerClipRect = spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||
var spoilerClipRect = spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY - UIScreenPixel)
|
||||
spoilerClipRect.size.height += 1.0 + UIScreenPixel
|
||||
clipRects.append(spoilerClipRect)
|
||||
}
|
||||
@ -1375,7 +1380,7 @@ public class TextNode: ASDisplayNode {
|
||||
context.restoreGState()
|
||||
} else {
|
||||
for spoiler in line.spoilerWords {
|
||||
var spoilerClearRect = spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||
var spoilerClearRect = spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY - UIScreenPixel)
|
||||
spoilerClearRect.size.height += 1.0 + UIScreenPixel
|
||||
clearRects.append(spoilerClearRect)
|
||||
}
|
||||
|
@ -122,7 +122,6 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
emitterLayer.emitterSize = CGSize(width: 1, height: 1)
|
||||
emitterLayer.emitterShape = CAEmitterLayerEmitterShape(rawValue: "rectangles")
|
||||
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
|
||||
// emitterLayer.setValue(0.0322, forKey: "updateInterval")
|
||||
|
||||
emitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
|
||||
emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
@ -188,7 +187,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
|
||||
var scaleAddition = maxFactor * 4.0
|
||||
var durationAddition = -maxFactor * 0.2
|
||||
if self.emitterNode.frame.height > 0.0, self.emitterNode.frame.width / self.emitterNode.frame.height < 0.3 {
|
||||
if self.emitterNode.frame.height > 0.0, self.emitterNode.frame.width / self.emitterNode.frame.height < 0.7 {
|
||||
scaleAddition *= 5.0
|
||||
durationAddition *= 2.0
|
||||
}
|
||||
|
@ -196,6 +196,7 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -318,6 +318,7 @@ private final class ItemListInviteLinkTimeLimitItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0 //params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -558,6 +558,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -328,6 +328,7 @@ private final class ItemListInviteLinkUsageLimitItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0 //params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -713,6 +713,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -416,6 +416,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -327,6 +327,7 @@ public class ItemListAddressItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -627,6 +627,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -715,6 +715,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -251,6 +251,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -253,6 +253,7 @@ public class ItemListCheckboxItemNode: ListViewItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -429,6 +429,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -311,6 +311,7 @@ public class InfoItemNode: ListViewItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -319,6 +319,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -279,6 +279,7 @@ public class ItemListMultilineTextItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -399,6 +399,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -346,6 +346,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -325,6 +325,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -344,6 +344,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -400,10 +400,6 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
func decodeImage() {
|
||||
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
self.codecContext.flushBuffers()
|
||||
self.resetDecoderOnNextFrame = true
|
||||
|
@ -240,6 +240,7 @@ class ChatSlowmodeItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -343,6 +343,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset + sideImageInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
@ -358,7 +359,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
|
||||
if strongSelf.imageNode == nil, let availableReactions = item.availableReactions {
|
||||
let imageNode = ReactionImageNode(context: item.context, availableReactions: availableReactions, reaction: item.reaction)
|
||||
let imageNode = ReactionImageNode(context: item.context, availableReactions: availableReactions, reaction: item.reaction, displayPixelSize: CGSize(width: 30.0 * UIScreenScale, height: 30.0 * UIScreenScale))
|
||||
strongSelf.imageNode = imageNode
|
||||
strongSelf.addSubnode(imageNode)
|
||||
}
|
||||
|
@ -242,6 +242,7 @@ class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -14,6 +14,7 @@ swift_library(
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
|
@ -7,6 +7,7 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramAnimatedStickerNode
|
||||
import ReactionButtonListComponent
|
||||
import SwiftSignalKit
|
||||
|
||||
public final class ReactionContextItem {
|
||||
public struct Reaction: Equatable {
|
||||
@ -60,7 +61,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let previewingItemContainer: ASDisplayNode
|
||||
private var visibleItemNodes: [Int: ReactionNode] = [:]
|
||||
|
||||
private weak var currentLongPressItemNode: ReactionNode?
|
||||
private var longPressRecognizer: UILongPressGestureRecognizer?
|
||||
private var longPressTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var highlightedReaction: ReactionContextItem.Reaction?
|
||||
private var didTriggerExpandedReaction: Bool = false
|
||||
@ -144,9 +146,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
|
||||
longPressGesture.minimumPressDuration = 0.2
|
||||
self.view.addGestureRecognizer(longPressGesture)
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
|
||||
longPressRecognizer.minimumPressDuration = 0.2
|
||||
self.longPressRecognizer = longPressRecognizer
|
||||
self.view.addGestureRecognizer(longPressRecognizer)
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, transition: ContainedViewLayoutTransition) {
|
||||
@ -485,6 +488,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
|
||||
itemNode.layer.animatePosition(from: itemNode.layer.position, to: targetPosition, duration: duration, removeOnCompletion: false)
|
||||
targetSnapshotView.alpha = 1.0
|
||||
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
|
||||
targetSnapshotView.layer.animatePosition(from: sourceFrame.center, to: targetPosition, duration: duration, removeOnCompletion: false)
|
||||
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak targetSnapshotView] _ in
|
||||
@ -606,7 +610,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
intermediateCompletion()
|
||||
}
|
||||
|
||||
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0), completion: { [weak self, weak itemNode, weak targetView] _ in
|
||||
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0), completion: { [weak self, weak itemNode, weak targetView, weak animateTargetContainer] _ in
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -620,7 +624,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
guard let targetView = targetView as? ReactionIconView else {
|
||||
return
|
||||
}
|
||||
if let animateTargetContainer = animateTargetContainer {
|
||||
animateTargetContainer.isHidden = false
|
||||
}
|
||||
targetView.isHidden = false
|
||||
targetView.alpha = 1.0
|
||||
targetView.imageView.alpha = 0.0
|
||||
targetView.addSubnode(itemNode)
|
||||
itemNode.frame = targetView.bounds
|
||||
@ -649,7 +657,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
theme: strongSelf.context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
reaction: itemNode.item,
|
||||
targetView: targetView,
|
||||
hideNode: true,
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.removeFromSupernode()
|
||||
}
|
||||
@ -700,8 +707,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let (size, insets, anchorRect) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 2.5, curve: .linear), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
|
||||
}
|
||||
|
||||
self.longPressTimer?.invalidate()
|
||||
self.longPressTimer = SwiftSignalKit.Timer(timeout: 2.5, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.longPressRecognizer?.state = .cancelled
|
||||
}, queue: .mainQueue())
|
||||
self.longPressTimer?.start()
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
self.longPressTimer?.invalidate()
|
||||
self.continuousHaptic = nil
|
||||
self.didTriggerExpandedReaction = true
|
||||
self.highlightGestureFinished(performAction: true)
|
||||
@ -835,7 +852,6 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
private var isCancelled: Bool = false
|
||||
|
||||
private weak var targetView: UIView?
|
||||
private var hideNode: Bool = false
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
@ -843,18 +859,17 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
||||
self.animateReactionSelection(context: context, theme: theme, reaction: reaction, targetView: targetView, currentItemNode: nil, hideNode: hideNode, completion: completion)
|
||||
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, completion: @escaping () -> Void) {
|
||||
self.animateReactionSelection(context: context, theme: theme, reaction: reaction, targetView: targetView, currentItemNode: nil, completion: completion)
|
||||
}
|
||||
|
||||
func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, currentItemNode: ReactionNode?, hideNode: Bool, completion: @escaping () -> Void) {
|
||||
func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem, targetView: UIView, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
|
||||
guard let sourceSnapshotView = targetView.snapshotContentTree() else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
self.targetView = targetView
|
||||
self.hideNode = hideNode
|
||||
|
||||
let itemNode: ReactionNode
|
||||
if let currentItemNode = currentItemNode {
|
||||
@ -880,9 +895,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
|
||||
targetView.imageView.isHidden = true
|
||||
} else {
|
||||
if hideNode {
|
||||
targetView.isHidden = true
|
||||
}
|
||||
targetView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -947,10 +960,8 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
if let targetView = targetView as? ReactionIconView {
|
||||
targetView.imageView.isHidden = false
|
||||
} else {
|
||||
if strongSelf.hideNode {
|
||||
targetView.alpha = 1.0
|
||||
targetView.isHidden = false
|
||||
}
|
||||
targetView.alpha = 1.0
|
||||
targetView.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -977,63 +988,6 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
/*private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
|
||||
if "".isEmpty {
|
||||
if hideNode {
|
||||
targetView.alpha = 1.0
|
||||
targetView.isHidden = false
|
||||
}
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
guard let targetSnapshotView = targetView.snapshotContentTree(unhide: true) else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
let sourceFrame = itemNode.view.convert(itemNode.bounds, to: self.view)
|
||||
let targetFrame = self.view.convert(targetView.convert(targetView.bounds, to: nil), from: nil)
|
||||
|
||||
targetSnapshotView.frame = targetFrame
|
||||
self.view.insertSubview(targetSnapshotView, belowSubview: itemNode.view)
|
||||
|
||||
var completedTarget = false
|
||||
var targetScaleCompleted = false
|
||||
let intermediateCompletion: () -> Void = {
|
||||
if completedTarget && targetScaleCompleted {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
let targetPosition = targetFrame.center
|
||||
let duration: Double = 0.16
|
||||
|
||||
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
|
||||
itemNode.layer.animatePosition(from: itemNode.layer.position, to: targetPosition, duration: duration, removeOnCompletion: false)
|
||||
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
|
||||
targetSnapshotView.layer.animatePosition(from: sourceFrame.center, to: targetPosition, duration: duration, removeOnCompletion: false)
|
||||
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 1.0, duration: duration, removeOnCompletion: false, completion: { [weak targetSnapshotView] _ in
|
||||
completedTarget = true
|
||||
intermediateCompletion()
|
||||
|
||||
targetSnapshotView?.isHidden = true
|
||||
|
||||
if hideNode {
|
||||
targetView.alpha = 1.0
|
||||
targetView.isHidden = false
|
||||
targetSnapshotView?.isHidden = true
|
||||
targetScaleCompleted = true
|
||||
intermediateCompletion()
|
||||
} else {
|
||||
targetScaleCompleted = true
|
||||
intermediateCompletion()
|
||||
}
|
||||
})
|
||||
|
||||
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 1.0) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
||||
}*/
|
||||
|
||||
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
||||
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y)
|
||||
transition.animateOffsetAdditive(node: self, offset: -offset.y)
|
||||
@ -1042,7 +996,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
public func cancel() {
|
||||
self.isCancelled = true
|
||||
|
||||
if let targetView = self.targetView, self.hideNode {
|
||||
if let targetView = self.targetView {
|
||||
if let targetView = targetView as? ReactionIconView {
|
||||
targetView.imageView.isHidden = false
|
||||
} else {
|
||||
|
@ -269,6 +269,7 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0 //params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -243,6 +243,7 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -201,6 +201,7 @@ private final class CalculatingCacheSizeItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 16.0 + params.leftInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -229,6 +229,7 @@ private final class KeepMediaDurationPickerItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -244,6 +244,7 @@ private final class MaximumCacheSizePickerItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -206,6 +206,7 @@ private final class ProxySettingsActionItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -389,6 +389,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -254,6 +254,7 @@ private final class StorageUsageItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -237,6 +237,7 @@ private final class WebBrowserItemNode: ListViewItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -307,6 +307,7 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -325,6 +325,7 @@ public class NotificationsCategoryItemListItemNode: ListViewItemNode, ItemListIt
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -252,6 +252,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -388,6 +388,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -11,6 +11,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import ReactionImageComponent
|
||||
import WebPBinding
|
||||
|
||||
private final class QuickReactionSetupControllerArguments {
|
||||
let context: AccountContext
|
||||
@ -46,7 +47,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
|
||||
case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: String?)
|
||||
case demoDescription(String)
|
||||
case itemsHeader(String)
|
||||
case item(index: Int, value: String, image: UIImage?, text: String, isSelected: Bool)
|
||||
case item(index: Int, value: String, image: UIImage?, imageIsAnimation: Bool, text: String, isSelected: Bool)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -67,7 +68,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
|
||||
return .demoDescription
|
||||
case .itemsHeader:
|
||||
return .itemsHeader
|
||||
case let .item(_, value, _, _, _):
|
||||
case let .item(_, value, _, _, _, _):
|
||||
return .item(value)
|
||||
}
|
||||
}
|
||||
@ -82,7 +83,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
|
||||
return 2
|
||||
case .itemsHeader:
|
||||
return 3
|
||||
case let .item(index, _, _, _, _):
|
||||
case let .item(index, _, _, _, _, _):
|
||||
return 100 + index
|
||||
}
|
||||
}
|
||||
@ -113,8 +114,8 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .item(index, value, file, text, isEnabled):
|
||||
if case .item(index, value, file, text, isEnabled) = rhs {
|
||||
case let .item(index, value, file, imageIsAnimation, text, isEnabled):
|
||||
if case .item(index, value, file, imageIsAnimation, text, isEnabled) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -152,11 +153,16 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .itemsHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .item(_, value, image, text, isSelected):
|
||||
case let .item(_, value, image, imageIsAnimation, text, isSelected):
|
||||
var imageFitSize = CGSize(width: 30.0, height: 30.0)
|
||||
if imageIsAnimation {
|
||||
imageFitSize.width = floor(imageFitSize.width * 2.0)
|
||||
imageFitSize.height = floor(imageFitSize.height * 2.0)
|
||||
}
|
||||
return ItemListCheckboxItem(
|
||||
presentationData: presentationData,
|
||||
icon: image,
|
||||
iconSize: image?.size.aspectFitted(CGSize(width: 30.0, height: 30.0)),
|
||||
iconSize: image?.size.aspectFitted(imageFitSize),
|
||||
title: text,
|
||||
style: .right,
|
||||
color: .accent,
|
||||
@ -178,7 +184,7 @@ private struct QuickReactionSetupControllerState: Equatable {
|
||||
private func quickReactionSetupControllerEntries(
|
||||
presentationData: PresentationData,
|
||||
availableReactions: AvailableReactions?,
|
||||
images: [String: UIImage],
|
||||
images: [String: (image: UIImage, isAnimation: Bool)],
|
||||
reactionSettings: ReactionSettings,
|
||||
state: QuickReactionSetupControllerState
|
||||
) -> [QuickReactionSetupControllerEntry] {
|
||||
@ -207,7 +213,8 @@ private func quickReactionSetupControllerEntries(
|
||||
entries.append(.item(
|
||||
index: index,
|
||||
value: availableReaction.value,
|
||||
image: images[availableReaction.value],
|
||||
image: images[availableReaction.value]?.image,
|
||||
imageIsAnimation: images[availableReaction.value]?.isAnimation ?? false,
|
||||
text: availableReaction.title,
|
||||
isSelected: reactionSettings.quickReaction == availableReaction.value
|
||||
))
|
||||
@ -263,39 +270,53 @@ public func quickReactionSetupController(
|
||||
return reactionSettings
|
||||
}
|
||||
|
||||
let images: Signal<[String: UIImage], NoError> = context.engine.stickers.availableReactions()
|
||||
|> mapToSignal { availableReactions -> Signal<[String: UIImage], NoError> in
|
||||
var signals: [Signal<(String, UIImage?), NoError>] = []
|
||||
let images: Signal<[String: (image: UIImage, isAnimation: Bool)], NoError> = context.engine.stickers.availableReactions()
|
||||
|> mapToSignal { availableReactions -> Signal<[String: (image: UIImage, isAnimation: Bool)], NoError> in
|
||||
var signals: [Signal<(String, (image: UIImage, isAnimation: Bool)?), NoError>] = []
|
||||
|
||||
if let availableReactions = availableReactions {
|
||||
for availableReaction in availableReactions.reactions {
|
||||
if !availableReaction.isEnabled {
|
||||
continue
|
||||
}
|
||||
guard let centerAnimation = availableReaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
if let centerAnimation = availableReaction.centerAnimation {
|
||||
let signal: Signal<(String, (image: UIImage, isAnimation: Bool)?), NoError> = reactionStaticImage(context: context, animation: centerAnimation, pixelSize: CGSize(width: 72.0 * 2.0, height: 72.0 * 2.0))
|
||||
|> map { data -> (String, (image: UIImage, isAnimation: Bool)?) in
|
||||
guard data.isComplete else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
guard let image = UIImage(data: dataValue) else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
return (availableReaction.value, (image, true))
|
||||
}
|
||||
signals.append(signal)
|
||||
} else {
|
||||
let signal: Signal<(String, (image: UIImage, isAnimation: Bool)?), NoError> = context.account.postbox.mediaBox.resourceData(availableReaction.staticIcon.resource)
|
||||
|> map { data -> (String, (image: UIImage, isAnimation: Bool)?) in
|
||||
guard data.complete else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
guard let image = WebP.convert(fromWebP: dataValue) else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
|
||||
let signal: Signal<(String, UIImage?), NoError> = reactionStaticImage(context: context, animation: centerAnimation, pixelSize: CGSize(width: 72.0, height: 72.0))
|
||||
|> map { data -> (String, UIImage?) in
|
||||
guard data.isComplete else {
|
||||
return (availableReaction.value, nil)
|
||||
return (availableReaction.value, (image, false))
|
||||
}
|
||||
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
guard let image = UIImage(data: dataValue) else {
|
||||
return (availableReaction.value, nil)
|
||||
}
|
||||
return (availableReaction.value, image)
|
||||
signals.append(signal)
|
||||
}
|
||||
signals.append(signal)
|
||||
}
|
||||
}
|
||||
|
||||
return combineLatest(queue: .mainQueue(), signals)
|
||||
|> map { values -> [String: UIImage] in
|
||||
var dict: [String: UIImage] = [:]
|
||||
|> map { values -> [String: (image: UIImage, isAnimation: Bool)] in
|
||||
var dict: [String: (image: UIImage, isAnimation: Bool)] = [:]
|
||||
for (key, image) in values {
|
||||
if let image = image {
|
||||
dict[key] = image
|
||||
|
@ -171,7 +171,6 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
||||
largeApplicationAnimation: reaction.effectAnimation
|
||||
),
|
||||
targetView: targetView,
|
||||
hideNode: true,
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.removeFromSupernode()
|
||||
}
|
||||
@ -306,6 +305,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -232,6 +232,7 @@ class BubbleSettingsRadiusItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -729,6 +729,7 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -479,6 +479,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -833,6 +833,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -256,6 +256,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -184,6 +184,7 @@ class ThemeSettingsBrightnessItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -254,6 +254,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -241,6 +241,7 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -586,6 +586,7 @@ class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = params.leftInset + 16.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
|
@ -2,8 +2,12 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ContextUI
|
||||
|
||||
public final class ShareActionButtonNode: HighlightTrackingButtonNode {
|
||||
private let referenceNode: ContextReferenceContentNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
|
||||
private let badgeLabel: TextNode
|
||||
private var badgeText: NSAttributedString?
|
||||
private let badgeBackground: ASImageNode
|
||||
@ -38,7 +42,14 @@ public final class ShareActionButtonNode: HighlightTrackingButtonNode {
|
||||
}
|
||||
}
|
||||
|
||||
var shouldBegin: (() -> Bool)?
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
public init(badgeBackgroundColor: UIColor, badgeTextColor: UIColor) {
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
|
||||
self.badgeBackgroundColor = badgeBackgroundColor
|
||||
self.badgeTextColor = badgeTextColor
|
||||
|
||||
@ -57,8 +68,27 @@ public final class ShareActionButtonNode: HighlightTrackingButtonNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.addSubnode(self.badgeBackground)
|
||||
self.addSubnode(self.badgeLabel)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||
return false
|
||||
}
|
||||
if let shouldBegin = strongSelf.shouldBegin {
|
||||
return shouldBegin()
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.contextAction?(strongSelf.referenceNode, gesture)
|
||||
}
|
||||
}
|
||||
|
||||
override public func layout() {
|
||||
@ -74,5 +104,8 @@ public final class ShareActionButtonNode: HighlightTrackingButtonNode {
|
||||
self.badgeBackground.frame = backgroundFrame
|
||||
self.badgeLabel.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeLayout.size.width / 2.0), y: backgroundFrame.minY + 3.0), size: badgeLayout.size)
|
||||
}
|
||||
|
||||
self.containerNode.frame = self.bounds
|
||||
self.referenceNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
@ -516,7 +516,7 @@ public final class ShareController: ViewController {
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
})
|
||||
}
|
||||
self.controllerNode.share = { [weak self] text, peerIds in
|
||||
self.controllerNode.share = { [weak self] text, peerIds, showNames, silently in
|
||||
guard let strongSelf = self else {
|
||||
return .complete()
|
||||
}
|
||||
@ -528,6 +528,21 @@ public final class ShareController: ViewController {
|
||||
subject = selectedValue.subject
|
||||
}
|
||||
|
||||
func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool) -> [EnqueueMessage] {
|
||||
return messages.map { message in
|
||||
return message.withUpdatedAttributes({ attributes in
|
||||
var attributes = attributes
|
||||
if !showNames {
|
||||
attributes.append(ForwardOptionsMessageAttribute(hideNames: true, hideCaptions: false))
|
||||
}
|
||||
if silently {
|
||||
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
|
||||
}
|
||||
return attributes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
switch subject {
|
||||
case let .url(url):
|
||||
for peerId in peerIds {
|
||||
@ -537,6 +552,7 @@ public final class ShareController: ViewController {
|
||||
} else {
|
||||
messages.append(.message(text: url, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
}
|
||||
messages = transformMessages(messages, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages))
|
||||
}
|
||||
case let .text(string):
|
||||
@ -546,6 +562,7 @@ public final class ShareController: ViewController {
|
||||
messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
}
|
||||
messages.append(.message(text: string, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
messages = transformMessages(messages, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages))
|
||||
}
|
||||
case let .quote(string, url):
|
||||
@ -558,12 +575,14 @@ public final class ShareController: ViewController {
|
||||
attributedText.append(NSAttributedString(string: "\n\n\(url)"))
|
||||
let entities = generateChatInputTextEntities(attributedText)
|
||||
messages.append(.message(text: attributedText.string, attributes: [TextEntitiesMessageAttribute(entities: entities)], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
messages = transformMessages(messages, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages))
|
||||
}
|
||||
case let .image(representations):
|
||||
for peerId in peerIds {
|
||||
var messages: [EnqueueMessage] = []
|
||||
messages.append(.message(text: text, attributes: [], mediaReference: .standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
messages = transformMessages(messages, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages))
|
||||
}
|
||||
case let .media(mediaReference):
|
||||
@ -578,6 +597,7 @@ public final class ShareController: ViewController {
|
||||
messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
}
|
||||
messages.append(.message(text: sendTextAsCaption ? text : "", attributes: [], mediaReference: mediaReference, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
messages = transformMessages(messages, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages))
|
||||
}
|
||||
case let .mapMedia(media):
|
||||
@ -587,6 +607,7 @@ public final class ShareController: ViewController {
|
||||
messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
}
|
||||
messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
messages = transformMessages(messages, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages))
|
||||
}
|
||||
case let .messages(messages):
|
||||
@ -598,6 +619,7 @@ public final class ShareController: ViewController {
|
||||
for message in messages {
|
||||
messagesToEnqueue.append(.forward(source: message.id, grouping: .auto, attributes: [], correlationId: nil))
|
||||
}
|
||||
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently)
|
||||
shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue))
|
||||
}
|
||||
case let .fromExternal(f):
|
||||
@ -1140,8 +1162,13 @@ public class ShareToInstagramActivity: UIActivity {
|
||||
}
|
||||
|
||||
public override func perform() {
|
||||
if let url = self.activityItems.first as? URL, let data = try? Data(contentsOf: url) {
|
||||
let pasteboardItems: [[String: Any]] = [["com.instagram.sharedSticker.backgroundImage": data]]
|
||||
if let url = self.activityItems.first as? URL, let data = try? Data(contentsOf: url, options: .mappedIfSafe) {
|
||||
let pasteboardItems: [[String: Any]]
|
||||
if url.path.hasSuffix(".mp4") {
|
||||
pasteboardItems = [["com.instagram.sharedSticker.backgroundVideo": data]]
|
||||
} else {
|
||||
pasteboardItems = [["com.instagram.sharedSticker.backgroundImage": data]]
|
||||
}
|
||||
if #available(iOS 10.0, *) {
|
||||
UIPasteboard.general.setItems(pasteboardItems, options: [.expirationDate: Date().addingTimeInterval(5 * 60)])
|
||||
} else {
|
||||
|
@ -59,7 +59,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
var dismiss: ((Bool) -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
var share: ((String, [PeerId]) -> Signal<ShareState, NoError>)?
|
||||
var share: ((String, [PeerId], Bool, Bool) -> Signal<ShareState, NoError>)?
|
||||
var shareExternal: ((Bool) -> Signal<ShareExternalState, NoError>)?
|
||||
var switchToAnotherAccount: (() -> Void)?
|
||||
var debugAction: (() -> Void)?
|
||||
@ -83,6 +83,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
private let presetText: String?
|
||||
|
||||
private let showNames = ValuePromise<Bool>(true)
|
||||
|
||||
init(sharedContext: SharedAccountContext, presentationData: PresentationData, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, fromForeignApp: Bool, forceTheme: PresentationTheme?, fromPublicChannel: Bool, segmentedValues: [ShareControllerSegmentedValue]?) {
|
||||
self.sharedContext = sharedContext
|
||||
self.presentationData = presentationData
|
||||
@ -179,6 +181,59 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
self.isHidden = true
|
||||
|
||||
self.actionButtonNode.shouldBegin = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
return !strongSelf.controllerInteraction!.selectedPeers.isEmpty
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
self.actionButtonNode.contextAction = { [weak self] node, gesture in
|
||||
if let strongSelf = self, let context = strongSelf.context, let node = node as? ContextReferenceContentNode {
|
||||
let presentationData = strongSelf.presentationData
|
||||
let items: Signal<ContextController.Items, NoError> =
|
||||
strongSelf.showNames.get()
|
||||
|> map { showNamesValue in
|
||||
return ContextController.Items(content: .list([
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_ShowSendersName, icon: { theme in
|
||||
if showNamesValue {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { _, _ in
|
||||
self?.showNames.set(true)
|
||||
})),
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_HideSendersName, icon: { theme in
|
||||
if !showNamesValue {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { _, _ in
|
||||
self?.showNames.set(false)
|
||||
})),
|
||||
.separator,
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
if let strongSelf = self {
|
||||
strongSelf.send(showNames: showNamesValue, silently: true)
|
||||
}
|
||||
})),
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_SendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
if let strongSelf = self {
|
||||
strongSelf.send(showNames: showNamesValue, silently: false)
|
||||
}
|
||||
})),
|
||||
]))
|
||||
}
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: -116.0))), items: items, gesture: gesture)
|
||||
contextController.immediateItemsTransitionAnimation = true
|
||||
strongSelf.present?(contextController)
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
|
||||
if let strongSelf = self {
|
||||
var added = false
|
||||
@ -542,11 +597,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
defaultAction.action()
|
||||
}
|
||||
} else {
|
||||
self.send()
|
||||
let _ = (self.showNames.get()
|
||||
|> take(1)).start(next: { [weak self] showNames in
|
||||
self?.send(showNames: showNames)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func send(peerId: PeerId? = nil) {
|
||||
func send(peerId: PeerId? = nil, showNames: Bool = true, silently: Bool = false) {
|
||||
if !self.inputFieldNode.text.isEmpty {
|
||||
for peer in self.controllerInteraction!.selectedPeers {
|
||||
if let channel = peer.peer as? TelegramChannel, channel.isRestrictedBySlowmode {
|
||||
@ -579,7 +637,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
donateSendMessageIntent(account: context.account, sharedContext: self.sharedContext, intentContext: .share, peerIds: peerIds)
|
||||
}
|
||||
|
||||
if let signal = self.share?(self.inputFieldNode.text, peerIds) {
|
||||
if let signal = self.share?(self.inputFieldNode.text, peerIds, showNames, silently) {
|
||||
var wasDone = false
|
||||
let timestamp = CACurrentMediaTime()
|
||||
let doneImpl: (Bool) -> Void = { [weak self] shouldDelay in
|
||||
@ -809,20 +867,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}))
|
||||
}
|
||||
|
||||
if strongSelf.fromPublicChannel, let context = strongSelf.context, let node = node as? ContextReferenceContentNode {
|
||||
let presentationData = strongSelf.presentationData
|
||||
let items: [ContextMenuItem] = [
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Share_ShareAsLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
proceed(false)
|
||||
})),
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.Share_ShareAsImage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Image"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
proceed(true)
|
||||
}))
|
||||
]
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
strongSelf.present?(contextController)
|
||||
if strongSelf.fromPublicChannel {
|
||||
proceed(true)
|
||||
} else {
|
||||
proceed(false)
|
||||
}
|
||||
@ -1010,12 +1056,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
private final class ShareContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
private let customPosition: CGPoint?
|
||||
|
||||
init(sourceNode: ContextReferenceContentNode) {
|
||||
init(sourceNode: ContextReferenceContentNode, customPosition: CGPoint?) {
|
||||
self.sourceNode = sourceNode
|
||||
self.customPosition = customPosition
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds, customPosition: self.customPosition)
|
||||
}
|
||||
}
|
||||
|
23
submodules/SoftwareVideo/BUILD
Normal file
23
submodules/SoftwareVideo/BUILD
Normal file
@ -0,0 +1,23 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "SoftwareVideo",
|
||||
module_name = "SoftwareVideo",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
22
submodules/SoftwareVideo/Info.plist
Normal file
22
submodules/SoftwareVideo/Info.plist
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
@ -14,19 +14,19 @@ private final class SampleBufferLayerImpl: AVSampleBufferDisplayLayer {
|
||||
}
|
||||
}
|
||||
|
||||
final class SampleBufferLayer {
|
||||
let layer: AVSampleBufferDisplayLayer
|
||||
public final class SampleBufferLayer {
|
||||
public let layer: AVSampleBufferDisplayLayer
|
||||
private let enqueue: (AVSampleBufferDisplayLayer) -> Void
|
||||
|
||||
|
||||
var isFreed: Bool = false
|
||||
public var isFreed: Bool = false
|
||||
fileprivate init(layer: AVSampleBufferDisplayLayer, enqueue: @escaping (AVSampleBufferDisplayLayer) -> Void) {
|
||||
self.layer = layer
|
||||
self.enqueue = enqueue
|
||||
}
|
||||
|
||||
deinit {
|
||||
if !isFreed {
|
||||
if !self.isFreed {
|
||||
self.enqueue(self.layer)
|
||||
}
|
||||
}
|
||||
@ -34,11 +34,11 @@ final class SampleBufferLayer {
|
||||
|
||||
private let pool = Atomic<[AVSampleBufferDisplayLayer]>(value: [])
|
||||
|
||||
func clearSampleBufferLayerPoll() {
|
||||
public func clearSampleBufferLayerPoll() {
|
||||
let _ = pool.modify { _ in return [] }
|
||||
}
|
||||
|
||||
func takeSampleBufferLayer() -> SampleBufferLayer {
|
||||
public func takeSampleBufferLayer() -> SampleBufferLayer {
|
||||
var layer: AVSampleBufferDisplayLayer?
|
||||
let _ = pool.modify { list in
|
||||
var list = list
|
@ -10,7 +10,7 @@ private let applyQueue = Queue()
|
||||
private let workers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
||||
private var nextWorker = 0
|
||||
|
||||
final class SoftwareVideoLayerFrameManager {
|
||||
public final class SoftwareVideoLayerFrameManager {
|
||||
private let fetchDisposable: Disposable
|
||||
private var dataDisposable = MetaDisposable()
|
||||
private let source = Atomic<SoftwareVideoSource?>(value: nil)
|
||||
@ -32,7 +32,10 @@ final class SoftwareVideoLayerFrameManager {
|
||||
|
||||
private var layerRotationAngleAndAspect: (CGFloat, CGFloat)?
|
||||
|
||||
init(account: Account, fileReference: FileMediaReference, layerHolder: SampleBufferLayer, hintVP9: Bool = false) {
|
||||
private var didStart = false
|
||||
var started: () -> Void = { }
|
||||
|
||||
public init(account: Account, fileReference: FileMediaReference, layerHolder: SampleBufferLayer, hintVP9: Bool = false) {
|
||||
var resource = fileReference.media.resource
|
||||
var secondaryResource: MediaResource?
|
||||
for attribute in fileReference.media.attributes {
|
||||
@ -61,7 +64,7 @@ final class SoftwareVideoLayerFrameManager {
|
||||
self.dataDisposable.dispose()
|
||||
}
|
||||
|
||||
func start() {
|
||||
public func start() {
|
||||
func stringForResource(_ resource: MediaResource?) -> String {
|
||||
guard let resource = resource else {
|
||||
return "<none>"
|
||||
@ -115,7 +118,7 @@ final class SoftwareVideoLayerFrameManager {
|
||||
}))
|
||||
}
|
||||
|
||||
func tick(timestamp: Double) {
|
||||
public func tick(timestamp: Double) {
|
||||
applyQueue.async {
|
||||
if self.baseTimestamp == nil && !self.frames.isEmpty {
|
||||
self.baseTimestamp = timestamp
|
||||
@ -148,6 +151,13 @@ final class SoftwareVideoLayerFrameManager {
|
||||
self.layerHolder.layer.setAffineTransform(transform)
|
||||
}*/
|
||||
self.layerHolder.layer.enqueue(frame.sampleBuffer)
|
||||
|
||||
if !self.didStart {
|
||||
self.didStart = true
|
||||
Queue.mainQueue().async {
|
||||
self.started()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
56
submodules/SoftwareVideo/Sources/VideoStickerNode.swift
Normal file
56
submodules/SoftwareVideo/Sources/VideoStickerNode.swift
Normal file
@ -0,0 +1,56 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
|
||||
public class VideoStickerNode: ASDisplayNode {
|
||||
private var layerHolder: SampleBufferLayer?
|
||||
private var manager: SoftwareVideoLayerFrameManager?
|
||||
|
||||
private var displayLink: ConstantDisplayLinkAnimator?
|
||||
private var displayLinkTimestamp: Double = 0.0
|
||||
|
||||
public var started: () -> Void = {}
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
public func update(isPlaying: Bool) {
|
||||
let displayLink: ConstantDisplayLinkAnimator
|
||||
if let current = self.displayLink {
|
||||
displayLink = current
|
||||
} else {
|
||||
displayLink = ConstantDisplayLinkAnimator { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.manager?.tick(timestamp: strongSelf.displayLinkTimestamp)
|
||||
strongSelf.displayLinkTimestamp += 1.0 / 30.0
|
||||
}
|
||||
displayLink.frameInterval = 2
|
||||
self.displayLink = displayLink
|
||||
}
|
||||
self.displayLink?.isPaused = !isPlaying
|
||||
}
|
||||
|
||||
public func update(account: Account, fileReference: FileMediaReference) {
|
||||
let layerHolder = takeSampleBufferLayer()
|
||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
if let size = self.validLayout {
|
||||
layerHolder.layer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
self.layer.addSublayer(layerHolder.layer)
|
||||
self.layerHolder = layerHolder
|
||||
|
||||
let manager = SoftwareVideoLayerFrameManager(account: account, fileReference: fileReference, layerHolder: layerHolder, hintVP9: true)
|
||||
manager.started = self.started
|
||||
self.manager = manager
|
||||
manager.start()
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
self.validLayout = size
|
||||
|
||||
self.layerHolder?.layer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
@ -236,6 +236,7 @@ class MessageStatsOverviewItemNode: ListViewItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
@ -238,6 +238,7 @@ class StatsGraphItemNode: ListViewItemNode {
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
hasBottomCorners = true
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user