mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
347cf2da81
71 changed files with 2560 additions and 918 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MTRequestContext : NSObject
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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, _, _):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ¤tPathBounds() = 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 ¤tPathBounds() 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 ¤tPathBounds() 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 ¤tPathBounds() 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 ¤tPathBounds() 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 ¤tPathBounds() 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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@
|
|||
|
||||
namespace lottie {
|
||||
|
||||
enum class TrimType: int {
|
||||
Simultaneously = 1,
|
||||
Individually = 2
|
||||
};
|
||||
|
||||
/// An item that defines trim
|
||||
class Trim: public ShapeItem {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -385,6 +385,7 @@ public class ShareRootControllerImpl {
|
|||
voipMaxLayer: 0,
|
||||
voipVersions: [],
|
||||
appData: .single(nil),
|
||||
externalRequestVerificationStream: .never(),
|
||||
autolockDeadine: .single(nil),
|
||||
encryptionProvider: OpenSSLEncryptionProvider(),
|
||||
deviceModelName: nil,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue