Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2024-05-17 23:43:32 +04:00
commit 347cf2da81
71 changed files with 2560 additions and 918 deletions

View file

@ -742,7 +742,7 @@ private final class NotificationServiceHandler {
Logger.shared.logToConsole = loggingSettings.logToConsole
Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false)
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false)
let isLockedMessage: String?
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {

View file

@ -174,7 +174,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
if let accountCache = accountCache {
account = .single(accountCache)
} else {
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|> mapToSignal { account -> Signal<Account?, NoError> in
if let account = account {
switch account {

View file

@ -6,30 +6,169 @@
#include <LottieCpp/RenderTreeNode.h>
namespace lottie {
namespace {
static void processRenderContentItem(std::shared_ptr<RenderTreeNodeContentItem> const &contentItem, std::optional<CGRect> &effectiveLocalBounds, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
for (const auto &shadingVariant : contentItem->shadings) {
CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value());
if (shadingVariant->stroke) {
shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0);
if (effectiveLocalBounds) {
effectiveLocalBounds = effectiveLocalBounds->unionWith(shapeBounds);
} else {
effectiveLocalBounds = shapeBounds;
}
} else if (shadingVariant->fill) {
if (effectiveLocalBounds) {
effectiveLocalBounds = effectiveLocalBounds->unionWith(shapeBounds);
} else {
effectiveLocalBounds = shapeBounds;
}
struct TransformedPath {
lottie::BezierPath path;
lottie::CATransform3D transform;
TransformedPath(lottie::BezierPath const &path_, lottie::CATransform3D const &transform_) :
path(path_),
transform(transform_) {
}
};
static lottie::CGRect collectPathBoundingBoxes(std::shared_ptr<lottie::RenderTreeNodeContentItem> item, size_t subItemLimit, lottie::CATransform3D const &parentTransform, bool skipApplyTransform) {
//TODO:remove skipApplyTransform
lottie::CATransform3D effectiveTransform = parentTransform;
if (!skipApplyTransform && item->isGroup) {
effectiveTransform = item->transform * effectiveTransform;
}
size_t maxSubitem = std::min(item->subItems.size(), subItemLimit);
lottie::CGRect boundingBox(0.0, 0.0, 0.0, 0.0);
if (item->path) {
boundingBox = item->pathBoundingBox.applyingTransform(effectiveTransform);
}
for (size_t i = 0; i < maxSubitem; i++) {
auto &subItem = item->subItems[i];
lottie::CGRect subItemBoundingBox = collectPathBoundingBoxes(subItem, INT32_MAX, effectiveTransform, false);
if (boundingBox.empty()) {
boundingBox = subItemBoundingBox;
} else {
boundingBox = boundingBox.unionWith(subItemBoundingBox);
}
}
for (const auto &subItem : contentItem->subItems) {
processRenderContentItem(subItem, effectiveLocalBounds, bezierPathsBoundingBoxContext);
return boundingBox;
}
static std::vector<TransformedPath> collectPaths(std::shared_ptr<lottie::RenderTreeNodeContentItem> item, size_t subItemLimit, lottie::CATransform3D const &parentTransform, bool skipApplyTransform) {
std::vector<TransformedPath> mappedPaths;
//TODO:remove skipApplyTransform
lottie::CATransform3D effectiveTransform = parentTransform;
if (!skipApplyTransform && item->isGroup) {
effectiveTransform = item->transform * effectiveTransform;
}
size_t maxSubitem = std::min(item->subItems.size(), subItemLimit);
if (item->path) {
mappedPaths.emplace_back(item->path.value(), effectiveTransform);
}
assert(!item->trimParams);
for (size_t i = 0; i < maxSubitem; i++) {
auto &subItem = item->subItems[i];
auto subItemPaths = collectPaths(subItem, INT32_MAX, effectiveTransform, false);
for (auto &path : subItemPaths) {
mappedPaths.emplace_back(path.path, path.transform);
}
}
return mappedPaths;
}
}
namespace lottie {
static void processRenderContentItem(std::shared_ptr<RenderTreeNodeContentItem> const &contentItem, Vector2D const &globalSize, CATransform3D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
auto currentTransform = parentTransform;
CATransform3D localTransform = contentItem->transform;
currentTransform = localTransform * currentTransform;
if (!currentTransform.isInvertible()) {
contentItem->renderData.isValid = false;
return;
}
std::optional<CGRect> globalRect;
int drawContentDescendants = 0;
for (const auto &shadingVariant : contentItem->shadings) {
lottie::CGRect shapeBounds = collectPathBoundingBoxes(contentItem, shadingVariant->subItemLimit, lottie::CATransform3D::identity(), true);
if (shadingVariant->stroke) {
shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0);
} else if (shadingVariant->fill) {
} else {
continue;
}
drawContentDescendants += 1;
CGRect shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform);
if (globalRect) {
globalRect = globalRect->unionWith(shapeGlobalBounds);
} else {
globalRect = shapeGlobalBounds;
}
}
if (contentItem->isGroup) {
for (auto it = contentItem->subItems.rbegin(); it != contentItem->subItems.rend(); it++) {
const auto &subItem = *it;
processRenderContentItem(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext);
if (subItem->renderData.isValid) {
drawContentDescendants += subItem->renderData.drawContentDescendants;
if (globalRect) {
globalRect = globalRect->unionWith(subItem->renderData.globalRect);
} else {
globalRect = subItem->renderData.globalRect;
}
}
}
} else {
for (const auto &subItem : contentItem->subItems) {
subItem->renderData.isValid = false;
}
}
if (!globalRect) {
contentItem->renderData.isValid = false;
return;
}
CGRect integralGlobalRect(
std::floor(globalRect->x),
std::floor(globalRect->y),
std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)),
std::ceil(globalRect->height + globalRect->y - floor(globalRect->y))
);
if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(integralGlobalRect)) {
contentItem->renderData.isValid = false;
return;
}
if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) {
contentItem->renderData.isValid = false;
return;
}
contentItem->renderData.isValid = true;
contentItem->renderData.layer._bounds = CGRect(0.0, 0.0, 0.0, 0.0);
contentItem->renderData.layer._position = Vector2D(0.0, 0.0);
contentItem->renderData.layer._transform = contentItem->transform;
contentItem->renderData.layer._opacity = contentItem->alpha;
contentItem->renderData.layer._masksToBounds = false;
contentItem->renderData.layer._isHidden = false;
contentItem->renderData.globalRect = integralGlobalRect;
contentItem->renderData.globalTransform = currentTransform;
contentItem->renderData.drawContentDescendants = drawContentDescendants;
contentItem->renderData.isInvertedMatte = false;
}
static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
@ -58,102 +197,59 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
return;
}
std::optional<CGRect> effectiveLocalBounds;
double alpha = node->alpha();
int drawContentDescendants = 0;
std::optional<CGRect> globalRect;
if (node->_contentItem) {
processRenderContentItem(node->_contentItem, effectiveLocalBounds, bezierPathsBoundingBoxContext);
processRenderContentItem(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext);
if (node->_contentItem->renderData.isValid) {
drawContentDescendants += node->_contentItem->renderData.drawContentDescendants;
globalRect = node->_contentItem->renderData.globalRect;
}
}
bool isInvertedMatte = isInvertedMask;
if (isInvertedMatte) {
effectiveLocalBounds = node->bounds();
CGRect globalBounds = node->bounds().applyingTransform(currentTransform);
if (globalRect) {
globalRect = globalRect->unionWith(globalBounds);
} else {
globalRect = globalBounds;
}
}
if (effectiveLocalBounds && effectiveLocalBounds->empty()) {
effectiveLocalBounds = std::nullopt;
}
std::optional<CGRect> effectiveLocalRect;
if (effectiveLocalBounds.has_value()) {
effectiveLocalRect = effectiveLocalBounds;
}
std::optional<CGRect> subnodesGlobalRect;
bool masksToBounds = node->masksToBounds();
int drawContentDescendants = 0;
for (const auto &item : node->subnodes()) {
processRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext);
if (item->renderData.isValid) {
drawContentDescendants += item->renderData.drawContentDescendants;
if (item->_contentItem) {
drawContentDescendants += 1;
}
if (!item->renderData.localRect.empty()) {
if (effectiveLocalRect.has_value()) {
effectiveLocalRect = effectiveLocalRect->unionWith(item->renderData.localRect);
} else {
effectiveLocalRect = item->renderData.localRect;
}
}
if (subnodesGlobalRect) {
subnodesGlobalRect = subnodesGlobalRect->unionWith(item->renderData.globalRect);
if (globalRect) {
globalRect = globalRect->unionWith(item->renderData.globalRect);
} else {
subnodesGlobalRect = item->renderData.globalRect;
globalRect = item->renderData.globalRect;
}
}
}
if (masksToBounds && effectiveLocalRect.has_value()) {
if (node->bounds().contains(effectiveLocalRect.value())) {
masksToBounds = false;
}
}
std::optional<CGRect> fuzzyGlobalRect;
if (effectiveLocalBounds) {
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
if (fuzzyGlobalRect) {
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds);
} else {
fuzzyGlobalRect = effectiveGlobalBounds;
}
}
if (subnodesGlobalRect) {
if (fuzzyGlobalRect) {
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value());
} else {
fuzzyGlobalRect = subnodesGlobalRect;
}
}
if (!fuzzyGlobalRect) {
if (!globalRect) {
node->renderData.isValid = false;
return;
}
CGRect globalRect(
std::floor(fuzzyGlobalRect->x),
std::floor(fuzzyGlobalRect->y),
std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)),
std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y))
CGRect integralGlobalRect(
std::floor(globalRect->x),
std::floor(globalRect->y),
std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)),
std::ceil(globalRect->height + globalRect->y - floor(globalRect->y))
);
if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) {
if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(integralGlobalRect)) {
node->renderData.isValid = false;
return;
}
if (masksToBounds && effectiveLocalBounds) {
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
bool masksToBounds = node->masksToBounds();
if (masksToBounds) {
CGRect effectiveGlobalBounds = node->bounds().applyingTransform(currentTransform);
if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) {
masksToBounds = false;
}
@ -162,7 +258,7 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
if (node->mask()) {
processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext);
if (node->mask()->renderData.isValid) {
if (!node->mask()->renderData.globalRect.intersects(globalRect)) {
if (!node->mask()->renderData.globalRect.intersects(integralGlobalRect)) {
node->renderData.isValid = false;
return;
}
@ -172,21 +268,22 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
}
}
CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform);
if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) {
node->renderData.isValid = false;
return;
}
node->renderData.isValid = true;
node->renderData.layer._bounds = node->bounds();
node->renderData.layer._position = node->position();
node->renderData.layer._transform = node->transform();
node->renderData.layer._opacity = alpha;
node->renderData.layer._opacity = node->alpha();
node->renderData.layer._masksToBounds = masksToBounds;
node->renderData.layer._isHidden = node->isHidden();
node->renderData.globalRect = globalRect;
node->renderData.localRect = localRect;
node->renderData.globalRect = integralGlobalRect;
node->renderData.globalTransform = currentTransform;
node->renderData.drawsContent = effectiveLocalBounds.has_value();
node->renderData.drawContentDescendants = drawContentDescendants;
node->renderData.isInvertedMatte = isInvertedMatte;
}
@ -195,9 +292,62 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
namespace {
static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> context, std::shared_ptr<lottie::RenderTreeNodeContentItem> item) {
static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> parentContext, std::shared_ptr<lottie::RenderTreeNodeContentItem> item, double parentAlpha) {
if (!item->renderData.isValid) {
return;
}
float normalizedOpacity = item->renderData.layer.opacity();
double layerAlpha = ((double)normalizedOpacity) * parentAlpha;
if (item->renderData.layer.isHidden() || normalizedOpacity == 0.0f) {
return;
}
parentContext->saveState();
std::shared_ptr<lottieRendering::Canvas> currentContext;
std::shared_ptr<lottieRendering::Canvas> tempContext;
bool needsTempContext = false;
needsTempContext = layerAlpha != 1.0 && item->renderData.drawContentDescendants > 1;
if (needsTempContext) {
auto tempContextValue = parentContext->makeLayer((int)(item->renderData.globalRect.width), (int)(item->renderData.globalRect.height));
tempContext = tempContextValue;
currentContext = tempContextValue;
currentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-item->renderData.globalRect.x, -item->renderData.globalRect.y)));
currentContext->saveState();
currentContext->concatenate(item->renderData.globalTransform);
} else {
currentContext = parentContext;
}
parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(item->renderData.layer.position().x, item->renderData.layer.position().y)));
parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-item->renderData.layer.bounds().x, -item->renderData.layer.bounds().y)));
parentContext->concatenate(item->renderData.layer.transform());
double renderAlpha = 1.0;
if (tempContext) {
renderAlpha = 1.0;
} else {
renderAlpha = layerAlpha;
}
for (const auto &shading : item->shadings) {
if (shading->explicitPath->empty()) {
std::vector<lottie::BezierPath> itemPaths;
if (shading->explicitPath) {
itemPaths = shading->explicitPath.value();
} else {
auto rawPaths = collectPaths(item, shading->subItemLimit, lottie::CATransform3D::identity(), true);
for (const auto &rawPath : rawPaths) {
itemPaths.push_back(rawPath.path.copyUsingTransform(rawPath.transform));
}
}
if (itemPaths.empty()) {
continue;
}
@ -228,7 +378,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
};
LottiePathItem pathItem;
for (const auto &path : shading->explicitPath.value()) {
for (const auto &path : itemPaths) {
std::optional<lottie::PathElement> previousElement;
for (const auto &element : path.elements()) {
if (previousElement.has_value()) {
@ -304,7 +454,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
dashPattern = shading->stroke->dashPattern;
}
context->strokePath(path, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity));
currentContext->strokePath(path, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha));
} else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) {
//TODO:gradient stroke
}
@ -328,7 +478,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) {
lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get();
if (solidShading->opacity != 0.0) {
context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity));
currentContext->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha));
}
} else if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) {
lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get();
@ -337,7 +487,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
std::vector<lottieRendering::Color> colors;
std::vector<double> locations;
for (const auto &color : gradientShading->colors) {
colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity));
colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha));
}
locations = gradientShading->locations;
@ -347,11 +497,11 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
switch (gradientShading->gradientType) {
case lottie::GradientType::Linear: {
context->linearGradientFillPath(path, rule, gradient, start, end);
currentContext->linearGradientFillPath(path, rule, gradient, start, end);
break;
}
case lottie::GradientType::Radial: {
context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end));
currentContext->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end));
break;
}
default: {
@ -363,9 +513,21 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
}
}
for (const auto &subItem : item->subItems) {
drawLottieContentItem(context, subItem);
for (auto it = item->subItems.rbegin(); it != item->subItems.rend(); it++) {
const auto &subItem = *it;
drawLottieContentItem(currentContext, subItem, renderAlpha);
}
if (tempContext) {
tempContext->restoreState();
parentContext->concatenate(item->renderData.globalTransform.inverted());
parentContext->setAlpha(layerAlpha);
parentContext->draw(tempContext, item->renderData.globalRect);
parentContext->setAlpha(1.0);
}
parentContext->restoreState();
}
static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node, std::shared_ptr<lottieRendering::Canvas> parentContext, lottie::Vector2D const &globalSize, double parentAlpha) {
@ -432,10 +594,8 @@ static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node,
renderAlpha = layerAlpha;
}
currentContext->setAlpha(renderAlpha);
if (node->_contentItem) {
drawLottieContentItem(currentContext, node->_contentItem);
drawLottieContentItem(currentContext, node->_contentItem, renderAlpha);
}
if (node->renderData.isInvertedMatte) {
@ -497,6 +657,10 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
return nil;
}
if (!useReferenceRendering) {
return nil;
}
processRenderTree(renderNode, lottie::Vector2D((int)size.width, (int)size.height), lottie::CATransform3D::identity().scaled(lottie::Vector2D(size.width / (double)animation.size.width, size.height / (double)animation.size.height)), false, *_bezierPathsBoundingBoxContext.get());
if (useReferenceRendering) {

View file

@ -119,7 +119,7 @@ public final class ViewController: UIViewController {
self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
if "".isEmpty {
if !"".isEmpty {
if #available(iOS 13.0, *) {
self.test = ReferenceCompareTest(view: self.view)
}
@ -167,7 +167,7 @@ public final class ViewController: UIViewController {
var frameIndex = 0
while true {
animationContainer.update(frameIndex)
//let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false)
let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false)
frameIndex = (frameIndex + 1) % animationContainer.animation.frameCount
numUpdates += 1
let timestamp = CFAbsoluteTimeGetCurrent()

View file

@ -969,6 +969,7 @@ public protocol SharedAccountContext: AnyObject {
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
func makeStorySearchController(context: AccountContext, query: String) -> ViewController
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
func makeArchiveSettingsController(context: AccountContext) -> ViewController
func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
@ -1073,11 +1074,24 @@ public protocol AccountGroupCallContext: AnyObject {
public protocol AccountGroupCallContextCache: AnyObject {
}
public final class ChatSendMessageActionSheetControllerMessageEffect {
public let id: Int64
public struct ChatSendMessageActionSheetControllerSendParameters {
public struct Effect {
public let id: Int64
public init(id: Int64) {
self.id = id
}
}
public init(id: Int64) {
self.id = id
public var effect: Effect?
public var textIsAboveMedia: Bool
public init(
effect: Effect?,
textIsAboveMedia: Bool
) {
self.effect = effect
self.textIsAboveMedia = textIsAboveMedia
}
}
@ -1093,7 +1107,7 @@ public protocol ChatSendMessageActionSheetControllerSourceSendButtonNode: ASDisp
public protocol ChatSendMessageActionSheetController: ViewController {
typealias SendMode = ChatSendMessageActionSheetControllerSendMode
typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect
typealias SendParameters = ChatSendMessageActionSheetControllerSendParameters
}
public protocol AccountContext: AnyObject {

View file

@ -3,7 +3,7 @@ import Display
import SwiftSignalKit
public protocol ContactSelectionController: ViewController {
var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?, NoError> { get }
var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get }
var displayProgress: Bool { get set }
var dismissed: (() -> Void)? { get set }
var presentScheduleTimePicker: (@escaping (Int32) -> Void) -> Void { get set }

View file

@ -147,7 +147,7 @@ public enum PeerSelectionControllerContext {
public protocol PeerSelectionController: ViewController {
var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set }
var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)? { get set }
var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)? { get set }
var inProgress: Bool { get set }
var customDismiss: (() -> Void)? { get set }
}

View file

@ -269,7 +269,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)?
public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void = { _, _ in }
public var updateHeight: (Bool) -> Void = { _ in }
private var updatingInputState = false

View file

@ -154,14 +154,18 @@ public protocol AttachmentMediaPickerContext {
var selectionCount: Signal<Int, NoError> { get }
var caption: Signal<NSAttributedString?, NoError> { get }
var hasCaption: Bool { get }
var captionIsAboveMedia: Signal<Bool, NoError> { get }
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void
var loadingProgress: Signal<CGFloat?, NoError> { get }
var mainButtonState: Signal<AttachmentMainButtonState?, NoError> { get }
func mainButtonAction()
func setCaption(_ caption: NSAttributedString)
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?)
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?)
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?)
}
private func generateShadowImage() -> UIImage? {
@ -249,7 +253,7 @@ public class AttachmentController: ViewController {
private var selectionCount: Int = 0
fileprivate var mediaPickerContext: AttachmentMediaPickerContext? {
var mediaPickerContext: AttachmentMediaPickerContext? {
didSet {
if let mediaPickerContext = self.mediaPickerContext {
self.captionDisposable.set((mediaPickerContext.caption
@ -317,7 +321,7 @@ public class AttachmentController: ViewController {
self.container = AttachmentContainer()
self.container.canHaveKeyboardFocus = true
self.panel = AttachmentPanel(context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView)
self.panel = AttachmentPanel(controller: controller, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView)
self.panel.fromMenu = controller.fromMenu
self.panel.isStandalone = controller.isStandalone
@ -423,17 +427,17 @@ public class AttachmentController: ViewController {
}
}
self.panel.sendMessagePressed = { [weak self] mode, messageEffect in
self.panel.sendMessagePressed = { [weak self] mode, parameters in
if let strongSelf = self {
switch mode {
case .generic:
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, messageEffect: messageEffect)
strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, parameters: parameters)
case .silent:
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, messageEffect: messageEffect)
strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, parameters: parameters)
case .schedule:
strongSelf.mediaPickerContext?.schedule(messageEffect: messageEffect)
strongSelf.mediaPickerContext?.schedule(parameters: parameters)
case .whenOnline:
strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media, messageEffect: messageEffect)
strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media, parameters: parameters)
}
}
}

View file

@ -683,6 +683,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
}
final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private weak var controller: AttachmentController?
private let context: AccountContext
private let isScheduledMessages: Bool
private var presentationData: PresentationData
@ -729,7 +730,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var beganTextEditing: () -> Void = {}
var textUpdated: (NSAttributedString) -> Void = { _ in }
var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in }
var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void = { _, _ in }
var requestLayout: () -> Void = {}
var present: (ViewController) -> Void = { _ in }
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
@ -738,7 +739,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var mainButtonPressed: () -> Void = { }
init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
init(controller: AttachmentController, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
self.controller = controller
self.context = context
self.updatedPresentationData = updatedPresentationData
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
@ -982,8 +984,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
isReady = .single(true)
}
let _ = (isReady
|> deliverOnMainQueue).start(next: { [weak strongSelf] _ in
var captionIsAboveMedia: Signal<Bool, NoError> = .single(false)
if let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext {
captionIsAboveMedia = mediaPickerContext.captionIsAboveMedia
}
let _ = (combineLatest(
isReady,
captionIsAboveMedia |> take(1)
)
|> deliverOnMainQueue).start(next: { [weak strongSelf] _, captionIsAboveMedia in
guard let strongSelf else {
return
}
@ -998,6 +1008,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
sourceSendButton: node,
textInputView: textInputNode.textView,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: (captionIsAboveMedia, { [weak strongSelf] value in
guard let strongSelf, let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext else {
return
}
mediaPickerContext.setCaptionIsAboveMedia(value)
}),
emojiViewProvider: textInputPanelNode.emojiViewProvider,
attachment: true,
canSendWhenOnline: sendWhenOnlineAvailable,

View file

@ -389,7 +389,7 @@ public final class CallListController: TelegramBaseController {
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak controller, weak self] result in
controller?.dismissSearch()
if let strongSelf = self, let (contactPeers, action, _, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer {
if let strongSelf = self, let (contactPeers, action, _, _, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer {
strongSelf.call(peer.id, isVideo: action == .videoCall, began: {
if let strongSelf = self {
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()

View file

@ -29,8 +29,8 @@ private final class ChatSendMessageActionSheetControllerImpl: ViewController, Ch
private let attachment: Bool
private let canSendWhenOnline: Bool
private let completion: () -> Void
private let sendMessage: (SendMode, MessageEffect?) -> Void
private let schedule: (MessageEffect?) -> Void
private let sendMessage: (SendMode, SendParameters?) -> Void
private let schedule: (SendParameters?) -> Void
private let reactionItems: [ReactionItem]?
private var presentationData: PresentationData
@ -44,7 +44,7 @@ private final class ChatSendMessageActionSheetControllerImpl: ViewController, Ch
private let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id?, isScheduledMessages: Bool = false, forwardMessageIds: [EngineMessage.Id]?, hasEntityKeyboard: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, attachment: Bool = false, canSendWhenOnline: Bool, completion: @escaping () -> Void, sendMessage: @escaping (SendMode, MessageEffect?) -> Void, schedule: @escaping (MessageEffect?) -> Void, reactionItems: [ReactionItem]? = nil) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id?, isScheduledMessages: Bool = false, forwardMessageIds: [EngineMessage.Id]?, hasEntityKeyboard: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, attachment: Bool = false, canSendWhenOnline: Bool, completion: @escaping () -> Void, sendMessage: @escaping (SendMode, SendParameters?) -> Void, schedule: @escaping (SendParameters?) -> Void, reactionItems: [ReactionItem]? = nil) {
self.context = context
self.peerId = peerId
self.isScheduledMessages = isScheduledMessages
@ -108,32 +108,16 @@ private final class ChatSendMessageActionSheetControllerImpl: ViewController, Ch
}
self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, presentationData: self.presentationData, reminders: reminders, gesture: gesture, sourceSendButton: self.sourceSendButton, textInputView: self.textInputView, attachment: self.attachment, canSendWhenOnline: self.canSendWhenOnline, forwardedCount: forwardedCount, hasEntityKeyboard: self.hasEntityKeyboard, emojiViewProvider: self.emojiViewProvider, send: { [weak self] in
var messageEffect: MessageEffect?
if let selectedEffect = self?.controllerNode.selectedMessageEffect {
messageEffect = MessageEffect(id: selectedEffect.id)
}
self?.sendMessage(.generic, messageEffect)
self?.sendMessage(.generic, nil)
self?.dismiss(cancel: false)
}, sendSilently: { [weak self] in
var messageEffect: MessageEffect?
if let selectedEffect = self?.controllerNode.selectedMessageEffect {
messageEffect = MessageEffect(id: selectedEffect.id)
}
self?.sendMessage(.silently, messageEffect)
self?.sendMessage(.silently, nil)
self?.dismiss(cancel: false)
}, sendWhenOnline: { [weak self] in
var messageEffect: MessageEffect?
if let selectedEffect = self?.controllerNode.selectedMessageEffect {
messageEffect = MessageEffect(id: selectedEffect.id)
}
self?.sendMessage(.whenOnline, messageEffect)
self?.sendMessage(.whenOnline, nil)
self?.dismiss(cancel: false)
}, schedule: !canSchedule ? nil : { [weak self] in
var messageEffect: MessageEffect?
if let selectedEffect = self?.controllerNode.selectedMessageEffect {
messageEffect = MessageEffect(id: selectedEffect.id)
}
self?.schedule(messageEffect)
self?.schedule(nil)
self?.dismiss(cancel: false)
}, cancel: { [weak self] in
self?.dismiss(cancel: true)
@ -185,13 +169,14 @@ public func makeChatSendMessageActionSheetController(
sourceSendButton: ASDisplayNode,
textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview? = nil,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)? = nil,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode? = nil,
attachment: Bool = false,
canSendWhenOnline: Bool,
completion: @escaping () -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
reactionItems: [ReactionItem]? = nil,
availableMessageEffects: AvailableMessageEffects? = nil,
isPremium: Bool = false
@ -228,6 +213,7 @@ public func makeChatSendMessageActionSheetController(
sourceSendButton: sourceSendButton,
textInputView: textInputView,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: mediaCaptionIsAbove,
emojiViewProvider: emojiViewProvider,
wallpaperBackgroundNode: wallpaperBackgroundNode,
attachment: attachment,

View file

@ -63,13 +63,14 @@ final class ChatSendMessageContextScreenComponent: Component {
let sourceSendButton: ASDisplayNode
let textInputView: UITextView
let mediaPreview: ChatSendMessageContextScreenMediaPreview?
let mediaCaptionIsAbove: (Bool, (Bool) -> Void)?
let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
let wallpaperBackgroundNode: WallpaperBackgroundNode?
let attachment: Bool
let canSendWhenOnline: Bool
let completion: () -> Void
let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void
let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void
let reactionItems: [ReactionItem]?
let availableMessageEffects: AvailableMessageEffects?
let isPremium: Bool
@ -85,13 +86,14 @@ final class ChatSendMessageContextScreenComponent: Component {
sourceSendButton: ASDisplayNode,
textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode?,
attachment: Bool,
canSendWhenOnline: Bool,
completion: @escaping () -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
reactionItems: [ReactionItem]?,
availableMessageEffects: AvailableMessageEffects?,
isPremium: Bool
@ -106,6 +108,7 @@ final class ChatSendMessageContextScreenComponent: Component {
self.sourceSendButton = sourceSendButton
self.textInputView = textInputView
self.mediaPreview = mediaPreview
self.mediaCaptionIsAbove = mediaCaptionIsAbove
self.emojiViewProvider = emojiViewProvider
self.wallpaperBackgroundNode = wallpaperBackgroundNode
self.attachment = attachment
@ -160,6 +163,8 @@ final class ChatSendMessageContextScreenComponent: Component {
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private var mediaCaptionIsAbove: Bool = false
private let messageEffectDisposable = MetaDisposable()
private var selectedMessageEffect: AvailableMessageEffects.MessageEffect?
private var standaloneReactionAnimation: AnimatedStickerNode?
@ -217,7 +222,13 @@ final class ChatSendMessageContextScreenComponent: Component {
return
}
self.animateOutToEmpty = true
component.sendMessage(.generic, self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.sendMessage(.generic, sendParameters)
self.environment?.controller()?.dismiss()
}
@ -260,6 +271,18 @@ final class ChatSendMessageContextScreenComponent: Component {
self.isUpdating = false
}
let environment = environment[EnvironmentType.self].value
var transition = transition
var transitionIsImmediate = transition.animation.isImmediate
if case let .curve(duration, _) = transition.animation, duration == 0.0 {
transitionIsImmediate = true
}
if transitionIsImmediate, let previousEnvironment = self.environment, previousEnvironment.inputHeight != 0.0, environment.inputHeight != 0.0, previousEnvironment.inputHeight != environment.inputHeight {
transition = .spring(duration: 0.4)
}
let previousAnimationState = self.appliedAnimationState
self.appliedAnimationState = self.presentationAnimationState
@ -273,11 +296,11 @@ final class ChatSendMessageContextScreenComponent: Component {
}
let _ = alphaTransition
let environment = environment[EnvironmentType.self].value
let themeUpdated = environment.theme !== self.environment?.theme
if self.component == nil {
self.mediaCaptionIsAbove = component.mediaCaptionIsAbove?.0 ?? false
component.gesture.externalUpdated = { [weak self] view, location in
guard let self, let actionsStackNode = self.actionsStackNode else {
return
@ -320,6 +343,13 @@ final class ChatSendMessageContextScreenComponent: Component {
)
}
let textString: NSAttributedString
if let attributedText = component.textInputView.attributedText {
textString = attributedText
} else {
textString = NSAttributedString(string: " ", font: Font.regular(17.0), textColor: .black)
}
let sendButton: SendButton
if let current = self.sendButton {
sendButton = current
@ -345,9 +375,121 @@ final class ChatSendMessageContextScreenComponent: Component {
sendButtonScale = 1.0
}
var reminders = false
var isSecret = false
var canSchedule = false
if let peerId = component.peerId {
reminders = peerId == component.context.account.peerId
isSecret = peerId.namespace == Namespaces.Peer.SecretChat
canSchedule = !isSecret
}
if component.isScheduledMessages {
canSchedule = false
}
var items: [ContextMenuItem] = []
if component.mediaCaptionIsAbove != nil, textString.length != 0, case .media = component.mediaPreview?.layoutType {
//TODO:localize
let mediaCaptionIsAbove = self.mediaCaptionIsAbove
items.append(.action(ContextMenuActionItem(
id: AnyHashable("captionPosition"),
text: mediaCaptionIsAbove ? "Move Caption Down" : "Move Caption Up",
icon: { _ in
return nil
}, iconAnimation: ContextMenuActionItem.IconAnimation(
name: !mediaCaptionIsAbove ? "message_preview_sort_above" : "message_preview_sort_below"
), action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.mediaCaptionIsAbove = !self.mediaCaptionIsAbove
component.mediaCaptionIsAbove?.1(self.mediaCaptionIsAbove)
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.35))
}
}
)))
}
if !reminders {
items.append(.action(ContextMenuActionItem(
id: AnyHashable("silent"),
text: environment.strings.Conversation_SendMessage_SendSilently,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.sendMessage(.silently, sendParameters)
self.environment?.controller()?.dismiss()
}
)))
if component.canSendWhenOnline && canSchedule {
items.append(.action(ContextMenuActionItem(
id: AnyHashable("whenOnline"),
text: environment.strings.Conversation_SendMessage_SendWhenOnline,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.sendMessage(.whenOnline, sendParameters)
self.environment?.controller()?.dismiss()
}
)))
}
}
if canSchedule {
items.append(.action(ContextMenuActionItem(
id: AnyHashable("schedule"),
text: reminders ? environment.strings.Conversation_SendMessage_SetReminder: environment.strings.Conversation_SendMessage_ScheduleMessage,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
let sendParameters = ChatSendMessageActionSheetController.SendParameters(
effect: self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.SendParameters.Effect(id: $0.id) }),
textIsAboveMedia: self.mediaCaptionIsAbove
)
component.schedule(sendParameters)
self.environment?.controller()?.dismiss()
}
)))
}
let actionsStackNode: ContextControllerActionsStackNode
if let current = self.actionsStackNode {
actionsStackNode = current
actionsStackNode.replace(item: ContextControllerActionsListStackItem(
id: AnyHashable("items"),
items: items,
reactionItems: nil,
tip: nil,
tipSignal: .single(nil),
dismissed: nil
), animated: !transition.animation.isImmediate)
} else {
actionsStackNode = ContextControllerActionsStackNode(
getController: {
@ -366,69 +508,9 @@ final class ChatSendMessageContextScreenComponent: Component {
)
actionsStackNode.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
var reminders = false
var isSecret = false
var canSchedule = false
if let peerId = component.peerId {
reminders = peerId == component.context.account.peerId
isSecret = peerId.namespace == Namespaces.Peer.SecretChat
canSchedule = !isSecret
}
if component.isScheduledMessages {
canSchedule = false
}
var items: [ContextMenuItem] = []
if !reminders {
items.append(.action(ContextMenuActionItem(
text: environment.strings.Conversation_SendMessage_SendSilently,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
component.sendMessage(.silently, self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
self.environment?.controller()?.dismiss()
}
)))
if component.canSendWhenOnline && canSchedule {
items.append(.action(ContextMenuActionItem(
text: environment.strings.Conversation_SendMessage_SendWhenOnline,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
component.sendMessage(.whenOnline, self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
self.environment?.controller()?.dismiss()
}
)))
}
}
if canSchedule {
items.append(.action(ContextMenuActionItem(
text: reminders ? environment.strings.Conversation_SendMessage_SetReminder: environment.strings.Conversation_SendMessage_ScheduleMessage,
icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, _ in
guard let self, let component = self.component else {
return
}
self.animateOutToEmpty = true
component.schedule(self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) }))
self.environment?.controller()?.dismiss()
}
)))
}
actionsStackNode.push(
item: ContextControllerActionsListStackItem(
id: nil,
id: AnyHashable("items"),
items: items,
reactionItems: nil,
tip: nil,
@ -458,13 +540,6 @@ final class ChatSendMessageContextScreenComponent: Component {
self.addSubview(messageItemView)
}
let textString: NSAttributedString
if let attributedText = component.textInputView.attributedText {
textString = attributedText
} else {
textString = NSAttributedString(string: " ", font: Font.regular(17.0), textColor: .black)
}
let localSourceTextInputViewFrame = convertFrame(component.textInputView.bounds, from: component.textInputView, to: self)
let sourceMessageTextInsets = UIEdgeInsets(top: 7.0, left: 12.0, bottom: 6.0, right: 20.0)
@ -505,6 +580,7 @@ final class ChatSendMessageContextScreenComponent: Component {
sourceTextInputView: component.textInputView as? ChatInputTextView,
emojiViewProvider: component.emojiViewProvider,
sourceMediaPreview: component.mediaPreview,
mediaCaptionIsAbove: self.mediaCaptionIsAbove,
textInsets: messageTextInsets,
explicitBackgroundSize: explicitMessageBackgroundSize,
maxTextWidth: localSourceTextInputViewFrame.width,
@ -910,12 +986,19 @@ final class ChatSendMessageContextScreenComponent: Component {
Transition.immediate.setScale(view: actionsStackNode.view, scale: 1.0)
actionsStackNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0)
messageItemView.animateIn(transition: transition)
messageItemView.animateIn(
sourceTextInputView: component.textInputView as? ChatInputTextView,
transition: transition
)
case .animatedOut:
transition.setAlpha(view: actionsStackNode.view, alpha: 0.0)
transition.setScale(view: actionsStackNode.view, scale: 0.001)
messageItemView.animateOut(toEmpty: self.animateOutToEmpty, transition: transition)
messageItemView.animateOut(
sourceTextInputView: component.textInputView as? ChatInputTextView,
toEmpty: self.animateOutToEmpty,
transition: transition
)
}
} else {
switch self.presentationAnimationState {
@ -1093,13 +1176,14 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
sourceSendButton: ASDisplayNode,
textInputView: UITextView,
mediaPreview: ChatSendMessageContextScreenMediaPreview?,
mediaCaptionIsAbove: (Bool, (Bool) -> Void)?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
wallpaperBackgroundNode: WallpaperBackgroundNode?,
attachment: Bool,
canSendWhenOnline: Bool,
completion: @escaping () -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void,
sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
reactionItems: [ReactionItem]?,
availableMessageEffects: AvailableMessageEffects?,
isPremium: Bool
@ -1119,6 +1203,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
sourceSendButton: sourceSendButton,
textInputView: textInputView,
mediaPreview: mediaPreview,
mediaCaptionIsAbove: mediaCaptionIsAbove,
emojiViewProvider: emojiViewProvider,
wallpaperBackgroundNode: wallpaperBackgroundNode,
attachment: attachment,

View file

@ -155,6 +155,7 @@ final class MessageItemView: UIView {
private var chatTheme: ChatPresentationThemeData?
private var currentSize: CGSize?
private var currentMediaCaptionIsAbove: Bool = false
override init(frame: CGRect) {
self.backgroundWallpaperNode = ChatMessageBubbleBackdrop()
@ -162,6 +163,7 @@ final class MessageItemView: UIView {
self.backgroundNode.backdropNode = self.backgroundWallpaperNode
self.textClippingContainer = UIView()
self.textClippingContainer.layer.anchorPoint = CGPoint()
self.textClippingContainer.clipsToBounds = true
super.init(frame: frame)
@ -178,13 +180,20 @@ final class MessageItemView: UIView {
preconditionFailure()
}
func animateIn(transition: Transition) {
func animateIn(
sourceTextInputView: ChatInputTextView?,
transition: Transition
) {
if let mediaPreview = self.mediaPreview {
mediaPreview.animateIn(transition: transition)
}
}
func animateOut(toEmpty: Bool, transition: Transition) {
func animateOut(
sourceTextInputView: ChatInputTextView?,
toEmpty: Bool,
transition: Transition
) {
if let mediaPreview = self.mediaPreview {
if toEmpty {
mediaPreview.animateOutOnSend(transition: transition)
@ -202,6 +211,7 @@ final class MessageItemView: UIView {
sourceTextInputView: ChatInputTextView?,
emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?,
sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?,
mediaCaptionIsAbove: Bool,
textInsets: UIEdgeInsets,
explicitBackgroundSize: CGSize?,
maxTextWidth: CGFloat,
@ -255,6 +265,18 @@ final class MessageItemView: UIView {
backgroundNode: backgroundNode
)
self.backgroundNode.setType(
type: .outgoing(.None),
highlighted: false,
graphics: themeGraphics,
maskMode: true,
hasWallpaper: true,
transition: transition.containedViewLayoutTransition,
backgroundNode: backgroundNode
)
let alphaTransition: Transition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
if let sourceMediaPreview {
let mediaPreviewClippingView: UIView
if let current = self.mediaPreviewClippingView {
@ -281,7 +303,7 @@ final class MessageItemView: UIView {
let mediaPreviewSize = sourceMediaPreview.update(containerSize: containerSize, transition: transition)
var backgroundSize = CGSize(width: mediaPreviewSize.width, height: mediaPreviewSize.height)
let mediaPreviewFrame: CGRect
var mediaPreviewFrame: CGRect
switch sourceMediaPreview.layoutType {
case .message, .media:
backgroundSize.width += 7.0
@ -290,8 +312,140 @@ final class MessageItemView: UIView {
mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize)
}
let backgroundAlpha: CGFloat
switch sourceMediaPreview.layoutType {
case .media:
if textString.length != 0 {
backgroundAlpha = explicitBackgroundSize != nil ? 0.0 : 1.0
} else {
backgroundAlpha = 0.0
}
case .message, .videoMessage:
backgroundAlpha = 0.0
}
var backgroundFrame = mediaPreviewFrame.insetBy(dx: -2.0, dy: -2.0)
backgroundFrame.size.width += 6.0
if textString.length != 0, case .media = sourceMediaPreview.layoutType {
let textNode: ChatInputTextNode
if let current = self.textNode {
textNode = current
} else {
textNode = ChatInputTextNode(disableTiling: true)
textNode.textView.isScrollEnabled = false
textNode.isUserInteractionEnabled = false
self.textNode = textNode
self.textClippingContainer.addSubview(textNode.view)
if let sourceTextInputView {
var textContainerInset = sourceTextInputView.defaultTextContainerInset
textContainerInset.right = 0.0
textNode.textView.defaultTextContainerInset = textContainerInset
}
let messageAttributedText = NSMutableAttributedString(attributedString: textString)
textNode.attributedText = messageAttributedText
}
let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor
let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle
if let sourceTextInputView, let textTheme = sourceTextInputView.theme {
switch textTheme.quote.lineStyle {
case .solid:
mappedLineStyle = .solid(color: mainColor)
case .doubleDashed:
mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear)
case .tripleDashed:
mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear)
}
} else {
mappedLineStyle = .solid(color: mainColor)
}
textNode.textView.theme = ChatInputTextView.Theme(
quote: ChatInputTextView.Theme.Quote(
background: mainColor.withMultipliedAlpha(0.1),
foreground: mainColor,
lineStyle: mappedLineStyle,
codeBackground: mainColor.withMultipliedAlpha(0.1),
codeForeground: mainColor
)
)
let maxTextWidth = mediaPreviewFrame.width
let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0)
let currentRightInset: CGFloat = 0.0
let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset)
textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight))
let textBoundingRect = textNode.textView.currentTextBoundingRect().integral
let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral
let textWidth = textBoundingRect.width
let textSize = CGSize(width: textWidth, height: textHeight)
var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom)
let effectInset: CGFloat = 12.0
if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset {
if lastLineBoundingRect != textBoundingRect {
positionedTextSize.height += 11.0
} else {
positionedTextSize.width += effectInset
}
}
let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom)
positionedTextSize.height = min(positionedTextSize.height, maxTextHeight)
let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom)
var textFrame = CGRect(origin: CGPoint(x: textInsets.left - 6.0, y: backgroundFrame.height - 4.0 + textInsets.top), size: positionedTextSize)
if mediaCaptionIsAbove {
textFrame.origin.y = 5.0
}
backgroundFrame.size.height += textSize.height + 2.0
if mediaCaptionIsAbove {
mediaPreviewFrame.origin.y += textSize.height + 2.0
}
let backgroundSize = explicitBackgroundSize ?? size
let previousSize = self.currentSize
self.currentSize = backgroundFrame.size
let _ = previousSize
let textClippingContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 1.0, y: backgroundFrame.minY + 1.0), size: CGSize(width: backgroundFrame.width - 1.0 - 7.0, height: backgroundFrame.height - 1.0 - 1.0))
var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size)
if explicitBackgroundSize != nil, let sourceTextInputView {
textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y
} else {
textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0
textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y)
}
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.origin)
transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds)
alphaTransition.setAlpha(view: textNode.view, alpha: backgroundAlpha)
transition.setFrame(view: textNode.view, frame: CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight)))
self.updateTextContents()
}
transition.setFrame(view: sourceMediaPreview.view, frame: mediaPreviewFrame)
transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
alphaTransition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha)
self.backgroundWallpaperNode.updateFrame(backgroundFrame, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.backgroundNode.view, frame: backgroundFrame)
alphaTransition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha)
self.backgroundNode.updateLayout(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition)
if let effectIcon = self.effectIcon, let effectIconSize {
if let effectIconView = effectIcon.view {
var animateIn = false
@ -367,7 +521,7 @@ final class MessageItemView: UIView {
}
}
return backgroundSize
return backgroundFrame.size
} else {
let textNode: ChatInputTextNode
if let current = self.textNode {
@ -384,7 +538,6 @@ final class MessageItemView: UIView {
}
let messageAttributedText = NSMutableAttributedString(attributedString: textString)
//messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length))
textNode.attributedText = messageAttributedText
}
@ -446,16 +599,6 @@ final class MessageItemView: UIView {
let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize)
self.backgroundNode.setType(
type: .outgoing(.None),
highlighted: false,
graphics: themeGraphics,
maskMode: true,
hasWallpaper: true,
transition: transition.containedViewLayoutTransition,
backgroundNode: backgroundNode
)
let backgroundSize = explicitBackgroundSize ?? size
let previousSize = self.currentSize
@ -471,7 +614,7 @@ final class MessageItemView: UIView {
textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y)
}
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center)
transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.origin)
transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds)
textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight))

View file

@ -538,6 +538,17 @@ private final class CreatePollContext: AttachmentMediaPickerContext {
return .single(nil)
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
var hasCaption: Bool {
return false
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}
@ -549,10 +560,10 @@ private final class CreatePollContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func mainButtonAction() {

View file

@ -451,7 +451,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
reactionContextNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: .animated(duration: 0.25, curve: .easeInOut))
if !reactionContextNode.isExpanded && reactionContextNode.canBeExpanded {
if topOverscroll > 30.0 && self.scroller.isDragging {
if topOverscroll > 30.0 && self.scroller.isTracking {
self.scroller.panGestureRecognizer.state = .cancelled
reactionContextNode.expand()
} else {

View file

@ -393,6 +393,17 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
return .single(nil)
}
var hasCaption: Bool {
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}
@ -404,10 +415,10 @@ private final class LocationPickerContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func mainButtonAction() {

View file

@ -33,14 +33,23 @@ final class MediaPickerInteraction {
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
let openDraft: (MediaEditorDraft, UIImage?) -> Void
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void
let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void
let dismissInput: () -> Void
let selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
var captionIsAboveMedia: Bool = false {
didSet {
if self.captionIsAboveMedia != oldValue {
self.captionIsAboveMediaValue.set(self.captionIsAboveMedia)
}
}
}
let captionIsAboveMediaValue = ValuePromise<Bool>(false)
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
self.downloadManager = downloadManager
self.openMedia = openMedia
self.openSelectedMedia = openSelectedMedia
@ -200,7 +209,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
public var presentFilePicker: () -> Void = {}
private var completed = false
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in }
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in }
public var requestAttachmentMenuExpansion: () -> Void = { }
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
@ -1218,12 +1227,24 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, messageEffect: ChatSendMessageActionSheetController.MessageEffect?, completion: @escaping () -> Void) {
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, parameters: ChatSendMessageActionSheetController.SendParameters?, completion: @escaping () -> Void) {
guard let controller = self.controller, !controller.completed else {
return
}
controller.dismissAllTooltips()
var parameters = parameters
if parameters == nil {
var textIsAboveMedia = false
if let interaction = controller.interaction {
textIsAboveMedia = interaction.captionIsAboveMedia
}
parameters = ChatSendMessageActionSheetController.SendParameters(
effect: nil,
textIsAboveMedia: textIsAboveMedia
)
}
var hasHeic = false
let allItems = controller.interaction?.selectionState?.selectedItems() ?? []
for item in allItems {
@ -1246,7 +1267,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
return
}
controller.completed = true
controller.legacyCompletion(signals, silently, scheduleTime, messageEffect, { [weak self] identifier in
controller.legacyCompletion(signals, silently, scheduleTime, parameters, { [weak self] identifier in
return !asFile ? self?.getItemSnapshot(identifier) : nil
}, { [weak self] in
completion()
@ -1959,18 +1980,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} else {
return false
}
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, messageEffect, completion in
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, parameters, completion in
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing {
strongSelf.isDismissing = true
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, messageEffect: messageEffect, completion: completion)
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
}
}, schedule: { [weak self] messageEffect in
}, schedule: { [weak self] parameters in
if let strongSelf = self {
strongSelf.presentSchedulePicker(false, { [weak self] time in
self?.interaction?.sendSelected(nil, false, time, true, messageEffect, {})
self?.interaction?.sendSelected(nil, false, time, true, parameters, {})
})
}
}, dismissInput: { [weak self] in
@ -2419,10 +2440,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
}
var isCaptionAboveMediaAvailable: Signal<Bool, NoError> = .single(false)
if let mediaPickerContext = self.mediaPickerContext {
isCaptionAboveMediaAvailable = .single(mediaPickerContext.hasCaption)
}
let items: Signal<ContextController.Items, NoError> = self.groupedPromise.get()
let items: Signal<ContextController.Items, NoError> = combineLatest(
self.groupedPromise.get(),
isCaptionAboveMediaAvailable
)
|> deliverOnMainQueue
|> map { [weak self] grouped -> ContextController.Items in
|> map { [weak self] grouped, isCaptionAboveMediaAvailable -> ContextController.Items in
var items: [ContextMenuItem] = []
if !hasSpoilers {
items.append(.action(ContextMenuActionItem(text: selectionCount > 1 ? strings.Attachment_SendAsFiles : strings.Attachment_SendAsFile, icon: { theme in
@ -2430,7 +2459,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}, action: { [weak self] _, f in
f(.default)
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, messageEffect: nil, completion: {})
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {})
})))
}
if selectionCount > 1 {
@ -2458,25 +2487,48 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self?.groupedValue = false
})))
}
if isSpoilerAvailable {
if isSpoilerAvailable || (selectionCount > 0 && isCaptionAboveMediaAvailable) {
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
name: "anim_spoiler",
loop: true
), action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
if isCaptionAboveMediaAvailable {
var mediaCaptionIsAbove = false
if let interaction = self?.interaction {
mediaCaptionIsAbove = interaction.captionIsAboveMedia
}
if let selectionContext = strongSelf.interaction?.selectionState, let editingContext = strongSelf.interaction?.editingState {
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
editingContext.setSpoiler(hasGeneric, for: item)
//TODO:localize
items.append(.action(ContextMenuActionItem(text: mediaCaptionIsAbove ? "Move Caption Down" : "Move Caption Up", icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
name: !mediaCaptionIsAbove ? "message_preview_sort_above" : "message_preview_sort_below"
), action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
}
})))
if let interaction = strongSelf.interaction {
interaction.captionIsAboveMedia = !interaction.captionIsAboveMedia
}
})))
}
if isSpoilerAvailable {
items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
name: "anim_spoiler",
loop: true
), action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
if let selectionContext = strongSelf.interaction?.selectionState, let editingContext = strongSelf.interaction?.editingState {
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
editingContext.setSpoiler(hasGeneric, for: item)
}
}
})))
}
}
return ContextController.Items(content: .list(items))
}
@ -2534,7 +2586,18 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
var caption: Signal<NSAttributedString?, NoError> {
return Signal { [weak self] subscriber in
let disposable = self?.controller?.interaction?.editingState.forcedCaption().start(next: { caption in
guard let self else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
guard let caption = self.controller?.interaction?.editingState.forcedCaption() else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
let disposable = caption.start(next: { caption in
if let caption = caption as? NSAttributedString {
subscriber.putNext(caption)
} else {
@ -2546,6 +2609,34 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
}
}
}
var hasCaption: Bool {
guard let isForcedCaption = self.controller?.interaction?.editingState.isForcedCaption() else {
return false
}
return isForcedCaption
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return Signal { [weak self] subscriber in
guard let interaction = self?.controller?.interaction else {
subscriber.putNext(false)
subscriber.putCompletion()
return EmptyDisposable
}
let disposable = interaction.captionIsAboveMediaValue.get().start(next: { value in
subscriber.putNext(value)
}, error: { _ in }, completed: { })
return ActionDisposable {
disposable.dispose()
}
}
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
self.controller?.interaction?.captionIsAboveMedia = captionIsAboveMedia
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
@ -2563,12 +2654,12 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, messageEffect, {})
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, parameters, {})
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.interaction?.schedule(messageEffect)
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.controller?.interaction?.schedule(parameters)
}
func mainButtonAction() {

View file

@ -976,7 +976,14 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: wallpaper, bubbleCorners: bubbleCorners)
var groupIndex = 0
var isFirstGroup = true
for (items, groupSize) in groupLayouts {
if isFirstGroup {
isFirstGroup = false
} else {
contentHeight += spacing
}
var groupRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top + contentHeight), size: groupSize)
if !self.isExternalPreview {
groupRect.origin.x = insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0)
@ -1005,7 +1012,6 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
}
var isFirstGroup = true
for (item, itemRect, itemPosition) in items {
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
var corners: CACornerMask = []
@ -1039,11 +1045,6 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
}
}
if isFirstGroup {
isFirstGroup = false
} else {
contentHeight += spacing
}
contentHeight += groupSize.height
contentWidth = max(contentWidth, groupSize.width)
groupIndex += 1

View file

@ -99,6 +99,8 @@
- (void)removeChangeListener:(id<MTContextChangeListener> _Nonnull)changeListener;
- (void)setDiscoverBackupAddressListSignal:(MTSignal * _Nonnull)signal;
- (void)setExternalRequestVerification:(MTSignal * _Nonnull (^ _Nonnull)(NSString * _Nonnull))externalRequestVerification;
- (MTSignal * _Nullable)performExternalRequestVerificationWithNonce:(NSString * _Nonnull)nonce;
- (NSTimeInterval)globalTime;
- (NSTimeInterval)globalTimeDifference;

View file

@ -1,5 +1,3 @@
#import <Foundation/Foundation.h>
@interface MTRequestContext : NSObject

View file

@ -1,7 +1,18 @@
#import <Foundation/Foundation.h>
@protocol MTDisposable;
@interface MTRequestPendingVerificationData : NSObject
@property (nonatomic, strong, readonly) NSString *nonce;
@property (nonatomic, strong) NSString *secret;
@property (nonatomic) bool isResolved;
@property (nonatomic, strong) id<MTDisposable> disposable;
- (instancetype)initWithNonce:(NSString *)nonce;
@end
@interface MTRequestErrorContext : NSObject
@property (nonatomic) CFAbsoluteTime minimalExecuteTime;
@ -13,4 +24,6 @@
@property (nonatomic) bool waitingForTokenExport;
@property (nonatomic, strong) id waitingForRequestToComplete;
@property (nonatomic, strong) MTRequestPendingVerificationData *pendingVerificationData;
@end

View file

@ -181,6 +181,7 @@ static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKeyInteger(NSNumber *key
NSMutableArray<MTWeakContextChangeListener *> *_changeListeners;
MTSignal *_discoverBackupAddressListSignal;
MTSignal * _Nonnull (^ _Nullable _externalRequestVerification)(NSString * _Nonnull);
NSMutableDictionary *_discoverDatacenterAddressActions;
NSMutableDictionary<NSNumber *, MTDatacenterAuthAction *> *_datacenterAuthActions;
@ -526,6 +527,25 @@ static void copyKeychainDictionaryKey(NSString * _Nonnull group, NSString * _Non
} synchronous:true];
}
- (void)setExternalRequestVerification:(MTSignal * _Nonnull (^ _Nonnull)(NSString * _Nonnull))externalRequestVerification {
[[MTContext contextQueue] dispatchOnQueue:^ {
_externalRequestVerification = externalRequestVerification;
} synchronous:true];
}
- (MTSignal * _Nullable)performExternalRequestVerificationWithNonce:(NSString * _Nonnull)nonce {
__block MTSignal * _Nonnull (^ _Nullable externalRequestVerification)(NSString * _Nonnull);
[[MTContext contextQueue] dispatchOnQueue:^ {
externalRequestVerification = _externalRequestVerification;
} synchronous:true];
if (externalRequestVerification != nil) {
return externalRequestVerification(nonce);
} else {
return [MTSignal single:nil];
}
}
- (NSTimeInterval)globalTime
{
return [[NSDate date] timeIntervalSince1970] + [self globalTimeDifference];

View file

@ -1,5 +1,17 @@
#import <MtProtoKit/MTRequestErrorContext.h>
@implementation MTRequestPendingVerificationData
- (instancetype)initWithNonce:(NSString *)nonce {
self = [super init];
if (self != nil) {
_nonce = nonce;
}
return self;
}
@end
@implementation MTRequestErrorContext
@end

View file

@ -17,6 +17,7 @@
#import <MtProtoKit/MTDropResponseContext.h>
#import <MtProtoKit/MTApiEnvironment.h>
#import <MtProtoKit/MTDatacenterAuthInfo.h>
#import <MtProtoKit/MTSignal.h>
#import "MTBuffer.h"
#import "MTInternalMessageParser.h"
@ -24,6 +25,26 @@
#import <MtProtoKit/MTRpcError.h>
#import "MTDropRpcResultMessage.h"
@interface MTRequestVerificationData : NSObject
@property (nonatomic, strong, readonly) NSString *nonce;
@property (nonatomic, strong, readonly) NSString *secret;
@end
@implementation MTRequestVerificationData
- (instancetype)initWithNonce:(NSString *)nonce secret:(NSString *)secret {
self = [super init];
if (self != nil) {
_nonce = nonce;
_secret = secret;
}
return self;
}
@end
@interface MTRequestMessageService ()
{
MTContext *_context;
@ -381,8 +402,8 @@
}
}
- (NSData *)decorateRequestData:(MTRequest *)request initializeApi:(bool)initializeApi unresolvedDependencyOnRequestInternalId:(__autoreleasing id *)unresolvedDependencyOnRequestInternalId decoratedDebugDescription:(__autoreleasing NSString **)decoratedDebugDescription
{
- (NSData *)decorateRequestData:(MTRequest *)request initializeApi:(bool)initializeApi requestVerificationData:(MTRequestVerificationData *)requestVerificationData unresolvedDependencyOnRequestInternalId:(__autoreleasing id *)unresolvedDependencyOnRequestInternalId decoratedDebugDescription:(__autoreleasing NSString **)decoratedDebugDescription
{
NSData *currentData = request.payload;
NSString *debugDescription = @"";
@ -397,8 +418,6 @@
// invokeWithLayer
[buffer appendInt32:(int32_t)0xda9b0d0d];
[buffer appendInt32:(int32_t)[_serialization currentLayer]];
//initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
int32_t flags = 0;
if (_apiEnvironment.socksProxySettings.secret != nil) {
@ -482,6 +501,19 @@
}
}
if (requestVerificationData != nil) {
MTBuffer *buffer = [[MTBuffer alloc] init];
[buffer appendInt32:(int32_t)0xdae54f8];
[buffer appendTLString:requestVerificationData.nonce];
[buffer appendTLString:requestVerificationData.secret];
[buffer appendBytes:currentData.bytes length:currentData.length];
currentData = buffer.data;
debugDescription = [debugDescription stringByAppendingFormat:@", apnsSecret(%@, %@)", requestVerificationData.nonce, requestVerificationData.secret];
}
if (decoratedDebugDescription != nil) {
*decoratedDebugDescription = debugDescription;
}
@ -511,6 +543,11 @@
if (request.errorContext.waitingForTokenExport) {
continue;
}
if (request.errorContext.pendingVerificationData != nil) {
if (!request.errorContext.pendingVerificationData.isResolved) {
continue;
}
}
bool foundDependency = false;
for (MTRequest *anotherRequest in _requests) {
@ -542,7 +579,16 @@
messageSeqNo = request.requestContext.messageSeqNo;
}
NSData *decoratedRequestData = [self decorateRequestData:request initializeApi:requestsWillInitializeApi unresolvedDependencyOnRequestInternalId:&autoreleasingUnresolvedDependencyOnRequestInternalId decoratedDebugDescription:&decoratedDebugDescription];
MTRequestVerificationData *requestVerificationData = nil;
if (request.errorContext != nil) {
if (request.errorContext.pendingVerificationData != nil) {
if (request.errorContext.pendingVerificationData.isResolved) {
requestVerificationData = [[MTRequestVerificationData alloc] initWithNonce:request.errorContext.pendingVerificationData.nonce secret:request.errorContext.pendingVerificationData.secret];
}
}
}
NSData *decoratedRequestData = [self decorateRequestData:request initializeApi:requestsWillInitializeApi requestVerificationData:requestVerificationData unresolvedDependencyOnRequestInternalId:&autoreleasingUnresolvedDependencyOnRequestInternalId decoratedDebugDescription:&decoratedDebugDescription];
MTOutgoingMessage *outgoingMessage = [[MTOutgoingMessage alloc] initWithData:decoratedRequestData metadata:request.metadata additionalDebugDescription:decoratedDebugDescription shortMetadata:request.shortMetadata messageId:messageId messageSeqNo:messageSeqNo];
outgoingMessage.needsQuickAck = request.acknowledgementReceived != nil;
@ -875,6 +921,34 @@
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
}];
restartRequest = true;
} else if (rpcError.errorCode == 403 && [rpcError.errorDescription rangeOfString:@"APNS_VERIFY_CHECK_"].location != NSNotFound) {
if (request.errorContext == nil) {
request.errorContext = [[MTRequestErrorContext alloc] init];
}
NSString *nonce = [rpcError.errorDescription substringFromIndex:[@"APNS_VERIFY_CHECK_" length]];
request.errorContext.pendingVerificationData = [[MTRequestPendingVerificationData alloc] initWithNonce:nonce];
__weak MTRequestMessageService *weakSelf = self;
MTQueue *queue = _queue;
id requestId = request.internalId;
request.errorContext.pendingVerificationData.disposable = [[_context performExternalRequestVerificationWithNonce:nonce] startWithNext:^(id result) {
[queue dispatchOnQueue:^{
__strong MTRequestMessageService *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
for (MTRequest *request in strongSelf->_requests) {
if (request.internalId == requestId) {
request.errorContext.pendingVerificationData.secret = result;
request.errorContext.pendingVerificationData.isResolved = true;
}
}
[strongSelf->_mtProto requestTransportTransaction];
}];
}];
restartRequest = true;
} else if (rpcError.errorCode == 406) {
if (_didReceiveSoftAuthResetError) {

View file

@ -1415,7 +1415,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie
(parentController.navigationController as? NavigationController)?.pushViewController(contactsController)
let _ = (contactsController.result
|> deliverOnMainQueue).start(next: { result in
if let (peers, _, _, _, _) = result, let peer = peers.first {
if let (peers, _, _, _, _, _) = result, let peer = peers.first {
let dataSignal: Signal<(EnginePeer?, DeviceContactStableId?), NoError>
switch peer {
case let .peer(contact, _, _):

View file

@ -1943,11 +1943,13 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
}
}
} else {
let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> = .single((FoundStickerSets(), false)) |> then(
let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> = .single((FoundStickerSets(), false))
|> then(
context.engine.stickers.searchEmojiSetsRemotely(query: query) |> map {
($0, true)
}
)
let localPacksSignal: Signal<FoundStickerSets, NoError> = context.engine.stickers.searchEmojiSets(query: query)
resultSignal = signal
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
@ -1999,9 +2001,10 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1),
context.engine.stickers.availableReactions() |> take(1),
hasPremium |> take(1),
remotePacksSignal
remotePacksSignal,
localPacksSignal
)
|> map { view, availableReactions, hasPremium, foundPacks -> [EmojiPagerContentComponent.ItemGroup] in
|> map { view, availableReactions, hasPremium, foundPacks, foundLocalPacks -> [EmojiPagerContentComponent.ItemGroup] in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
@ -2072,10 +2075,21 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
items: items
))
for (collectionId, info, _, _) in foundPacks.sets.infos {
var combinedSets: FoundStickerSets
combinedSets = foundLocalPacks
combinedSets = combinedSets.merge(with: foundPacks.sets)
var existingCollectionIds = Set<ItemCollectionId>()
for (collectionId, info, _, _) in combinedSets.infos {
if !existingCollectionIds.contains(collectionId) {
existingCollectionIds.insert(collectionId)
} else {
continue
}
if let info = info as? StickerPackCollectionInfo {
var topItems: [StickerPackItem] = []
for e in foundPacks.sets.entries {
for e in combinedSets.entries {
if let item = e.item as? StickerPackItem {
if e.index.collectionId == collectionId {
topItems.append(item)

View file

@ -434,13 +434,14 @@ public struct NetworkInitializationArguments {
public let voipMaxLayer: Int32
public let voipVersions: [CallSessionManagerImplementationVersion]
public let appData: Signal<Data?, NoError>
public let externalRequestVerificationStream: Signal<[String: String], NoError>
public let autolockDeadine: Signal<Int32?, NoError>
public let encryptionProvider: EncryptionProvider
public let deviceModelName:String?
public let useBetaFeatures: Bool
public let isICloudEnabled: Bool
public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool, isICloudEnabled: Bool) {
public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal<Data?, NoError>, externalRequestVerificationStream: Signal<[String: String], NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool, isICloudEnabled: Bool) {
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
@ -448,6 +449,7 @@ public struct NetworkInitializationArguments {
self.voipMaxLayer = voipMaxLayer
self.voipVersions = voipVersions
self.appData = appData
self.externalRequestVerificationStream = externalRequestVerificationStream
self.autolockDeadine = autolockDeadine
self.encryptionProvider = encryptionProvider
self.deviceModelName = deviceModelName
@ -573,6 +575,25 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
if !supplementary {
context.setDiscoverBackupAddressListSignal(MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber, mainDatacenterId: datacenterId))
let externalRequestVerificationStream = arguments.externalRequestVerificationStream
context.setExternalRequestVerification({ nonce in
return MTSignal(generator: { subscriber in
let disposable = (externalRequestVerificationStream
|> map { dict -> String? in
return dict[nonce]
}
|> filter { $0 != nil }
|> take(1)
|> timeout(15.0, queue: .mainQueue(), alternate: .single("APNS_PUSH_TIMEOUT"))).start(next: { secret in
subscriber?.putNext(secret)
subscriber?.putCompletion()
})
return MTBlockDisposable(block: {
disposable.dispose()
})
})
})
}
/*#if DEBUG

View file

@ -1175,7 +1175,7 @@ public final class PeerStoryListContext {
public var hasCache: Bool
public var allEntityFiles: [MediaId: TelegramMediaFile]
init(
public init(
peerReference: PeerReference?,
items: [EngineStoryItem],
pinnedIds: Set<Int32>,

View file

@ -323,7 +323,7 @@ func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion)
case let .paymentFormStars(_, _, _, title, description, photo, invoice, _):
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion)
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion)
}
}
|> mapError { _ -> BotPaymentFormRequestError in }

View file

@ -151,7 +151,7 @@ private final class StarsContextImpl {
guard let self, let state = self._state, let balance = balances[peerId] else {
return
}
self._state = StarsContext.State(balance: balance, transactions: state.transactions)
self._state = StarsContext.State(balance: balance, transactions: state.transactions, canLoadMore: nextOffset != nil, isLoading: false)
self.load()
})
}
@ -169,7 +169,7 @@ private final class StarsContextImpl {
|> deliverOnMainQueue).start(next: { [weak self] status in
if let self {
if let status {
self._state = StarsContext.State(balance: status.balance, transactions: status.transactions)
self._state = StarsContext.State(balance: status.balance, transactions: status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)
self.nextOffset = status.nextOffset
self.loadMore()
@ -180,17 +180,30 @@ private final class StarsContextImpl {
}))
}
func add(balance: Int64) {
if var state = self._state {
var transactions = state.transactions
transactions.insert(.init(id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore), at: 0)
state.balance = state.balance + balance
self._state = state
}
}
func loadMore() {
assert(Queue.mainQueue().isCurrent())
guard let currentState = self._state, let nextOffset = self.nextOffset else {
return
}
self._state?.isLoading = true
self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nextOffset)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let self {
if let status {
self._state = StarsContext.State(balance: status.balance, transactions: currentState.transactions + status.transactions)
self._state = StarsContext.State(balance: status.balance, transactions: currentState.transactions + status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)
self.nextOffset = status.nextOffset
} else {
self.nextOffset = nil
@ -246,12 +259,15 @@ public final class StarsContext {
}
}
public let balance: Int64
public let transactions: [Transaction]
init(balance: Int64, transactions: [Transaction]) {
public var balance: Int64
public var transactions: [Transaction]
public var canLoadMore: Bool
public var isLoading: Bool
init(balance: Int64, transactions: [Transaction], canLoadMore: Bool, isLoading: Bool) {
self.balance = balance
self.transactions = transactions
self.canLoadMore = canLoadMore
self.isLoading = isLoading
}
public static func == (lhs: State, rhs: State) -> Bool {
@ -261,6 +277,12 @@ public final class StarsContext {
if lhs.transactions != rhs.transactions {
return false
}
if lhs.canLoadMore != rhs.canLoadMore {
return false
}
if lhs.isLoading != rhs.isLoading {
return false
}
return true
}
}
@ -279,6 +301,18 @@ public final class StarsContext {
}
}
public func add(balance: Int64) {
self.impl.with {
$0.add(balance: balance)
}
}
public func loadMore() {
self.impl.with {
$0.loadMore()
}
}
init(account: Account, peerId: EnginePeer.Id) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return StarsContextImpl(account: account, peerId: peerId)

View file

@ -12,8 +12,8 @@ public func chatMessageBubbleImageContentCorners(relativeContentPosition positio
case let .linear(top, _):
switch top {
case .Neighbour:
topLeftCorner = .Corner(mergedWithAnotherContentRadius)
topRightCorner = .Corner(mergedWithAnotherContentRadius)
topLeftCorner = .Corner(normalRadius)
topRightCorner = .Corner(normalRadius)
case .BubbleNeighbour:
topLeftCorner = .Corner(mergedRadius)
topRightCorner = .Corner(mergedRadius)

View file

@ -252,7 +252,11 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
messageWithCaptionToAdd = (message, itemAttributes)
skipText = true
} else {
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
if let _ = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) {
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: 0)
} else {
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
}
needReactions = false
}
} else {
@ -297,8 +301,15 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
}
if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false
if let _ = messageWithCaptionToAdd.attributes.first(where: { $0 is InvertMediaMessageAttribute }) {
if result.isEmpty {
needReactions = false
}
result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0)
} else {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false
}
}
if let additionalContent = item.additionalContent {
@ -2764,7 +2775,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: contentNodesHeight)
contentNodeFramesPropertiesAndApply.append((contentNodeFrame, properties, true, apply))
if mosaicIndex == mosaicRange.upperBound - 1 {
if i == mosaicRange.upperBound - 1 {
contentNodesHeight += size.height
totalContentNodesHeight += size.height

View file

@ -254,6 +254,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
let statusType: ChatMessageDateAndStatusType?
if case .customChatContents = item.associatedData.subject {
statusType = nil
} else if item.message.timestamp == 0 {
statusType = nil
} else {
switch position {
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
@ -274,6 +276,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
let messageEffect = item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects)
if let statusType = statusType {
var isReplyThread = false
if case .replyThread = item.chatLocation {
@ -295,7 +298,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
reactionPeers: dateReactionsAndPeers.peers,
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
messageEffect: messageEffect,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
@ -447,11 +450,18 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else if messageEffect != nil {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.playMessageEffect(item.message)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}

View file

@ -14,6 +14,180 @@ import WallpaperBackgroundNode
import AudioWaveform
import ChatMessageItemView
public final class ChatSendContactMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview {
private let context: AccountContext
private let presentationData: PresentationData
private let wallpaperBackgroundNode: WallpaperBackgroundNode?
private let contactPeers: [ContactListPeer]
private var messageNodes: [ListViewItemNode]?
private let messagesContainer: UIView
public var isReady: Signal<Bool, NoError> {
return .single(true)
}
public var view: UIView {
return self
}
public var globalClippingRect: CGRect? {
return nil
}
public var layoutType: ChatSendMessageContextScreenMediaPreviewLayoutType {
return .message
}
public init(context: AccountContext, presentationData: PresentationData, wallpaperBackgroundNode: WallpaperBackgroundNode?, contactPeers: [ContactListPeer]) {
self.context = context
self.presentationData = presentationData
self.wallpaperBackgroundNode = wallpaperBackgroundNode
self.contactPeers = contactPeers
self.messagesContainer = UIView()
self.messagesContainer.layer.sublayerTransform = CATransform3DMakeScale(-1.0, -1.0, 1.0)
super.init(frame: CGRect())
self.addSubview(self.messagesContainer)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
public func animateIn(transition: Transition) {
transition.animateAlpha(view: self.messagesContainer, from: 0.0, to: 1.0)
transition.animateScale(view: self.messagesContainer, from: 0.001, to: 1.0)
}
public func animateOut(transition: Transition) {
transition.setAlpha(view: self.messagesContainer, alpha: 0.0)
transition.setScale(view: self.messagesContainer, scale: 0.001)
}
public func animateOutOnSend(transition: Transition) {
transition.setAlpha(view: self.messagesContainer, alpha: 0.0)
}
public func update(containerSize: CGSize, transition: Transition) -> CGSize {
var contactsMedia: [TelegramMediaContact] = []
for peer in self.contactPeers {
switch peer {
case let .peer(contact, _, _):
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
continue
}
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
let phone = contactData.basicData.phoneNumbers[0].value
contactsMedia.append(TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil))
case let .deviceContact(_, basicData):
guard !basicData.phoneNumbers.isEmpty else {
continue
}
let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
let phone = contactData.basicData.phoneNumbers[0].value
contactsMedia.append(TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil))
}
}
var items: [ListViewItem] = []
for contactMedia in contactsMedia {
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [contactMedia], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let item = self.context.sharedContext.makeChatMessagePreviewItem(
context: self.context,
messages: [message],
theme: presentationData.theme,
strings: presentationData.strings,
wallpaper: presentationData.chatWallpaper,
fontSize: presentationData.chatFontSize,
chatBubbleCorners: presentationData.chatBubbleCorners,
dateTimeFormat: presentationData.dateTimeFormat,
nameOrder: presentationData.nameDisplayOrder,
forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: .Local),
tapMessage: nil,
clickThroughMessage: nil,
backgroundNode: self.wallpaperBackgroundNode,
availableReactions: nil,
accountPeer: nil,
isCentered: false,
isPreview: true,
isStandalone: true
)
items.append(item)
}
let params = ListViewItemLayoutParams(width: containerSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: containerSize.height)
if let messageNodes = self.messageNodes {
for i in 0 ..< items.count {
let itemNode = messageNodes[i]
items[i].updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: CGPoint(x: itemNode.frame.minX, y: itemNode.frame.minY), size: CGSize(width: containerSize.width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
}
} else {
var messageNodes: [ListViewItemNode] = []
for i in 0 ..< items.count {
var itemNode: ListViewItemNode?
items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in
itemNode = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode!.isUserInteractionEnabled = false
messageNodes.append(itemNode!)
self.messagesContainer.addSubview(itemNode!.view)
}
self.messageNodes = messageNodes
}
var contentSize = CGSize()
for messageNode in self.messageNodes ?? [] {
guard let messageNode = messageNode as? ChatMessageItemView else {
continue
}
if !contentSize.height.isZero {
contentSize.height += 2.0
}
let contentFrame = messageNode.contentFrame()
contentSize.height += contentFrame.height
contentSize.width = max(contentSize.width, contentFrame.width)
}
var contentOffsetY: CGFloat = 0.0
for messageNode in self.messageNodes ?? [] {
guard let messageNode = messageNode as? ChatMessageItemView else {
continue
}
if !contentOffsetY.isZero {
contentOffsetY += 2.0
}
let contentFrame = messageNode.contentFrame()
messageNode.frame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width - contentFrame.width + 6.0, y: 3.0 + contentOffsetY), size: CGSize(width: contentFrame.width, height: contentFrame.height))
contentOffsetY += contentFrame.height
}
self.messagesContainer.frame = CGRect(origin: CGPoint(x: 6.0, y: 3.0), size: CGSize(width: contentSize.width, height: contentSize.height))
return CGSize(width: contentSize.width - 4.0, height: contentSize.height + 2.0)
}
}
public final class ChatSendAudioMessageContextPreview: UIView, ChatSendMessageContextScreenMediaPreview {
private let context: AccountContext
private let presentationData: PresentationData

View file

@ -6,6 +6,7 @@
#include <LottieCpp/CurveVertex.h>
#include <LottieCpp/PathElement.h>
#include <LottieCpp/CGPath.h>
#include <LottieCpp/ShapeAttributes.h>
#include <vector>
@ -126,7 +127,7 @@ public:
public:
BezierPath(std::shared_ptr<BezierPathContents> contents);
private:
public:
std::shared_ptr<BezierPathContents> _contents;
};
@ -143,6 +144,9 @@ public:
CGRect bezierPathsBoundingBox(std::vector<BezierPath> const &paths);
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector<BezierPath> const &paths);
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, BezierPath const &path);
std::vector<BezierPath> trimBezierPaths(std::vector<BezierPath> &sourcePaths, double start, double end, double offset, TrimType type);
}

View file

@ -13,6 +13,82 @@
namespace lottie {
class ProcessedRenderTreeNodeData {
public:
struct LayerParams {
CGRect _bounds;
Vector2D _position;
CATransform3D _transform;
double _opacity;
bool _masksToBounds;
bool _isHidden;
LayerParams(
CGRect bounds_,
Vector2D position_,
CATransform3D transform_,
double opacity_,
bool masksToBounds_,
bool isHidden_
) :
_bounds(bounds_),
_position(position_),
_transform(transform_),
_opacity(opacity_),
_masksToBounds(masksToBounds_),
_isHidden(isHidden_) {
}
CGRect bounds() const {
return _bounds;
}
Vector2D position() const {
return _position;
}
CATransform3D transform() const {
return _transform;
}
double opacity() const {
return _opacity;
}
bool masksToBounds() const {
return _masksToBounds;
}
bool isHidden() const {
return _isHidden;
}
};
ProcessedRenderTreeNodeData() :
isValid(false),
layer(
CGRect(0.0, 0.0, 0.0, 0.0),
Vector2D(0.0, 0.0),
CATransform3D::identity(),
1.0,
false,
false
),
globalRect(CGRect(0.0, 0.0, 0.0, 0.0)),
globalTransform(CATransform3D::identity()),
drawContentDescendants(false),
isInvertedMatte(false) {
}
bool isValid = false;
LayerParams layer;
CGRect globalRect;
CATransform3D globalTransform;
int drawContentDescendants;
bool isInvertedMatte;
};
class RenderableItem {
public:
enum class Type {
@ -345,8 +421,14 @@ public:
public:
bool isGroup = false;
CATransform3D transform = CATransform3D::identity();
double alpha = 0.0;
std::optional<TrimParams> trimParams;
std::optional<BezierPath> path;
CGRect pathBoundingBox = CGRect(0.0, 0.0, 0.0, 0.0);
std::vector<std::shared_ptr<RenderTreeNodeContentShadingVariant>> shadings;
std::vector<std::shared_ptr<RenderTreeNodeContentItem>> subItems;
ProcessedRenderTreeNodeData renderData;
};
class RenderTreeNodeContentShadingVariant {
@ -362,86 +444,6 @@ public:
size_t subItemLimit = 0;
};
class ProcessedRenderTreeNodeData {
public:
struct LayerParams {
CGRect _bounds;
Vector2D _position;
CATransform3D _transform;
double _opacity;
bool _masksToBounds;
bool _isHidden;
LayerParams(
CGRect bounds_,
Vector2D position_,
CATransform3D transform_,
double opacity_,
bool masksToBounds_,
bool isHidden_
) :
_bounds(bounds_),
_position(position_),
_transform(transform_),
_opacity(opacity_),
_masksToBounds(masksToBounds_),
_isHidden(isHidden_) {
}
CGRect bounds() const {
return _bounds;
}
Vector2D position() const {
return _position;
}
CATransform3D transform() const {
return _transform;
}
double opacity() const {
return _opacity;
}
bool masksToBounds() const {
return _masksToBounds;
}
bool isHidden() const {
return _isHidden;
}
};
ProcessedRenderTreeNodeData() :
isValid(false),
layer(
CGRect(0.0, 0.0, 0.0, 0.0),
Vector2D(0.0, 0.0),
CATransform3D::identity(),
1.0,
false,
false
),
globalRect(CGRect(0.0, 0.0, 0.0, 0.0)),
localRect(CGRect(0.0, 0.0, 0.0, 0.0)),
globalTransform(CATransform3D::identity()),
drawsContent(false),
drawContentDescendants(false),
isInvertedMatte(false) {
}
bool isValid = false;
LayerParams layer;
CGRect globalRect;
CGRect localRect;
CATransform3D globalTransform;
bool drawsContent;
int drawContentDescendants;
bool isInvertedMatte;
};
class RenderTreeNode {
public:
RenderTreeNode(

View file

@ -31,6 +31,25 @@ enum class GradientType: int {
Radial = 2
};
enum class TrimType: int {
Simultaneously = 1,
Individually = 2
};
struct TrimParams {
double start = 0.0;
double end = 0.0;
double offset = 0.0;
TrimType type = TrimType::Simultaneously;
TrimParams(double start_, double end_, double offset_, TrimType type_) :
start(start_),
end(end_),
offset(offset_),
type(type_) {
}
};
}
#endif

View file

@ -82,7 +82,7 @@ public:
return _contentsLayer;
}
void displayWithFrame(double frame, bool forceUpdates) {
void displayWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
_transformNode->updateTree(frame, forceUpdates);
bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame);
@ -93,14 +93,14 @@ public:
/// Only update contents if current time is within the layers time bounds.
if (layerVisible) {
displayContentsWithFrame(frame, forceUpdates);
displayContentsWithFrame(frame, forceUpdates, boundingBoxContext);
if (_maskLayer) {
_maskLayer->updateWithFrame(frame, forceUpdates);
}
}
}
virtual void displayContentsWithFrame(double frame, bool forceUpdates) {
virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
/// To be overridden by subclass
}
@ -151,11 +151,11 @@ public:
return _timeStretch;
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() {
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) {
return nullptr;
}
virtual void updateRenderTree() {
virtual void updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) {
}
public:

View file

@ -86,7 +86,7 @@ public:
return result;
}
virtual void displayContentsWithFrame(double frame, bool forceUpdates) override {
virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override {
double localFrame = 0.0;
if (_remappingNode) {
_remappingNode->update(frame);
@ -96,11 +96,11 @@ public:
}
for (const auto &animationLayer : _animationLayers) {
animationLayer->displayWithFrame(localFrame, forceUpdates);
animationLayer->displayWithFrame(localFrame, forceUpdates, boundingBoxContext);
}
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() override {
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!_renderTreeNode) {
_contentsTreeNode = std::make_shared<RenderTreeNode>(
CGRect(0.0, 0.0, 0.0, 0.0),
@ -120,7 +120,7 @@ public:
std::shared_ptr<RenderTreeNode> maskNode;
bool invertMask = false;
if (_matteLayer) {
maskNode = _matteLayer->renderTreeNode();
maskNode = _matteLayer->renderTreeNode(boundingBoxContext);
if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) {
invertMask = true;
}
@ -148,7 +148,7 @@ public:
}
}
if (found) {
auto node = animationLayer->renderTreeNode();
auto node = animationLayer->renderTreeNode(boundingBoxContext);
if (node) {
renderTreeSubnodes.push_back(node);
}
@ -177,9 +177,9 @@ public:
return _renderTreeNode;
}
virtual void updateRenderTree() override {
virtual void updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (_matteLayer) {
_matteLayer->updateRenderTree();
_matteLayer->updateRenderTree(boundingBoxContext);
}
for (const auto &animationLayer : _animationLayers) {
@ -191,7 +191,7 @@ public:
}
}
if (found) {
animationLayer->updateRenderTree();
animationLayer->updateRenderTree(boundingBoxContext);
}
}

View file

@ -437,27 +437,10 @@ public:
std::shared_ptr<RenderTreeNodeContentItem::Stroke> _stroke;
};
struct TrimParams {
double start = 0.0;
double end = 0.0;
double offset = 0.0;
TrimType type = TrimType::Simultaneously;
size_t subItemLimit = 0;
TrimParams(double start_, double end_, double offset_, TrimType type_, size_t subItemLimit_) :
start(start_),
end(end_),
offset(offset_),
type(type_),
subItemLimit(subItemLimit_) {
}
};
class TrimParamsOutput {
public:
TrimParamsOutput(Trim const &trim, size_t subItemLimit) :
TrimParamsOutput(Trim const &trim) :
type(trim.trimType),
subItemLimit(subItemLimit),
start(trim.start.keyframes),
end(trim.end.keyframes),
offset(trim.offset.keyframes) {
@ -485,12 +468,11 @@ public:
double resolvedOffset = fmod(offsetValue, 360.0) / 360.0;
return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type, subItemLimit);
return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type);
}
private:
TrimType type;
size_t subItemLimit = 0;
KeyframeInterpolator<Vector1D> start;
double startValue = 0.0;
@ -524,8 +506,9 @@ public:
}
virtual ~PathOutput() = default;
virtual void update(AnimationFrameTime frameTime) = 0;
virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) = 0;
virtual BezierPath const *currentPath() = 0;
virtual CGRect const &currentPathBounds() = 0;
};
class StaticPathOutput : public PathOutput {
@ -534,15 +517,26 @@ public:
resolvedPath(path) {
}
virtual void update(AnimationFrameTime frameTime) override {
virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!isPathBoundsResolved) {
resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath);
isPathBoundsResolved = true;
}
}
virtual BezierPath const *currentPath() override {
return &resolvedPath;
}
virtual CGRect const &currentPathBounds() override {
return resolvedPathBounds;
}
private:
BezierPath resolvedPath;
bool isPathBoundsResolved = false;
CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0);
};
class ShapePathOutput : public PathOutput {
@ -551,9 +545,10 @@ public:
path(shape.path.keyframes) {
}
virtual void update(AnimationFrameTime frameTime) override {
virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!hasValidData || path.hasUpdate(frameTime)) {
path.update(frameTime, resolvedPath);
resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath);
}
hasValidData = true;
@ -563,12 +558,17 @@ public:
return &resolvedPath;
}
virtual CGRect const &currentPathBounds() override {
return resolvedPathBounds;
}
private:
bool hasValidData = false;
BezierPathKeyframeInterpolator path;
BezierPath resolvedPath;
CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0);
};
class RectanglePathOutput : public PathOutput {
@ -580,7 +580,7 @@ public:
cornerRadius(rectangle.cornerRadius.keyframes) {
}
virtual void update(AnimationFrameTime frameTime) override {
virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override {
bool hasUpdates = false;
if (!hasValidData || position.hasUpdate(frameTime)) {
@ -597,7 +597,8 @@ public:
}
if (hasUpdates) {
resolvedPath = makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction);
ValueInterpolator<BezierPath>::setInplace(makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction), resolvedPath);
resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath);
}
hasValidData = true;
@ -607,6 +608,10 @@ public:
return &resolvedPath;
}
virtual CGRect const &currentPathBounds() override {
return resolvedPathBounds;
}
private:
bool hasValidData = false;
@ -622,6 +627,7 @@ public:
double cornerRadiusValue = 0.0;
BezierPath resolvedPath;
CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0);
};
class EllipsePathOutput : public PathOutput {
@ -632,7 +638,7 @@ public:
size(ellipse.size.keyframes) {
}
virtual void update(AnimationFrameTime frameTime) override {
virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override {
bool hasUpdates = false;
if (!hasValidData || position.hasUpdate(frameTime)) {
@ -645,7 +651,8 @@ public:
}
if (hasUpdates) {
resolvedPath = makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction);
ValueInterpolator<BezierPath>::setInplace(makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction), resolvedPath);
resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath);
}
hasValidData = true;
@ -655,6 +662,10 @@ public:
return &resolvedPath;
}
virtual CGRect const &currentPathBounds() override {
return resolvedPathBounds;
}
private:
bool hasValidData = false;
@ -667,6 +678,7 @@ public:
Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0);
BezierPath resolvedPath;
CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0);
};
class StarPathOutput : public PathOutput {
@ -691,7 +703,7 @@ public:
}
}
virtual void update(AnimationFrameTime frameTime) override {
virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override {
bool hasUpdates = false;
if (!hasValidData || position.hasUpdate(frameTime)) {
@ -732,7 +744,8 @@ public:
}
if (hasUpdates) {
resolvedPath = makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction);
ValueInterpolator<BezierPath>::setInplace(makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction), resolvedPath);
resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath);
}
hasValidData = true;
@ -742,6 +755,10 @@ public:
return &resolvedPath;
}
virtual CGRect const &currentPathBounds() override {
return resolvedPathBounds;
}
private:
bool hasValidData = false;
@ -769,6 +786,7 @@ public:
double pointsValue = 0.0;
BezierPath resolvedPath;
CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0);
};
class TransformOutput {
@ -909,10 +927,6 @@ public:
transform = std::move(transform_);
}
std::shared_ptr<RenderTreeNode> const &renderTree() const {
return _renderTree;
}
private:
std::unique_ptr<PathOutput> path;
std::unique_ptr<TransformOutput> transform;
@ -920,14 +934,15 @@ public:
std::vector<ShadingVariant> shadings;
std::vector<std::shared_ptr<TrimParamsOutput>> trims;
public:
std::vector<std::shared_ptr<ContentItem>> subItems;
std::shared_ptr<RenderTreeNode> _renderTree;
std::shared_ptr<RenderTreeNodeContentItem> _contentItem;
private:
std::vector<TransformedPath> collectPaths(size_t subItemLimit, CATransform3D const &parentTransform, bool skipApplyTransform) {
std::vector<TransformedPath> mappedPaths;
//TODO:remove skipApplyTransform
CATransform3D effectiveTransform = parentTransform;
if (!skipApplyTransform && isGroup && transform) {
effectiveTransform = transform->transform() * effectiveTransform;
@ -935,8 +950,8 @@ public:
size_t maxSubitem = std::min(subItems.size(), subItemLimit);
if (path) {
mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform);
if (_contentItem->path) {
mappedPaths.emplace_back(_contentItem->path.value(), effectiveTransform);
}
for (size_t i = 0; i < maxSubitem; i++) {
@ -988,25 +1003,17 @@ public:
}
void addTrim(Trim const &trim) {
trims.push_back(std::make_shared<TrimParamsOutput>(trim, subItems.size()));
trims.push_back(std::make_shared<TrimParamsOutput>(trim));
}
public:
void initializeRenderChildren() {
_renderTree = std::make_shared<RenderTreeNode>(
CGRect(0.0, 0.0, 0.0, 0.0),
Vector2D(0.0, 0.0),
CATransform3D::identity(),
1.0,
false,
false,
std::vector<std::shared_ptr<RenderTreeNode>>(),
nullptr,
false
);
_contentItem = std::make_shared<RenderTreeNodeContentItem>();
_contentItem->isGroup = isGroup;
_renderTree->_contentItem = std::make_shared<RenderTreeNodeContentItem>();
_renderTree->_contentItem->isGroup = isGroup;
if (path) {
_contentItem->path = *path->currentPath();
}
if (!shadings.empty()) {
for (int i = 0; i < shadings.size(); i++) {
@ -1025,42 +1032,28 @@ public:
}
itemShadingVariant->subItemLimit = shadingVariant.subItemLimit;
_renderTree->_contentItem->shadings.push_back(itemShadingVariant);
_contentItem->shadings.push_back(itemShadingVariant);
}
}
if (isGroup && !subItems.empty()) {
std::vector<std::shared_ptr<RenderTreeNode>> subItemNodes;
for (int i = (int)subItems.size() - 1; i >= 0; i--) {
subItems[i]->initializeRenderChildren();
subItemNodes.push_back(subItems[i]->_renderTree);
//_renderTree->_contentItem->subItems.push_back(subItems[i]->_renderTree->_contentItem);
}
if (!subItemNodes.empty()) {
_renderTree->_subnodes.push_back(std::make_shared<RenderTreeNode>(
CGRect(0.0, 0.0, 0.0, 0.0),
Vector2D(0.0, 0.0),
CATransform3D::identity(),
1.0,
false,
false,
subItemNodes,
nullptr,
false
));
for (const auto &subItem : subItems) {
subItem->initializeRenderChildren();
_contentItem->subItems.push_back(subItem->_contentItem);
}
}
}
public:
void updateFrame(AnimationFrameTime frameTime) {
void updateFrame(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) {
if (transform) {
transform->update(frameTime);
}
if (path) {
path->update(frameTime);
path->update(frameTime, boundingBoxContext);
_contentItem->pathBoundingBox = path->currentPathBounds();
}
for (const auto &trim : trims) {
trim->update(frameTime);
@ -1076,19 +1069,37 @@ public:
}
for (const auto &subItem : subItems) {
subItem->updateFrame(frameTime);
subItem->updateFrame(frameTime, boundingBoxContext);
}
}
void updateChildren(std::optional<TrimParams> parentTrim) {
bool hasTrims() {
if (!trims.empty()) {
return true;
}
for (const auto &subItem : subItems) {
if (subItem->hasTrims()) {
return true;
}
}
return false;
}
void updateContents(std::optional<TrimParams> parentTrim) {
CATransform3D containerTransform = CATransform3D::identity();
double containerOpacity = 1.0;
if (transform) {
containerTransform = transform->transform();
containerOpacity = transform->opacity();
}
_renderTree->_transform = containerTransform;
_renderTree->_alpha = containerOpacity;
_contentItem->transform = containerTransform;
_contentItem->alpha = containerOpacity;
if (!trims.empty()) {
_contentItem->trimParams = trims[0]->trimParams();
}
for (int i = 0; i < shadings.size(); i++) {
const auto &shadingVariant = shadings[i];
@ -1097,12 +1108,6 @@ public:
continue;
}
CompoundBezierPath compoundPath;
auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true);
for (const auto &path : paths) {
compoundPath.appendPath(path.path.copyUsingTransform(path.transform));
}
//std::optional<TrimParams> currentTrim = parentTrim;
//TODO:investigate
/*if (!trims.empty()) {
@ -1110,29 +1115,48 @@ public:
}*/
if (parentTrim) {
CompoundBezierPath compoundPath;
auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true);
for (const auto &path : paths) {
compoundPath.appendPath(path.path.copyUsingTransform(path.transform));
}
compoundPath = trimCompoundPath(compoundPath, parentTrim->start, parentTrim->end, parentTrim->offset, parentTrim->type);
std::vector<BezierPath> resultPaths;
for (const auto &path : compoundPath.paths) {
resultPaths.push_back(path);
}
_contentItem->shadings[i]->explicitPath = resultPaths;
} else {
if (hasTrims()) {
CompoundBezierPath compoundPath;
auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true);
for (const auto &path : paths) {
compoundPath.appendPath(path.path.copyUsingTransform(path.transform));
}
std::vector<BezierPath> resultPaths;
for (const auto &path : compoundPath.paths) {
resultPaths.push_back(path);
}
_contentItem->shadings[i]->explicitPath = resultPaths;
} else {
_contentItem->shadings[i]->explicitPath = std::nullopt;
}
}
std::vector<BezierPath> resultPaths;
for (const auto &path : compoundPath.paths) {
resultPaths.push_back(path);
}
_renderTree->_contentItem->shadings[i]->explicitPath = resultPaths;
}
if (isGroup && !subItems.empty()) {
for (int i = (int)subItems.size() - 1; i >= 0; i--) {
std::optional<TrimParams> childTrim = parentTrim;
for (const auto &trim : trims) {
if (i < (int)trim->trimParams().subItemLimit) {
//TODO:allow combination
//assert(!parentTrim);
childTrim = trim->trimParams();
}
//TODO:allow combination
//assert(!parentTrim);
childTrim = trim->trimParams();
}
subItems[i]->updateChildren(childTrim);
subItems[i]->updateContents(childTrim);
}
}
}
@ -1229,7 +1253,14 @@ private:
case ShapeType::Trim: {
Trim const &trim = *((Trim *)item.get());
itemTree->addTrim(trim);
auto groupItem = std::make_shared<ContentItem>();
groupItem->isGroup = true;
for (const auto &subItem : itemTree->subItems) {
groupItem->addSubItem(subItem);
}
groupItem->addTrim(trim);
itemTree->subItems.clear();
itemTree->addSubItem(groupItem);
break;
}
@ -1302,29 +1333,43 @@ CompositionLayer(solidLayer, Vector2D::Zero()) {
_contentTree = std::make_shared<ShapeLayerPresentationTree>(solidLayer);
}
void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) {
void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
_frameTime = frame;
_frameTimeInitialized = true;
_contentTree->itemTree->updateFrame(_frameTime);
_contentTree->itemTree->updateChildren(std::nullopt);
_contentTree->itemTree->updateFrame(_frameTime, boundingBoxContext);
_contentTree->itemTree->updateContents(std::nullopt);
}
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) {
if (!_frameTimeInitialized) {
_frameTime = 0.0;
_frameTimeInitialized = true;
_contentTree->itemTree->updateFrame(_frameTime);
_contentTree->itemTree->updateChildren(std::nullopt);
_contentTree->itemTree->updateFrame(_frameTime, boundingBoxContext);
_contentTree->itemTree->updateContents(std::nullopt);
}
if (!_renderTreeNode) {
_contentRenderTreeNode = std::make_shared<RenderTreeNode>(
CGRect(0.0, 0.0, 0.0, 0.0),
Vector2D(0.0, 0.0),
CATransform3D::identity(),
1.0,
false,
false,
std::vector<std::shared_ptr<RenderTreeNode>>(),
nullptr,
false
);
_contentRenderTreeNode->_contentItem = _contentTree->itemTree->_contentItem;
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
subnodes.push_back(_contentTree->itemTree->renderTree());
//subnodes.push_back(_contentTree->itemTree->renderTree());
subnodes.push_back(_contentRenderTreeNode);
std::shared_ptr<RenderTreeNode> maskNode;
bool invertMask = false;
if (_matteLayer) {
maskNode = _matteLayer->renderTreeNode();
maskNode = _matteLayer->renderTreeNode(boundingBoxContext);
if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) {
invertMask = true;
}
@ -1346,26 +1391,17 @@ std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
return _renderTreeNode;
}
void ShapeCompositionLayer::updateRenderTree() {
void ShapeCompositionLayer::updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) {
if (_matteLayer) {
_matteLayer->updateRenderTree();
_matteLayer->updateRenderTree(boundingBoxContext);
}
_contentTree->itemTree->renderTree()->_bounds = _contentsLayer->bounds();
_contentTree->itemTree->renderTree()->_position = _contentsLayer->position();
_contentTree->itemTree->renderTree()->_transform = _contentsLayer->transform();
_contentTree->itemTree->renderTree()->_alpha = _contentsLayer->opacity();
_contentTree->itemTree->renderTree()->_masksToBounds = _contentsLayer->masksToBounds();
_contentTree->itemTree->renderTree()->_isHidden = _contentsLayer->isHidden();
assert(position() == Vector2D::Zero());
assert(transform().isIdentity());
assert(opacity() == 1.0);
assert(!masksToBounds());
assert(!isHidden());
assert(_contentsLayer->bounds() == CGRect(0.0, 0.0, 0.0, 0.0));
assert(_contentsLayer->position() == Vector2D::Zero());
assert(!_contentsLayer->masksToBounds());
_contentRenderTreeNode->_bounds = _contentsLayer->bounds();
_contentRenderTreeNode->_position = _contentsLayer->position();
_contentRenderTreeNode->_transform = _contentsLayer->transform();
_contentRenderTreeNode->_alpha = _contentsLayer->opacity();
_contentRenderTreeNode->_masksToBounds = _contentsLayer->masksToBounds();
_contentRenderTreeNode->_isHidden = _contentsLayer->isHidden();
_renderTreeNode->_bounds = bounds();
_renderTreeNode->_position = position();

View file

@ -16,9 +16,9 @@ public:
ShapeCompositionLayer(std::shared_ptr<ShapeLayerModel> const &shapeLayer);
ShapeCompositionLayer(std::shared_ptr<SolidLayerModel> const &solidLayer);
virtual void displayContentsWithFrame(double frame, bool forceUpdates) override;
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() override;
virtual void updateRenderTree() override;
virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override;
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override;
virtual void updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) override;
private:
std::shared_ptr<ShapeLayerPresentationTree> _contentTree;
@ -27,6 +27,7 @@ private:
bool _frameTimeInitialized = false;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
std::shared_ptr<RenderTreeNode> _contentRenderTreeNode;
};
}

View file

@ -42,7 +42,7 @@ public:
_fontProvider = fontProvider;
}
virtual void displayContentsWithFrame(double frame, bool forceUpdates) override {
virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!_textDocument) {
return;
}

View file

@ -103,7 +103,7 @@ public:
newFrame = floor(newFrame);
}
for (const auto &layer : _animationLayers) {
layer->displayWithFrame(newFrame, false);
layer->displayWithFrame(newFrame, false, _boundingBoxContext);
}
}
@ -118,7 +118,7 @@ public:
/// Forces the view to update its drawing.
void forceDisplayUpdate() {
for (const auto &layer : _animationLayers) {
layer->displayWithFrame(currentFrame(), true);
layer->displayWithFrame(currentFrame(), true, _boundingBoxContext);
}
}
@ -193,7 +193,7 @@ public:
_currentFrame = currentFrame;
for (size_t i = 0; i < _animationLayers.size(); i++) {
_animationLayers[i]->displayWithFrame(_currentFrame, false);
_animationLayers[i]->displayWithFrame(_currentFrame, false, _boundingBoxContext);
}
}
@ -230,7 +230,7 @@ public:
}
}
if (found) {
auto node = animationLayer->renderTreeNode();
auto node = animationLayer->renderTreeNode(_boundingBoxContext);
if (node) {
subnodes.push_back(node);
}
@ -264,7 +264,7 @@ public:
}
}
if (found) {
animationLayer->updateRenderTree();
animationLayer->updateRenderTree(_boundingBoxContext);
}
}
}
@ -288,6 +288,8 @@ private:
std::shared_ptr<LayerFontProvider> _layerFontProvider;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
BezierPathsBoundingBoxContext _boundingBoxContext;
};
}

View file

@ -8,11 +8,6 @@
namespace lottie {
enum class TrimType: int {
Simultaneously = 1,
Individually = 2
};
/// An item that defines trim
class Trim: public ShapeItem {
public:

View file

@ -582,6 +582,58 @@ CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, st
return calculateBoundingRectOpt(pointsX, pointsY, pointCount);
}
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, BezierPath const &path) {
int pointCount = 0;
float *pointsX = context.pointsX;
float *pointsY = context.pointsY;
int pointsSize = context.pointsSize;
PathElement const *pathElements = path.elements().data();
int pathElementCount = (int)path.elements().size();
for (int i = 0; i < pathElementCount; i++) {
const auto &element = pathElements[i];
if (pointsSize < pointCount + 1) {
pointsSize = (pointCount + 1) * 2;
pointsX = (float *)realloc(pointsX, pointsSize * 4);
pointsY = (float *)realloc(pointsY, pointsSize * 4);
}
pointsX[pointCount] = (float)element.vertex.point.x;
pointsY[pointCount] = (float)element.vertex.point.y;
pointCount++;
if (i != 0) {
const auto &previousElement = pathElements[i - 1];
if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
} else {
if (pointsSize < pointCount + 1) {
pointsSize = (pointCount + 2) * 2;
pointsX = (float *)realloc(pointsX, pointsSize * 4);
pointsY = (float *)realloc(pointsY, pointsSize * 4);
}
pointsX[pointCount] = (float)previousElement.vertex.outTangent.x;
pointsY[pointCount] = (float)previousElement.vertex.outTangent.y;
pointCount++;
pointsX[pointCount] = (float)element.vertex.inTangent.x;
pointsY[pointCount] = (float)element.vertex.inTangent.y;
pointCount++;
}
}
}
context.pointsX = pointsX;
context.pointsY = pointsY;
context.pointsSize = pointsSize;
if (pointCount == 0) {
return CGRect(0.0, 0.0, 0.0, 0.0);
}
return calculateBoundingRectOpt(pointsX, pointsY, pointCount);
}
CGRect bezierPathsBoundingBox(std::vector<BezierPath> const &paths) {
int pointCount = 0;

View file

@ -65,9 +65,7 @@
result.layer.isHidden = node->renderData.layer._isHidden;
result.globalRect = CGRectMake(node->renderData.globalRect.x, node->renderData.globalRect.y, node->renderData.globalRect.width, node->renderData.globalRect.height);
result.localRect = CGRectMake(node->renderData.localRect.x, node->renderData.localRect.y, node->renderData.localRect.width, node->renderData.localRect.height);
result.globalTransform = lottie::nativeTransform(node->renderData.globalTransform);
result.drawsContent = node->renderData.drawsContent;
result.hasSimpleContents = node->renderData.drawContentDescendants <= 1;
result.drawContentDescendants = node->renderData.drawContentDescendants;
result.isInvertedMatte = node->renderData.isInvertedMatte;

View file

@ -532,7 +532,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
if itemContext.targets.isEmpty {
strongSelf.itemContexts.removeValue(forKey: itemKey)
}
}
}.strict()
}
func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize) -> Bool {
@ -598,7 +598,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
completion(false, true)
}
}
})
}).strict()
}
func loadFirstFrameAsImage(cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (CGImage?) -> Void) -> Disposable {
@ -626,7 +626,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
completion(nil)
}
}
})
}).strict()
}
func setFrameIndex(itemId: String, size: CGSize, frameIndex: Int, placeholder: UIImage) {
@ -729,7 +729,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
return ActionDisposable {
disposable.dispose()
}
}.strict()
}
public func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize) -> Bool {
@ -763,7 +763,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
self.groupContext = groupContext
}
return groupContext.loadFirstFrame(target: target, cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion)
return groupContext.loadFirstFrame(target: target, cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion).strict()
}
public func loadFirstFrameAsImage(cache: AnimationCache, itemId: String, size: CGSize, fetch: ((AnimationCacheFetchOptions) -> Disposable)?, completion: @escaping (CGImage?) -> Void) -> Disposable {
@ -780,7 +780,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
self.groupContext = groupContext
}
return groupContext.loadFirstFrameAsImage(cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion)
return groupContext.loadFirstFrameAsImage(cache: cache, itemId: itemId, size: size, fetch: fetch, completion: completion).strict()
}
public func setFrameIndex(itemId: String, size: CGSize, frameIndex: Int, placeholder: UIImage) {

View file

@ -419,7 +419,7 @@ private final class PeerInfoPendingPane {
}
}
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext)
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext)
paneNode = visualPaneNode
visualPaneNode.openCurrentDate = {
openMediaCalendar()

View file

@ -456,7 +456,6 @@ final class PeerInfoStoryGridScreenComponent: Component {
paneNode = PeerInfoStoryPaneNode(
context: component.context,
peerId: component.peerId,
chatLocation: .peer(id: component.peerId),
contentType: .photoOrVideo,
captureProtected: false,
isSaved: true,

View file

@ -0,0 +1,261 @@
import Foundation
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
import ComponentFlow
import TelegramCore
import PeerInfoVisualMediaPaneNode
import ViewControllerComponent
import ChatListHeaderComponent
import ContextUI
import ChatTitleView
import BottomButtonPanelComponent
import UndoUI
import MoreHeaderButton
import MediaEditorScreen
import SaveToCameraRoll
final class StorySearchGridScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let searchQuery: String
init(
context: AccountContext,
searchQuery: String
) {
self.context = context
self.searchQuery = searchQuery
}
static func ==(lhs: StorySearchGridScreenComponent, rhs: StorySearchGridScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.searchQuery != rhs.searchQuery {
return false
}
return true
}
final class View: UIView {
private var component: StorySearchGridScreenComponent?
private(set) weak var state: EmptyComponentState?
private var environment: EnvironmentType?
private(set) var paneNode: PeerInfoStoryPaneNode?
private var paneStatusDisposable: Disposable?
private(set) var paneStatusText: String?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.paneStatusDisposable?.dispose()
}
func scrollToTop() {
guard let paneNode = self.paneNode else {
return
}
let _ = paneNode.scrollToTop()
}
private var isUpdating = false
func update(component: StorySearchGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component
self.state = state
let sideInset: CGFloat = 14.0
let _ = sideInset
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
if themeUpdated {
self.backgroundColor = environment.theme.list.plainBackgroundColor
}
let bottomInset: CGFloat = environment.safeInsets.bottom
let paneNode: PeerInfoStoryPaneNode
if let current = self.paneNode {
paneNode = current
} else {
paneNode = PeerInfoStoryPaneNode(
context: component.context,
peerId: nil,
searchQuery: component.searchQuery,
contentType: .photoOrVideo,
captureProtected: false,
isSaved: false,
isArchive: false,
isProfileEmbedded: false,
canManageStories: false,
navigationController: { [weak self] in
guard let self else {
return nil
}
return self.environment?.controller()?.navigationController as? NavigationController
},
listContext: nil
)
paneNode.isEmptyUpdated = { [weak self] _ in
guard let self else {
return
}
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
}
self.paneNode = paneNode
self.addSubview(paneNode.view)
self.paneStatusDisposable = (paneNode.status
|> deliverOnMainQueue).start(next: { [weak self] status in
guard let self else {
return
}
if self.paneStatusText != status?.text {
self.paneStatusText = status?.text
(self.environment?.controller() as? StorySearchGridScreen)?.updateTitle()
}
})
}
paneNode.update(
size: availableSize,
topInset: environment.navigationHeight,
sideInset: environment.safeInsets.left,
bottomInset: bottomInset,
deviceMetrics: environment.deviceMetrics,
visibleHeight: availableSize.height,
isScrollingLockedAtTop: false,
expandProgress: 1.0,
navigationHeight: 0.0,
presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }),
synchronous: false,
transition: transition.containedViewLayoutTransition
)
transition.setFrame(view: paneNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public class StorySearchGridScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let searchQuery: String
private var isDismissed: Bool = false
private var titleView: ChatTitleView?
public init(
context: AccountContext,
searchQuery: String
) {
self.context = context
self.searchQuery = searchQuery
super.init(context: context, component: StorySearchGridScreenComponent(
context: context,
searchQuery: searchQuery
), navigationBarAppearance: .default, theme: .default)
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
self.titleView = ChatTitleView(
context: context, theme:
presentationData.theme,
strings: presentationData.strings,
dateTimeFormat: presentationData.dateTimeFormat,
nameDisplayOrder: presentationData.nameDisplayOrder,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer
)
self.titleView?.disableAnimations = true
self.navigationItem.titleView = self.titleView
self.updateTitle()
self.scrollToTop = { [weak self] in
guard let self, let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View else {
return
}
componentView.scrollToTop()
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
func updateTitle() {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
guard let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View, let paneNode = componentView.paneNode else {
return
}
let _ = paneNode
let title: String?
if let paneStatusText = componentView.paneStatusText, !paneStatusText.isEmpty {
title = paneStatusText
} else {
title = nil
}
//TODO:localize
self.titleView?.titleContent = .custom("\(self.searchQuery)", title, false)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.titleView?.layout = layout
}
}
private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ContextReferenceContentNode
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View file

@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
private var customTitle: String?
public var peerSelected: ((EnginePeer, Int64?) -> Void)?
public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)?
public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
private let filter: ChatListNodePeersFilter
private let forumPeerId: EnginePeer.Id?
private let selectForumThreads: Bool

View file

@ -83,7 +83,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)?
var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)?
var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)?
var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
private var presentationData: PresentationData {
didSet {

View file

@ -34,6 +34,17 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext {
return .single(nil)
}
var hasCaption: Bool {
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}
@ -49,10 +60,10 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func mainButtonAction() {

View file

@ -362,6 +362,17 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext {
return .single(nil)
}
var hasCaption: Bool {
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}
@ -377,10 +388,10 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func mainButtonAction() {

View file

@ -385,6 +385,7 @@ public class ShareRootControllerImpl {
voipMaxLayer: 0,
voipVersions: [],
appData: .single(nil),
externalRequestVerificationStream: .never(),
autolockDeadine: .single(nil),
encryptionProvider: OpenSSLEncryptionProvider(),
deviceModelName: nil,

View file

@ -1556,14 +1556,14 @@ final class StoryItemSetContainerSendMessage {
completion(controller, mediaPickerContext)
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
attachmentController?.mediaPickerContext = mediaPickerContext
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in
guard let self, let view else {
return
}
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
}
)
case .file:
@ -1658,7 +1658,7 @@ final class StoryItemSetContainerSendMessage {
}
self.controllerNavigationDisposable.set((contactsController.result
|> deliverOnMainQueue).start(next: { [weak self, weak view] peers in
guard let self, let view, let (peers, _, silent, scheduleTime, text) = peers else {
guard let self, let view, let (peers, _, silent, scheduleTime, text, _) = peers else {
return
}
@ -1875,7 +1875,7 @@ final class StoryItemSetContainerSendMessage {
bannedSendVideos: (Int32, Bool)?,
present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void,
updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void,
completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void
completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void
) {
guard let component = view.component else {
return
@ -2240,14 +2240,14 @@ final class StoryItemSetContainerSendMessage {
present(controller, mediaPickerContext)
},
updateMediaPickerContext: { _ in },
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in
guard let self, let view else {
return
}
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
}
)
}
@ -2569,7 +2569,7 @@ final class StoryItemSetContainerSendMessage {
}
}
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
guard let component = view.component else {
return
}
@ -2620,11 +2620,20 @@ final class StoryItemSetContainerSendMessage {
}
}
}
if let messageEffect {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(EffectMessageAttribute(id: messageEffect.id))
return attributes
if let parameters {
if let effect = parameters.effect {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(EffectMessageAttribute(id: effect.id))
return attributes
}
}
if parameters.textIsAboveMedia {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(InvertMediaMessageAttribute())
return attributes
}
}
}
mappedMessages.append(message)
@ -2909,8 +2918,13 @@ final class StoryItemSetContainerSendMessage {
return
}
if !hashtag.isEmpty {
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true)
navigationController.pushViewController(searchController)
if "".isEmpty {
let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, query: hashtag)
navigationController.pushViewController(searchController)
} else {
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true)
navigationController.pushViewController(searchController)
}
}
}))
}

View file

@ -274,6 +274,15 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
private let firebaseSecretStream = Promise<[String: String]>([:])
private var firebaseRequestVerificationSecrets: [String: String] = [:] {
didSet {
if self.firebaseRequestVerificationSecrets != oldValue {
self.firebaseRequestVerificationSecretStream.set(.single(self.firebaseRequestVerificationSecrets))
}
}
}
private let firebaseRequestVerificationSecretStream = Promise<[String: String]>([:])
private var urlSessions: [URLSession] = []
private func urlSession(identifier: String) -> URLSession {
if let existingSession = self.urlSessions.first(where: { $0.configuration.identifier == identifier }) {
@ -491,7 +500,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
Logger.shared.log("data", "can't deserialize")
}
return data
}, autolockDeadine: autolockDeadine, encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: buildConfig.isICloudEnabled)
}, externalRequestVerificationStream: self.firebaseRequestVerificationSecretStream.get(), autolockDeadine: autolockDeadine, encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: buildConfig.isICloudEnabled)
guard let appGroupUrl = maybeAppGroupUrl else {
self.mainWindow?.presentNative(UIAlertController(title: nil, message: "Error 2", preferredStyle: .alert))
@ -1895,6 +1904,11 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
firebaseSecrets[receipt] = secret
self.firebaseSecrets = firebaseSecrets
}
if let nonce = firebaseDict["verify_nonce"] as? String, let secret = firebaseDict["verify_secret"] as? String {
var firebaseRequestVerificationSecrets = self.firebaseRequestVerificationSecrets
firebaseRequestVerificationSecrets[nonce] = secret
self.firebaseRequestVerificationSecrets = firebaseRequestVerificationSecrets
}
completionHandler(.newData)
return

View file

@ -172,6 +172,17 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext {
return .single(nil)
}
var hasCaption: Bool {
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}
@ -183,10 +194,10 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func mainButtonAction() {

View file

@ -27,8 +27,8 @@ extension ChatControllerImpl {
subjects: subjects,
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
if let strongSelf = self {
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
})
}
},

View file

@ -15,7 +15,7 @@ import ChatControllerInteraction
import ChatSendAudioMessageContextPreview
extension ChatSendMessageEffect {
convenience init(_ effect: ChatSendMessageActionSheetController.MessageEffect) {
convenience init(_ effect: ChatSendMessageActionSheetController.SendParameters.Effect) {
self.init(id: effect.id)
}
}
@ -114,17 +114,17 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
}
selfController.supportedOrientations = previousSupportedOrientations
},
sendMessage: { [weak selfController] mode, messageEffect in
sendMessage: { [weak selfController] mode, parameters in
guard let selfController else {
return
}
switch mode {
case .generic:
selfController.controllerInteraction?.sendCurrentMessage(false, messageEffect.flatMap(ChatSendMessageEffect.init))
selfController.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init))
case .silently:
selfController.controllerInteraction?.sendCurrentMessage(true, messageEffect.flatMap(ChatSendMessageEffect.init))
selfController.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init))
case .whenOnline:
selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: messageEffect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in
selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in
guard let selfController else {
return
}
@ -135,7 +135,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
}
}
},
schedule: { [weak selfController] messageEffect in
schedule: { [weak selfController] effect in
guard let selfController else {
return
}

View file

@ -9119,7 +9119,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!)
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
if let strongSelf = self {
@ -9174,11 +9174,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
completionImpl = nil
}
if let messageEffect {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(EffectMessageAttribute(id: messageEffect.id))
return attributes
if let parameters {
if let effect = parameters.effect {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(EffectMessageAttribute(id: effect.id))
return attributes
}
}
if parameters.textIsAboveMedia {
message = message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.append(InvertMediaMessageAttribute())
return attributes
}
}
}

View file

@ -309,11 +309,11 @@ extension ChatControllerImpl {
completion(controller, mediaPickerContext)
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
attachmentController?.mediaPickerContext = mediaPickerContext
}, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in
}, completion: { [weak self] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in
if !inputText.string.isEmpty {
self?.clearInputText()
}
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
})
case .file:
strongSelf.controllerNavigationDisposable.set(nil)
@ -405,7 +405,7 @@ extension ChatControllerImpl {
completion(contactsController, contactsController.mediaPickerContext)
strongSelf.controllerNavigationDisposable.set((contactsController.result
|> deliverOnMainQueue).startStrict(next: { [weak self] peers in
if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers {
if let strongSelf = self, let (peers, _, silent, scheduleTime, text, parameters) = peers {
var textEnqueueMessage: EnqueueMessage?
if let text = text, text.length > 0 {
var attributes: [MessageAttribute] = []
@ -456,6 +456,17 @@ extension ChatControllerImpl {
enqueueMessages.append(message)
}
}
if !enqueueMessages.isEmpty {
enqueueMessages[enqueueMessages.count - 1] = enqueueMessages[enqueueMessages.count - 1].withUpdatedAttributes { attributes in
var attributes = attributes
if let parameters {
if let effect = parameters.effect {
attributes.append(EffectMessageAttribute(id: effect.id))
}
}
return attributes
}
}
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
} else if let peer = peers.first {
let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError>
@ -471,14 +482,14 @@ extension ChatControllerImpl {
|> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in
var stableId: String?
let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber)
outer: for (id, data) in basicData {
for phoneNumber in data.phoneNumbers {
if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber {
stableId = id
break outer
outer: for (id, data) in basicData {
for phoneNumber in data.phoneNumbers {
if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber {
stableId = id
break outer
}
}
}
}
if let stableId = stableId {
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
@ -498,7 +509,7 @@ extension ChatControllerImpl {
}
}
strongSelf.controllerNavigationDisposable.set((dataSignal
|> deliverOnMainQueue).startStrict(next: { peerAndContactData in
|> deliverOnMainQueue).startStrict(next: { peerAndContactData in
if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 {
if contactData.isPrimitive {
let phone = contactData.basicData.phoneNumbers[0].value
@ -518,7 +529,13 @@ extension ChatControllerImpl {
if let textEnqueueMessage = textEnqueueMessage {
enqueueMessages.append(textEnqueueMessage)
}
enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
var attributes: [MessageAttribute] = []
if let parameters {
if let effect = parameters.effect {
attributes.append(EffectMessageAttribute(id: effect.id))
}
}
enqueueMessages.append(.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
} else {
let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in
@ -1137,7 +1154,7 @@ extension ChatControllerImpl {
self.present(actionSheet, in: .window(.root))
}
func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
var isScheduledMessages = false
if case .scheduledMessages = self.presentationInterfaceState.subject {
isScheduledMessages = true
@ -1211,8 +1228,8 @@ extension ChatControllerImpl {
controller.getCaptionPanelView = { [weak self] in
return self?.getCaptionPanelView(isFile: false)
}
controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion)
controller.legacyCompletion = { signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion in
completion(signals, silently, scheduleTime, parameters, getAnimatedTransitionSource, sendCompletion)
}
present(controller, mediaPickerContext)
}
@ -1492,7 +1509,7 @@ extension ChatControllerImpl {
self.effectiveNavigationController?.pushViewController(contactsController)
self.controllerNavigationDisposable.set((contactsController.result
|> deliverOnMainQueue).startStrict(next: { [weak self] peers in
if let strongSelf = self, let (peers, _, _, _, _) = peers {
if let strongSelf = self, let (peers, _, _, _, _, _) = peers {
if peers.count > 1 {
var enqueueMessages: [EnqueueMessage] = []
for peer in peers {

View file

@ -158,7 +158,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
strongSelf.createActionDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak controller] result in
if let strongSelf = self, let (contactPeers, _, _, _, _) = result, case let .peer(peer, _, _) = contactPeers.first {
if let strongSelf = self, let (contactPeers, _, _, _, _, _) = result, case let .peer(peer, _, _) = contactPeers.first {
controller?.dismissSearch()
controller?.displayNavigationActivity = true
strongSelf.createActionDisposable.set((strongSelf.context.engine.peers.createSecretChat(peerId: peer.id) |> deliverOnMainQueue).startStrict(next: { peerId in

View file

@ -12,6 +12,8 @@ import ContactListUI
import SearchUI
import AttachmentUI
import SearchBarNode
import ChatSendAudioMessageContextPreview
import ChatSendMessageActionUI
class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable {
private let context: AccountContext
@ -46,8 +48,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
fileprivate var caption: NSAttributedString?
private let _result = Promise<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?>()
var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?, NoError> {
private let _result = Promise<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?>()
var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> {
return self._result.get()
}
@ -87,6 +89,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
var isContainerPanning: () -> Bool = { return false }
var isContainerExpanded: () -> Bool = { return false }
var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)?
init(_ params: ContactSelectionControllerParams) {
self.context = params.context
self.autoDismiss = params.autoDismiss
@ -145,6 +149,24 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
if params.multipleSelection {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.beginSearch))
}
self.getCurrentSendMessageContextMediaPreview = { [weak self] in
guard let self else {
return nil
}
let selectedPeers = self.contactsNode.contactListNode.selectedPeers
if selectedPeers.isEmpty {
return nil
}
return ChatSendContactMessageContextPreview(
context: self.context,
presentationData: self.presentationData,
wallpaperBackgroundNode: nil,
contactPeers: selectedPeers
)
}
}
required init(coder aDecoder: NSCoder) {
@ -235,10 +257,10 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
}
}
self.contactsNode.requestMultipleAction = { [weak self] silent, scheduleTime in
self.contactsNode.requestMultipleAction = { [weak self] silent, scheduleTime, parameters in
if let strongSelf = self {
let selectedPeers = strongSelf.contactsNode.contactListNode.selectedPeers
strongSelf._result.set(.single((selectedPeers, .generic, silent, scheduleTime, strongSelf.caption)))
strongSelf._result.set(.single((selectedPeers, .generic, silent, scheduleTime, strongSelf.caption, parameters)))
if strongSelf.autoDismiss {
strongSelf.dismiss()
}
@ -337,7 +359,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self.confirmationDisposable.set((self.confirmation(peer) |> deliverOnMainQueue).startStrict(next: { [weak self] value in
if let strongSelf = self {
if value {
strongSelf._result.set(.single(([peer], action, false, nil, nil)))
strongSelf._result.set(.single(([peer], action, false, nil, nil, nil)))
if strongSelf.autoDismiss {
strongSelf.dismiss()
}
@ -435,6 +457,17 @@ final class ContactsPickerContext: AttachmentMediaPickerContext {
return .single(nil)
}
var hasCaption: Bool {
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
}
@ -451,13 +484,13 @@ final class ContactsPickerContext: AttachmentMediaPickerContext {
self.controller?.caption = caption
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil)
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, parameters)
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.controller?.presentScheduleTimePicker ({ time in
self.controller?.contactsNode.requestMultipleAction?(false, time)
self.controller?.contactsNode.requestMultipleAction?(false, time, parameters)
})
}

View file

@ -38,7 +38,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?) -> Void)?
var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?, _ parameters: ChatSendMessageActionSheetController.SendParameters?) -> Void)?
var dismiss: (() -> Void)?
var cancelSearch: (() -> Void)?
@ -110,7 +110,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
}
shareImpl = { [weak self] in
self?.requestMultipleAction?(false, nil)
self?.requestMultipleAction?(false, nil, nil)
}
contextActionImpl = { [weak self] peer, node, gesture, _ in

View file

@ -140,7 +140,7 @@ public final class NotificationViewControllerImpl {
return nil
})
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), externalRequestVerificationStream: .never(), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
presentationDataPromise.set(sharedAccountContext!.presentationData)
}

View file

@ -1906,6 +1906,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return HashtagSearchController(context: context, peer: peer, query: query, all: all)
}
public func makeStorySearchController(context: AccountContext, query: String) -> ViewController {
return StorySearchGridScreen(context: context, searchQuery: query)
}
public func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController {
return PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: isArchive ? .archive : .saved)
}

View file

@ -35,14 +35,14 @@ final class WebSearchControllerInteraction {
let setSearchQuery: (String) -> Void
let deleteRecentQuery: (String) -> Void
let toggleSelection: (ChatContextResult, Bool) -> Bool
let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void
let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void
let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?) -> Void
let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void
let avatarCompleted: (UIImage) -> Void
let selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
self.openResult = openResult
self.setSearchQuery = setSearchQuery
self.deleteRecentQuery = deleteRecentQuery
@ -589,6 +589,17 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext {
}
}
}
public var hasCaption: Bool {
return false
}
public var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
public func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return .single(nil)
@ -606,12 +617,12 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext {
self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
}
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.interaction?.sendSelected(nil, mode == .silently, nil, messageEffect)
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.interaction?.sendSelected(nil, mode == .silently, nil, parameters)
}
public func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
self.interaction?.schedule(messageEffect)
public func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
self.interaction?.schedule(parameters)
}
public func mainButtonAction() {

View file

@ -2089,6 +2089,17 @@ final class WebAppPickerContext: AttachmentMediaPickerContext {
return .single(nil)
}
var hasCaption: Bool {
return false
}
var captionIsAboveMedia: Signal<Bool, NoError> {
return .single(false)
}
func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void {
}
public var loadingProgress: Signal<CGFloat?, NoError> {
return self.controller?.controllerNode.loadingProgressPromise.get() ?? .single(nil)
}
@ -2104,10 +2115,10 @@ final class WebAppPickerContext: AttachmentMediaPickerContext {
func setCaption(_ caption: NSAttributedString) {
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) {
func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) {
}
func mainButtonAction() {