Merge commit '3e31d12db68bdbaff07a3fd3a61742c8a4c8906e' into beta

This commit is contained in:
Ilya Laktyushin 2025-07-02 22:05:08 +02:00
commit 2b5b56a5bb
73 changed files with 1046 additions and 400 deletions

View File

@ -1786,12 +1786,12 @@ ios_application(
#"//third-party/webrtc:webrtc_objc",
#"//submodules/AsyncDisplayKit",
#"//submodules/ObjCRuntimeUtils",
"//submodules/OpusBinding",
#"//submodules/OpusBinding",
#"//third-party/boringssl:ssl",
#"//third-party/boringssl:crypto",
#"//submodules/TelegramVoip",
"//third-party/recaptcha:RecaptchaEnterprise",
#"//submodules/TelegramUI",
#"//third-party/recaptcha:RecaptchaEnterprise",
"//submodules/TelegramUI",
],
)

View File

@ -14448,6 +14448,8 @@ Sorry for the inconvenience.";
"SuggestPost.SetTimeFormat.Date" = "%@";
"SuggestPost.SetTimeFormat.TodayAt" = "Today at %@";
"SuggestPost.SetTimeFormat.TomorrowAt" = "Tomorrow at %@";
"SuggestPost.SetTimeFormat.Any" = "Anytime";
"SuggestPost.SetTime.Label" = "Time";
"Chat.TodoItemCompletionTimestamp.Date" = "completed %@";
"Chat.TodoItemCompletionTimestamp.TodayAt" = "completed today at %@";
@ -14496,7 +14498,8 @@ Sorry for the inconvenience.";
"Chat.Todo.ContextMenu.SectionsInfo" = "You're viewing actions for one task.\nYou can switch to actions for the list.";
"Chat.Todo.PremiumRequired" = "Only [Telegram Premium]() subscribers can mark tasks as done.";
"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this to do list.";
"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this checklist.";
"Chat.Todo.CompletionLimitedForward" = "You cant make changes to forwarded checklists.";
"Forward.ErrorTodoDisabledInChannels" = "Sorry, checklists cant be forwarded to channels.";
@ -14542,8 +14545,8 @@ Sorry for the inconvenience.";
"Chat.PostApproval.Message.UserAgreementPriceTon" = "💰 You have been charged %1$@.\n\n⌛ **%2$@** will receive the TON once the post has been live for 24 hours.\n\n🔄 If admins the post before it has been live for 24 hours, your TON will be refunded.";
"Chat.PostApproval.Message.AdminAgreementPriceStars" = "💰 The user have been charged %1$@.\n\n⌛ **%2$@** will receive the Stars once the post has been live for 24 hours.\n\n🔄 If you remove the post before it has been live for 24 hours, the user's Stars will be refunded.";
"Chat.PostApproval.Message.AdminAgreementPriceTon" = "💰 The user have been charged %1$@.\n\n⌛ **%2$@** will receive the TON once the post has been live for 24 hours.\n\n🔄 If you remove the post before it has been live for 24 hours, the user's TON will be refunded.";
"Chat.PostApproval.Message.AdminDeclined" = "You declined the post.";
"Chat.PostApproval.Message.AdminDeclinedComment" = "You declined the post with the following comment:\n\n%@";
"Chat.PostApproval.Message.AdminDeclined" = "";
"Chat.PostApproval.Message.AdminDeclinedComment" = "%@";
"Chat.PostApproval.Message.ActionApprove" = "Approve";
"Chat.PostApproval.Message.ActionReject" = "Decline";
@ -14623,7 +14626,7 @@ Sorry for the inconvenience.";
"VoiceOver.SuggestPost" = "Suggest Post";
"Chat.ContextMenu.SuggestedPost.EditMessage" = "Edit Message";
"Chat.ContextMenu.SuggestedPost.EditPrice" = "Edit Prive";
"Chat.ContextMenu.SuggestedPost.EditPrice" = "Edit Price";
"Chat.ContextMenu.SuggestedPost.EditTime" = "Edit Time";
"Chat.ContextMenu.SuggestedPost.Create" = "Suggest Post";
@ -14667,3 +14670,10 @@ Sorry for the inconvenience.";
"Chat.Todo.Message.Completed_any" = "%@ of {count} completed";
"Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}";
"Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}";
"Notification.StarGift.ReleasedBy" = "released by %@";
"Gift.View.ReleasedBy" = "released by %@";
"Gift.Unique.CollectibleBy" = "collectible %1$@ by %2$@";
"SendInviteLink.TextCallsRestrictedSendOneInviteLink" = "**%@** restricts calling them. You can send them an invite link to call instead.";

View File

@ -6,6 +6,7 @@ import sys
import json
import shutil
import hashlib
import tempfile
# Read the modules JSON file
modules_json_path = "bazel-bin/Telegram/spm_build_root_modules.json"
@ -17,6 +18,7 @@ with open(modules_json_path, 'r') as f:
spm_files_dir = "spm-files"
previous_spm_files = set()
cleanup_temp_dirs = []
def scan_spm_files(path: str):
global previous_spm_files
@ -119,9 +121,6 @@ def parse_define_flag(flag: str) -> tuple[str, str | None]:
if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
value = value[1:-1] # Remove quotes
value = value.replace("\\\"", "\"")
#if key == "PACKAGE_VERSION":
# print(f"PACKAGE_VERSION={value}")
return (key, value)
else:
@ -328,7 +327,8 @@ for name, module in sorted(modules.items()):
sources_zip_directory = None
if module["type"] == "apple_static_xcframework_import":
pass
sources_zip_directory = tempfile.mkdtemp()
cleanup_temp_dirs.append(sources_zip_directory)
for source in module["sources"] + module.get("hdrs", []) + module.get("textual_hdrs", []):
# Process all sources (both regular and generated) with symlinks
@ -346,29 +346,42 @@ for name, module in sorted(modules.items()):
sys.exit(1)
source_file_name = source[len(module["path"]) + 1:]
# Create symlink for this source file
symlink_location = os.path.join(module_directory, source_file_name)
# Create parent directory for symlink if it doesn't exist
symlink_parent = os.path.dirname(symlink_location)
create_spm_directory(symlink_parent)
# Calculate relative path from symlink back to original file
# Count directory depth: spm-files/module_name/... -> spm-files
num_parent_dirs = symlink_location.count(os.path.sep)
relative_prefix = "".join(["../"] * num_parent_dirs)
symlink_target = relative_prefix + source
# Create the symlink
link_spm_file(symlink_target, symlink_location)
# Add to sources list (exclude certain file types)
if source.endswith(('.h', '.hpp', '.a', '.inc')):
if len(module_public_headers_prefix) != 0 and source_file_name.startswith(module_public_headers_prefix):
public_include_files.append(source_file_name[len(module_public_headers_prefix) + 1:])
exclude_source_files.append(source_file_name)
if sources_zip_directory is not None:
zip_location = os.path.join(sources_zip_directory, source_file_name)
zip_parent = os.path.dirname(zip_location)
if not os.path.exists(zip_parent):
os.makedirs(zip_parent)
shutil.copy2(source, zip_location)
else:
include_source_files.append(source_file_name)
# Create symlink for this source file
symlink_location = os.path.join(module_directory, source_file_name)
# Create parent directory for symlink if it doesn't exist
symlink_parent = os.path.dirname(symlink_location)
create_spm_directory(symlink_parent)
# Calculate relative path from symlink back to original file
# Count directory depth: spm-files/module_name/... -> spm-files
num_parent_dirs = symlink_location.count(os.path.sep)
relative_prefix = "".join(["../"] * num_parent_dirs)
symlink_target = relative_prefix + source
# Create the symlink
link_spm_file(symlink_target, symlink_location)
# Add to sources list (exclude certain file types)
if source.endswith(('.h', '.hpp', '.a', '.inc')):
if len(module_public_headers_prefix) != 0 and source_file_name.startswith(module_public_headers_prefix):
public_include_files.append(source_file_name[len(module_public_headers_prefix) + 1:])
exclude_source_files.append(source_file_name)
else:
include_source_files.append(source_file_name)
if sources_zip_directory is not None:
# Create zip file from sources directory
zip_output_path = os.path.join(module_directory, f"{name}.xcframework.zip")
shutil.make_archive(zip_output_path[:-4], 'zip', sources_zip_directory)
current_spm_files.add(zip_output_path)
if name in module_to_source_files:
print(f"{name}: duplicate module")
@ -562,6 +575,9 @@ for modulemap_path, modulemap in modulemaps.items():
# Clean up files and directories that are no longer needed
files_to_remove = previous_spm_files - current_spm_files
for cleanup_temp_dir in cleanup_temp_dirs:
files_to_remove.add(cleanup_temp_dir)
# Sort by path depth (deeper paths first) to ensure we remove files before their parent directories
sorted_files_to_remove = sorted(files_to_remove, key=lambda x: x.count(os.path.sep), reverse=True)

View File

@ -1026,8 +1026,13 @@ public enum OldChannelsControllerIntent {
}
public enum SendInviteLinkScreenSubject {
public enum GroupCall {
case existing(link: String)
case create
}
case chat(peer: EnginePeer, link: String?)
case groupCall(link: String)
case groupCall(GroupCall)
}
public enum StarsWithdrawalScreenSubject {

View File

@ -911,7 +911,9 @@ ASLayoutElementStyleExtensibilityForwarding
NSArray<ASLayout *> *sublayouts = _calculatedDisplayNodeLayout.layout.sublayouts;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
unowned ASLayout *cSublayouts[sublayouts.count];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
@ -936,7 +938,9 @@ ASLayoutElementStyleExtensibilityForwarding
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
NSArray<ASDisplayNode *> *layoutNodes = ASArrayByFlatMapping(sublayouts, ASLayout *layout, (ASDisplayNode *)layout.layoutElement);
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400

View File

@ -34,7 +34,9 @@ NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
return ASArrayByFlatMapping(allNames, NSString *name, ({

View File

@ -142,7 +142,9 @@ Class _Nullable ASGetClassFromType(const char * _Nullable type)
size_t resultLength = typeLength - 3;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
char className[resultLength + 1];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400

View File

@ -237,7 +237,9 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
// Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue.
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
unowned ASLayout *rawSublayouts[sublayoutsCount];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400

View File

@ -161,7 +161,9 @@ ASLayoutElementStyleExtensibilityForwarding
// Use tiny descriptions because these trees can get nested very deep.
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
const auto tinyDescriptions = ASArrayByFlatMapping(children, id object, ASObjectDescriptionMakeTiny(object));
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
@ -299,7 +301,9 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__)
const auto count = children.count;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
ASLayout *rawSublayouts[count];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400

View File

@ -34,7 +34,9 @@
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#endif
Ivar ivars[count];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 180400

View File

@ -67,12 +67,15 @@ static const int kMaxLayoutElementBoolExtensions = 1;
static const int kMaxLayoutElementStateIntegerExtensions = 4;
static const int kMaxLayoutElementStateEdgeInsetExtensions = 1;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
typedef struct ASLayoutElementStyleExtensions {
// Values to store extensions
BOOL boolExtensions[kMaxLayoutElementBoolExtensions];
NSInteger integerExtensions[kMaxLayoutElementStateIntegerExtensions];
UIEdgeInsets edgeInsetsExtensions[kMaxLayoutElementStateEdgeInsetExtensions];
} ASLayoutElementStyleExtensions;
#pragma clang diagnostic pop
#define ASLayoutElementStyleExtensibilityForwarding \
- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx\

View File

@ -1306,6 +1306,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
} else if let channel = peerViewMainPeer(view) as? TelegramChannel {
if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
return nil
} else if let cachedData = view.cachedData as? CachedChannelData, let sendPaidMessageStarsValue = cachedData.sendPaidMessageStars, sendPaidMessageStarsValue == .zero {
return nil
} else {
return channel.sendPaidMessageStars
}

View File

@ -680,8 +680,11 @@ public final class CallListController: TelegramBaseController {
}
if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.push(strongSelf.context.sharedContext.makeSendInviteLinkScreen(context: strongSelf.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: false,
premiumRequiredToContact: false
)], theme: strongSelf.presentationData.theme))
return
}

View File

@ -38,7 +38,7 @@ public final class SheetComponentEnvironment: Equatable {
}
public let sheetComponentTag = GenericComponentViewTag()
public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: Component {
public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment)
public class ExternalState {

View File

@ -244,7 +244,13 @@ ActionStage *ActionStageInstance()
return @"";
int length = (int)path.length;
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
unichar newPath[path.length];
#pragma clang diagnostic pop
int newLength = 0;
SEL sel = @selector(characterAtIndex:);

View File

@ -257,7 +257,12 @@ static bool consumeField(const char *desc, int length, int &offset, uint32_t &ou
{
outName = (uint32_t)murMurHashBytes32((void *)(desc + offset), i - offset);
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
char buf[i - offset + 1];
#pragma clang diagnostic pop
memcpy(buf, desc + offset, i - offset);
buf[i - offset] = 0;

View File

@ -96,7 +96,10 @@
const char *type = self.objCType;
static const NSInteger numberofNumberTypes = 10;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
static const char *numberTypes[numberofNumberTypes] = { "i", "s", "l", "q", "I", "S", "L", "Q", "f", "d" };
#pragma clang diagnostic pop
for (NSInteger i = 0; i < numberofNumberTypes; i++) {
if (strcmp(type, numberTypes[i]) == 0) {

View File

@ -113,10 +113,13 @@ const NSUInteger PGPhotoEnhanceSegments = 4;
GLubyte *bytes = [self _rawBytes];
NSUInteger bytesPerRow = [_retainedFramebuffer bytesPerRow];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
NSUInteger hist[totalSegments][PGPhotoEnhanceHistogramBins];
NSUInteger cdfs[totalSegments][PGPhotoEnhanceHistogramBins];
NSUInteger cdfsMin[totalSegments];
NSUInteger cdfsMax[totalSegments];
#pragma clang diagnostic pop
memset(hist, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
memset(cdfs, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));

View File

@ -26,7 +26,12 @@ static void decay_position(CGFloat *x, CGFloat *v, NSUInteger count, CFTimeInter
// x0 = x;
// x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 - deceleration)
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
float v0[count];
#pragma clang diagnostic pop
float kv = powf(deceleration, dt);
float kx = deceleration * (1 - kv) / (1 - deceleration);

View File

@ -257,7 +257,7 @@ const CGFloat TGGifConverterMaximumSide = 720.0f;
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pxBuffer);
CGContextRef context = CGBitmapContextCreate(pxData, size.width, size.height, 8, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst);
CGContextRef context = CGBitmapContextCreate(pxData, size.width, size.height, 8, bytesPerRow, colorSpace, (CGBitmapInfo)kCGImageAlphaNoneSkipFirst);
NSAssert(context, @"Could not create a context");
CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), frame);

View File

@ -840,6 +840,7 @@
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
const NSUInteger bufSize = 1024;
NSUInteger numberOfBuffersToRead = MIN(32, floor(fileData.length / bufSize));

View File

@ -1139,7 +1139,12 @@ int32_t legacy_murMurHashBytes32(void *bytes, int length)
int32_t phoneMatchHash(NSString *phone)
{
int length = (int)phone.length;
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
char cleanString[length];
#pragma clang diagnostic pop
int cleanLength = 0;
for (int i = 0; i < length; i++)

View File

@ -10,7 +10,13 @@
NSUInteger length = [data length];
int windowBits = 15 + 32; //Default + gzip header instead of zlib header
int retCode;
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
unsigned char output[kMemoryChunkSize];
#pragma clang diagnostic pop
uInt gotBack;
NSMutableData *result;
z_stream stream;

View File

@ -688,7 +688,13 @@
NSUInteger length = [data length];
int windowBits = 15 + 32; //Default + gzip header instead of zlib header
int retCode;
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
unsigned char output[kMemoryChunkSize];
#pragma clang diagnostic pop
uInt gotBack;
NSMutableData *result;
z_stream stream;

View File

@ -90,13 +90,13 @@ static int is_opus(ogg_page *og) {
return nil;
}
int pages = 0;
int packetsout = 0;
int invalid = 0;
__unused int pages = 0;
__unused int packetsout = 0;
__unused int invalid = 0;
int eos = 0;
int headers = 0;
int serialno = 0;
__unused int serialno = 0;
/* LOOP START */
while (ogg_sync_pageout(&ostate, &opage) == 1) {
@ -145,7 +145,7 @@ static int is_opus(ogg_page *og) {
}
/* get packet duration */
samples = opus_packet_get_nb_samples(opacket.packet, opacket.bytes, sampleRate);
samples = opus_packet_get_nb_samples(opacket.packet, (int)opacket.bytes, sampleRate);
if (samples <= 0) {
invalid++;
continue; // skipping invalid packet

View File

@ -943,8 +943,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
dict[-970274264] = { return Api.StarGift.parse_starGift($0) }
dict[1678891913] = { return Api.StarGift.parse_starGiftUnique($0) }
dict[2139438098] = { return Api.StarGift.parse_starGift($0) }
dict[-164136786] = { return Api.StarGift.parse_starGiftUnique($0) }
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) }
@ -1432,7 +1432,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) }
dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) }
dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) }
dict[785918357] = { return Api.payments.StarGifts.parse_starGifts($0) }
dict[-1551326360] = { return Api.payments.StarGifts.parse_starGiftsNotModified($0) }
dict[961445665] = { return Api.payments.StarsRevenueAdsAccountUrl.parse_starsRevenueAdsAccountUrl($0) }
dict[1814066038] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) }

View File

@ -636,14 +636,14 @@ public extension Api {
}
public extension Api {
enum StarGift: TypeConstructorDescription {
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?)
case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?)
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?)
case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?, releasedBy: Api.Peer?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title):
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy):
if boxed {
buffer.appendInt32(-970274264)
buffer.appendInt32(2139438098)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
@ -658,10 +658,11 @@ public extension Api {
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellMinStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 6) != 0 {releasedBy!.serialize(buffer, true)}
break
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars):
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy):
if boxed {
buffer.appendInt32(1678891913)
buffer.appendInt32(-164136786)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
@ -680,16 +681,17 @@ public extension Api {
serializeInt32(availabilityTotal, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeString(giftAddress!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 5) != 0 {releasedBy!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title):
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any)])
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars):
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any)])
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy):
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any)])
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy):
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any), ("releasedBy", releasedBy as Any)])
}
}
@ -722,6 +724,10 @@ public extension Api {
if Int(_1!) & Int(1 << 4) != 0 {_12 = reader.readInt64() }
var _13: String?
if Int(_1!) & Int(1 << 5) != 0 {_13 = parseString(reader) }
var _14: Api.Peer?
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
_14 = Api.parse(reader, signature: signature) as? Api.Peer
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -735,8 +741,9 @@ public extension Api {
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13)
let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14)
}
else {
return nil
@ -773,6 +780,10 @@ public extension Api {
if Int(_1!) & Int(1 << 3) != 0 {_12 = parseString(reader) }
var _13: Int64?
if Int(_1!) & Int(1 << 4) != 0 {_13 = reader.readInt64() }
var _14: Api.Peer?
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
_14 = Api.parse(reader, signature: signature) as? Api.Peer
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -786,8 +797,9 @@ public extension Api {
let _c11 = _11 != nil
let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 4) == 0) || _13 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13)
let _c14 = (Int(_1!) & Int(1 << 5) == 0) || _14 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13, releasedBy: _14)
}
else {
return nil

View File

@ -298,14 +298,14 @@ public extension Api.payments {
}
public extension Api.payments {
enum StarGifts: TypeConstructorDescription {
case starGifts(hash: Int32, gifts: [Api.StarGift])
case starGifts(hash: Int32, gifts: [Api.StarGift], chats: [Api.Chat], users: [Api.User])
case starGiftsNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starGifts(let hash, let gifts):
case .starGifts(let hash, let gifts, let chats, let users):
if boxed {
buffer.appendInt32(-1877571094)
buffer.appendInt32(785918357)
}
serializeInt32(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
@ -313,6 +313,16 @@ public extension Api.payments {
for item in gifts {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .starGiftsNotModified:
if boxed {
@ -325,8 +335,8 @@ public extension Api.payments {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starGifts(let hash, let gifts):
return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any)])
case .starGifts(let hash, let gifts, let chats, let users):
return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any), ("chats", chats as Any), ("users", users as Any)])
case .starGiftsNotModified:
return ("starGiftsNotModified", [])
}
@ -339,10 +349,20 @@ public extension Api.payments {
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!)
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!)
}
else {
return nil

View File

@ -867,11 +867,19 @@ public final class ManagedAudioSessionImpl: NSObject, ManagedAudioSession {
options.insert(.allowBluetoothA2DP)
}
case .voiceCall, .videoCall:
#if canImport(AlarmKit) //Xcode 26
options.insert(.allowBluetoothHFP)
#else
options.insert(.allowBluetooth)
#endif
options.insert(.allowBluetoothA2DP)
options.insert(.mixWithOthers)
case let .record(_, video, mixWithOthers):
#if canImport(AlarmKit) //Xcode 26
options.insert(.allowBluetoothHFP)
#else
options.insert(.allowBluetooth)
#endif
if video {
options.insert(.allowBluetoothA2DP)
}

View File

@ -2039,7 +2039,7 @@ final class RoundGradientButtonComponent: Component {
}
}
public final class Throttler<T: Hashable> {
public final class Throttler<T: Hashable & Sendable> {
public var duration: TimeInterval = 0.25
public var queue: DispatchQueue = .main
public var isEnabled: Bool { duration > 0 }

View File

@ -3659,7 +3659,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
case let .privacy(peer):
if let peer {
if let currentInviteLinks = self.currentInviteLinks {
let inviteLinkScreen = self.accountContext.sharedContext.makeSendInviteLinkScreen(context: self.accountContext, subject: .groupCall(link: currentInviteLinks.listenerLink), peers: [TelegramForbiddenInvitePeer(peer: peer, canInviteWithPremium: false, premiumRequiredToContact: false)], theme: defaultDarkColorPresentationTheme)
let inviteLinkScreen = self.accountContext.sharedContext.makeSendInviteLinkScreen(context: self.accountContext, subject: .groupCall(.existing(link: currentInviteLinks.listenerLink)), peers: [TelegramForbiddenInvitePeer(peer: peer, canInviteWithPremium: false, premiumRequiredToContact: false)], theme: defaultDarkColorPresentationTheme)
if let navigationController = self.accountContext.sharedContext.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(inviteLinkScreen)
}

View File

@ -122,7 +122,7 @@ final class AccountTaskManager {
tasks.add(managedDisabledChannelStatusIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(_internal_loadedStickerPack(postbox: self.stateManager.postbox, network: self.stateManager.network, reference: .iconTopicEmoji, forceActualized: true).start())
tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start())

View File

@ -354,7 +354,8 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne
],
availability: StarGift.UniqueGift.Availability(issued: 0, total: 0),
giftAddress: nil,
resellStars: nil
resellStars: nil,
releasedBy: nil
)
if let entry = CodableEntry(RecentStarGiftItem(gift)) {
items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry))

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 206
return 207
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -869,7 +869,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
return [peerId]
case let .prizeStars(_, _, boostPeerId, _, _):
return boostPeerId.flatMap { [$0] } ?? []
case let .starGift(_, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _):
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _):
var peerIds: [PeerId] = []
if let peerId {
peerIds.append(peerId)
@ -877,8 +877,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
if let senderId {
peerIds.append(senderId)
}
if let releasedBy = gift.releasedBy {
peerIds.append(releasedBy)
}
return peerIds
case let .starGiftUnique(_, _, _, _, _, _, _, peerId, senderId, _, _, _, _):
case let .starGiftUnique(gift, _, _, _, _, _, _, peerId, senderId, _, _, _, _):
var peerIds: [PeerId] = []
if let peerId {
peerIds.append(peerId)
@ -886,6 +889,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
if let senderId {
peerIds.append(senderId)
}
if let releasedBy = gift.releasedBy {
peerIds.append(releasedBy)
}
return peerIds
case let .conferenceCall(conferenceCall):
return conferenceCall.otherParticipants

View File

@ -11,6 +11,7 @@ public enum TranslationError {
case textTooLong
case invalidLanguage
case limitExceeded
case tryAlternative
}
func _internal_translate(network: Network, text: String, toLang: String, entities: [MessageTextEntity] = []) -> Signal<(String, [MessageTextEntity])?, TranslationError> {
@ -29,6 +30,8 @@ func _internal_translate(network: Network, text: String, toLang: String, entitie
return .textTooLong
} else if error.errorDescription == "TO_LANG_INVALID" {
return .invalidLanguage
} else if error.errorDescription == "TRANSLATIONS_DISABLED_ALT" {
return .tryAlternative
} else {
return .generic
}

View File

@ -54,6 +54,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
case soldOut
case flags
case upgradeStars
case releasedBy
}
public struct Availability: Equatable, Codable, PostboxCoding {
@ -149,8 +150,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
public let soldOut: SoldOut?
public let flags: Flags
public let upgradeStars: Int64?
public let releasedBy: EnginePeer.Id?
public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?) {
public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?, releasedBy: EnginePeer.Id?) {
self.id = id
self.title = title
self.file = file
@ -160,6 +162,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.soldOut = soldOut
self.flags = flags
self.upgradeStars = upgradeStars
self.releasedBy = releasedBy
}
public init(from decoder: Decoder) throws {
@ -179,6 +182,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.soldOut = try container.decodeIfPresent(SoldOut.self, forKey: .soldOut)
self.flags = Flags(rawValue: try container .decodeIfPresent(Int32.self, forKey: .flags) ?? 0)
self.upgradeStars = try container.decodeIfPresent(Int64.self, forKey: .upgradeStars)
self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy)
}
public init(decoder: PostboxDecoder) {
@ -191,6 +195,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.soldOut = decoder.decodeObjectForKey(CodingKeys.soldOut.rawValue, decoder: { StarGift.Gift.SoldOut(decoder: $0) }) as? StarGift.Gift.SoldOut
self.flags = Flags(rawValue: decoder.decodeInt32ForKey(CodingKeys.flags.rawValue, orElse: 0))
self.upgradeStars = decoder.decodeOptionalInt64ForKey(CodingKeys.upgradeStars.rawValue)
self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) }
}
public func encode(to encoder: Encoder) throws {
@ -209,6 +214,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
try container.encodeIfPresent(self.soldOut, forKey: .soldOut)
try container.encode(self.flags.rawValue, forKey: .flags)
try container.encodeIfPresent(self.upgradeStars, forKey: .upgradeStars)
try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy)
}
public func encode(_ encoder: PostboxEncoder) {
@ -237,6 +243,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
} else {
encoder.encodeNil(forKey: CodingKeys.upgradeStars.rawValue)
}
if let releasedBy = self.releasedBy {
encoder.encodeInt64(releasedBy.toInt64(), forKey: CodingKeys.releasedBy.rawValue)
} else {
encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue)
}
}
}
@ -253,6 +264,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
case availability
case giftAddress
case resellStars
case releasedBy
}
public enum Attribute: Equatable, Codable, PostboxCoding {
@ -506,8 +518,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
public let availability: Availability
public let giftAddress: String?
public let resellStars: Int64?
public let releasedBy: EnginePeer.Id?
public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?) {
public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?, releasedBy: EnginePeer.Id?) {
self.id = id
self.title = title
self.number = number
@ -517,6 +530,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.availability = availability
self.giftAddress = giftAddress
self.resellStars = resellStars
self.releasedBy = releasedBy
}
public init(from decoder: Decoder) throws {
@ -538,6 +552,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability)
self.giftAddress = try container.decodeIfPresent(String.self, forKey: .giftAddress)
self.resellStars = try container.decodeIfPresent(Int64.self, forKey: .resellStars)
self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy)
}
public init(decoder: PostboxDecoder) {
@ -558,6 +573,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability
self.giftAddress = decoder.decodeOptionalStringForKey(CodingKeys.giftAddress.rawValue)
self.resellStars = decoder.decodeOptionalInt64ForKey(CodingKeys.resellStars.rawValue)
self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) }
}
public func encode(to encoder: Encoder) throws {
@ -578,6 +594,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
try container.encode(self.availability, forKey: .availability)
try container.encodeIfPresent(self.giftAddress, forKey: .giftAddress)
try container.encodeIfPresent(self.resellStars, forKey: .resellStars)
try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy)
}
public func encode(_ encoder: PostboxEncoder) {
@ -605,6 +622,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
} else {
encoder.encodeNil(forKey: CodingKeys.resellStars.rawValue)
}
if let releasedBy = self.releasedBy {
encoder.encodeInt64(releasedBy.toInt64(), forKey: CodingKeys.releasedBy.rawValue)
} else {
encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue)
}
}
public func withResellStars(_ resellStars: Int64?) -> UniqueGift {
@ -617,7 +639,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
attributes: self.attributes,
availability: self.availability,
giftAddress: self.giftAddress,
resellStars: resellStars
resellStars: resellStars,
releasedBy: self.releasedBy
)
}
}
@ -683,10 +706,21 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
}
}
public extension StarGift {
var releasedBy: EnginePeer.Id? {
switch self {
case let .generic(gift):
return gift.releasedBy
case let .unique(gift):
return gift.releasedBy
}
}
}
extension StarGift {
init?(apiStarGift: Api.StarGift) {
switch apiStarGift {
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title):
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy):
var flags = StarGift.Gift.Flags()
if (apiFlags & (1 << 2)) != 0 {
flags.insert(.isBirthdayGift)
@ -708,8 +742,8 @@ extension StarGift {
guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else {
return nil
}
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars))
case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, reselltars):
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId))
case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellStars, releasedBy):
let owner: StarGift.UniqueGift.Owner
if let ownerAddress {
owner = .address(ownerAddress)
@ -720,7 +754,7 @@ extension StarGift {
} else {
return nil
}
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: reselltars))
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: resellStars, releasedBy: releasedBy?.peerId))
}
}
}
@ -739,7 +773,7 @@ func _internal_cachedStarGifts(postbox: Postbox) -> Signal<StarGiftsList?, NoErr
}
}
func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal<Never, NoError> {
let updateSignal = _internal_cachedStarGifts(postbox: postbox)
|> take(1)
|> mapToSignal { list -> Signal<Never, NoError> in
@ -755,7 +789,10 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) ->
return postbox.transaction { transaction in
switch result {
case let .starGifts(hash, gifts):
case let .starGifts(hash, gifts, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
let starGiftsLists = StarGiftsList(items: gifts.compactMap { StarGift(apiStarGift: $0) }, hashValue: hash)
transaction.setPreferencesEntry(key: PreferencesKeys.starGifts(), value: PreferencesEntry(starGiftsLists))
case .starGiftsNotModified:
@ -769,8 +806,8 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) ->
return updateSignal
}
func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network)
func managedStarGiftsUpdates(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal<Never, NoError> {
let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network, accountPeerId: accountPeerId)
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}

View File

@ -114,7 +114,7 @@ public extension TelegramEngine {
}
public func keepStarGiftsUpdated() -> Signal<Never, NoError> {
return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network)
return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
}
public func convertStarGift(reference: StarGiftReference) -> Signal<Never, NoError> {

View File

@ -481,6 +481,7 @@ swift_library(
"//submodules/TelegramUI/Components/BatchVideoRendering",
"//submodules/TelegramUI/Components/ComposeTodoScreen",
"//submodules/TelegramUI/Components/SuggestedPostApproveAlert",
"//submodules/TelegramUI/Components/Stars/BalanceNeededScreen",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View File

@ -16,6 +16,8 @@ objc_library(
includes = [
"PublicHeaders",
],
copts = [
],
sdk_frameworks = [
"Foundation",
"Accelerate",

View File

@ -371,43 +371,9 @@ typedef int16_t tran_low_t;
typedef int32_t tran_high_t;
typedef int16_t tran_coef_t;
static const tran_coef_t cospi_1_64 = 16364;
static const tran_coef_t cospi_2_64 = 16305;
static const tran_coef_t cospi_3_64 = 16207;
static const tran_coef_t cospi_4_64 = 16069;
static const tran_coef_t cospi_5_64 = 15893;
static const tran_coef_t cospi_6_64 = 15679;
static const tran_coef_t cospi_7_64 = 15426;
static const tran_coef_t cospi_8_64 = 15137;
static const tran_coef_t cospi_9_64 = 14811;
static const tran_coef_t cospi_10_64 = 14449;
static const tran_coef_t cospi_11_64 = 14053;
static const tran_coef_t cospi_12_64 = 13623;
static const tran_coef_t cospi_13_64 = 13160;
static const tran_coef_t cospi_14_64 = 12665;
static const tran_coef_t cospi_15_64 = 12140;
static const tran_coef_t cospi_16_64 = 11585;
static const tran_coef_t cospi_17_64 = 11003;
static const tran_coef_t cospi_18_64 = 10394;
static const tran_coef_t cospi_19_64 = 9760;
static const tran_coef_t cospi_20_64 = 9102;
static const tran_coef_t cospi_21_64 = 8423;
static const tran_coef_t cospi_22_64 = 7723;
static const tran_coef_t cospi_23_64 = 7005;
static const tran_coef_t cospi_24_64 = 6270;
static const tran_coef_t cospi_25_64 = 5520;
static const tran_coef_t cospi_26_64 = 4756;
static const tran_coef_t cospi_27_64 = 3981;
static const tran_coef_t cospi_28_64 = 3196;
static const tran_coef_t cospi_29_64 = 2404;
static const tran_coef_t cospi_30_64 = 1606;
static const tran_coef_t cospi_31_64 = 804;
// 16384 * sqrt(2) * sin(kPi/9) * 2 / 3
static const tran_coef_t sinpi_1_9 = 5283;
static const tran_coef_t sinpi_2_9 = 9929;
static const tran_coef_t sinpi_3_9 = 13377;
static const tran_coef_t sinpi_4_9 = 15212;
#define DCT_CONST_BITS 14
#define DCT_CONST_ROUNDING (1 << (DCT_CONST_BITS - 1))
@ -490,7 +456,7 @@ void vpx_fdct4x4_c(const int16_t *input, tran_low_t *output, int stride) {
#define ROUND_POWER_OF_TWO(value, n) (((value) + (1 << ((n)-1))) >> (n))
static inline tran_high_t dct_const_round_shift(tran_high_t input) {
/*static inline tran_high_t dct_const_round_shift(tran_high_t input) {
tran_high_t rv = ROUND_POWER_OF_TWO(input, DCT_CONST_BITS);
return (tran_high_t)rv;
}
@ -507,11 +473,11 @@ static inline tran_high_t check_range(tran_high_t input) {
assert(input <= INT16_MAX);
#endif // CONFIG_COEFFICIENT_RANGE_CHECKING
return input;
}
}*/
#define WRAPLOW(x) ((int32_t)check_range(x))
void idct4_c(const tran_low_t *input, tran_low_t *output) {
/*void idct4_c(const tran_low_t *input, tran_low_t *output) {
int16_t step[4];
tran_high_t temp1, temp2;
@ -553,7 +519,7 @@ void vpx_idct4x4_16_add_c(const tran_low_t *input, tran_low_t *dest, int stride)
dest[j * stride + i] = ROUND_POWER_OF_TWO(temp_out[j], 4);
}
}
}
}*/
#if defined(__aarch64__)

View File

@ -56,6 +56,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let ribbonBackgroundNode: ASImageNode
private let ribbonTextNode: TextNode
private let creatorButtonNode: HighlightTrackingButtonNode
private var creatorButtonBackgroundNode: NavigationBackgroundNode?
private let creatorButtonTitleNode: TextNode
private var shimmerEffectNode: ShimmerEffectForegroundNode?
private let buttonNode: HighlightTrackingButtonNode
private let buttonStarsNode: PremiumStarsNode
@ -152,17 +156,17 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.symbolValueTextNode = TextNode()
self.symbolValueTextNode.isUserInteractionEnabled = false
self.symbolValueTextNode.displaysAsynchronously = false
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 17.0
self.placeholderNode = StickerShimmerEffectNode()
self.placeholderNode.isUserInteractionEnabled = false
self.placeholderNode.alpha = 0.75
self.animationNode = DefaultAnimatedStickerNodeImpl()
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 17.0
self.buttonStarsNode = PremiumStarsNode()
self.buttonContentNode = ASDisplayNode()
@ -171,6 +175,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.buttonTitleNode = TextNode()
self.buttonTitleNode.displaysAsynchronously = false
self.creatorButtonNode = HighlightTrackingButtonNode()
self.creatorButtonNode.clipsToBounds = true
self.creatorButtonNode.cornerRadius = 9.0
self.creatorButtonTitleNode = TextNode()
self.creatorButtonTitleNode.displaysAsynchronously = false
self.ribbonBackgroundNode = ASImageNode()
self.ribbonBackgroundNode.displaysAsynchronously = false
@ -196,9 +207,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.buttonStarsNode)
self.buttonNode.addSubnode(self.buttonContentNode)
self.buttonContentNode.addSubnode(self.buttonTitleNode)
self.addSubnode(self.creatorButtonNode)
self.creatorButtonNode.addSubnode(self.creatorButtonTitleNode)
self.addSubnode(self.ribbonBackgroundNode)
self.addSubnode(self.ribbonTextNode)
@ -213,8 +227,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.creatorButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.creatorButtonNode.layer.removeAnimation(forKey: "opacity")
strongSelf.creatorButtonNode.alpha = 0.4
} else {
strongSelf.creatorButtonNode.alpha = 1.0
strongSelf.creatorButtonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.creatorButtonNode.addTarget(self, action: #selector(self.creatorButtonPressed), forControlEvents: .touchUpInside)
}
required public init?(coder aDecoder: NSCoder) {
@ -239,6 +265,31 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.maskView?.addSubview(maskOverlayView)
}
@objc private func creatorButtonPressed() {
guard let item = self.item else {
return
}
var releasedBy: EnginePeer.Id?
for media in item.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
releasedBy = gift.releasedBy
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _):
releasedBy = gift.releasedBy
default:
break
}
break
}
}
guard let releasedBy, let peer = item.message.peers[releasedBy] else {
return
}
item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
}
@objc private func buttonPressed() {
guard let item = self.item else {
return
@ -330,6 +381,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let makeMeasureTextLayout = TextNode.asyncLayout(nil)
let makeMoreTextLayout = TextNode.asyncLayout(self.moreTextNode)
let makeCreatorButtonTitleLayout = TextNode.asyncLayout(self.creatorButtonTitleNode)
let makeModelTitleLayout = TextNode.asyncLayout(self.modelTitleTextNode)
let makeModelValueLayout = TextNode.asyncLayout(self.modelValueTextNode)
let makeBackdropTitleLayout = TextNode.asyncLayout(self.backdropTitleTextNode)
@ -373,6 +426,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var textSpacing: CGFloat = 0.0
var isStarGift = false
var creatorButtonTitle = ""
var modelTitle: String?
var modelValue: String?
var backdropTitle: String?
@ -494,6 +549,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, channelPeerId, senderPeerId, _):
if case let .generic(gift) = gift {
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
}
isStarGift = true
var authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
@ -586,6 +645,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
if case let .unique(uniqueGift) = gift {
isStarGift = true
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
}
let isSelfGift = item.message.id.peerId == item.context.account.peerId
let authorName: String
if isUpgrade {
@ -763,11 +826,30 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let creatorButtonAttributedString = parseMarkdownIntoAttributedString(creatorButtonTitle, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(12.0), textColor: primaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(12.0), textColor: primaryTextColor),
link: MarkdownAttributeSet(font: Font.regular(12.0), textColor: primaryTextColor),
linkAttribute: { url in
return ("URL", url)
}
), textAlignment: .center)
let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
if modelTitle == nil && !creatorButtonTitle.isEmpty {
textSpacing += 28.0
}
giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0
if let _ = modelTitle {
giftSize.height += 70.0
if !creatorButtonTitle.isEmpty {
giftSize.height += 28.0
}
}
if !buttonTitle.isEmpty {
@ -847,8 +929,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.animationNode.isHidden = isStoryEntity
strongSelf.buttonNode.isHidden = buttonTitle.isEmpty
strongSelf.buttonNode.isUserInteractionEnabled = !item.presentationData.isPreview
strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty
strongSelf.creatorButtonNode.isHidden = creatorButtonTitle.isEmpty
strongSelf.creatorButtonNode.isUserInteractionEnabled = !item.presentationData.isPreview
strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty
if strongSelf.item == nil && !isStoryEntity {
strongSelf.animationNode.started = { [weak self] in
if let strongSelf = self {
@ -906,15 +993,48 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let _ = buttonTitleApply()
let _ = ribbonTextApply()
let _ = moreApply()
let _ = creatorButtonTitleApply()
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight))
var attributesOffsetY: CGFloat = 0.0
let creatorButtonSize = CGSize(width: creatorButtonTitleLayout.size.width + 18.0, height: 18.0)
let creatorButtonOriginY = modelTitle == nil ? titleFrame.maxY + 4.0 : clippingTextFrame.maxY + 5.0
let creatorButtonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize)
if !creatorButtonTitle.isEmpty {
attributesOffsetY += creatorButtonSize.height + 10.0
}
if !strongSelf.creatorButtonNode.isHidden, modelTitle != nil {
strongSelf.creatorButtonNode.backgroundColor = .clear
let creatorButtonBackgroundNode: NavigationBackgroundNode
if let current = strongSelf.creatorButtonBackgroundNode {
creatorButtonBackgroundNode = current
} else {
creatorButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.12), enableBlur: true, enableSaturation: false)
strongSelf.creatorButtonNode.insertSubnode(creatorButtonBackgroundNode, at: 0)
strongSelf.creatorButtonBackgroundNode = creatorButtonBackgroundNode
}
creatorButtonBackgroundNode.update(size: creatorButtonFrame.size, cornerRadius: 9.0, transition: .immediate)
} else {
strongSelf.creatorButtonNode.backgroundColor = overlayColor
if let creatorButtonBackgroundNode = strongSelf.creatorButtonBackgroundNode {
strongSelf.creatorButtonBackgroundNode = nil
creatorButtonBackgroundNode.removeFromSupernode()
}
}
strongSelf.creatorButtonTitleNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 1.0), size: creatorButtonTitleLayout.size)
animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: creatorButtonFrame, completion: nil)
let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size)
strongSelf.subtitleNode.textNode.frame = subtitleFrame
@ -997,7 +1117,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = titleApply()
titleTextNode.frame = CGRect(
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + yOffset),
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + attributesOffsetY + yOffset),
size: titleLayout.size
)
}
@ -1007,7 +1127,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = valueApply()
valueTextNode.frame = CGRect(
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + yOffset),
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + attributesOffsetY + yOffset),
size: valueLayout.size
)
}
@ -1038,7 +1158,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
var buttonOriginY = clippingTextFrame.maxY + 10.0
if modelTitleLayoutAndApply != nil {
buttonOriginY = clippingTextFrame.maxY + 80.0
buttonOriginY = clippingTextFrame.maxY + attributesOffsetY + 80.0
}
strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 8.0), size: buttonTitleLayout.size)
@ -1355,8 +1475,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
return ChatMessageBubbleContentTapAction(content: .none)
}
}
if self.buttonNode.frame.contains(point) {
if self.buttonNode.frame.contains(point) || self.creatorButtonNode.frame.contains(point) {
return ChatMessageBubbleContentTapAction(content: .ignore)
} else if self.textClippingNode.frame.contains(point) && !self.isExpanded && !self.moreTextNode.alpha.isZero {
return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in

View File

@ -139,7 +139,11 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
}
if case let .channel(channel) = peer, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = firstMessage.peers[linkedMonoforumId] {
title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
if author.id == mainChannel.id {
title = EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
} else {
title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
}
} else {
if let threadData = item.threadData {
title = "\(authorString)\(threadData.info.title)"

View File

@ -137,9 +137,7 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
}
} else {
var channelName = ""
if item.message.author is TelegramChannel {
channelName = item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " "
}
channelName = item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " "
if changedText && changedMedia && changedPrice && changedTime {
titleText = item.presentationData.strings.Chat_PostSuggestion_ChannelChangePTC(channelName).string
} else if changedText && changedPrice && changedTime {

View File

@ -372,6 +372,19 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio
return images
}
private class TodoTrackingButtonNode: HighlightableButtonNode {
private var internalHighlighted = false
public var shouldHighlightAtPoint: (CGPoint) -> Bool = { _ in return true }
open override func beginTracking(with touch: UITouch, with event: UIEvent?) -> Bool {
if self.shouldHighlightAtPoint(touch.location(in: self.view)) {
return super.beginTracking(with: touch, with: event)
}
return false
}
}
private final class ChatMessageTodoItemNode: ASDisplayNode {
private var backgroundWallpaperNode: ChatMessageBubbleBackdrop?
private var backgroundNode: ChatMessageBackground?
@ -390,7 +403,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
fileprivate var titleNode: TextNodeWithEntities?
fileprivate var nameNode: TextNode?
private let buttonNode: HighlightTrackingButtonNode
private let buttonNode: TodoTrackingButtonNode
let separatorNode: ASDisplayNode
var context: AccountContext?
@ -433,7 +446,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
self.highlightedBackgroundNode.alpha = 0.0
self.highlightedBackgroundNode.isUserInteractionEnabled = false
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode = TodoTrackingButtonNode()
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
@ -447,6 +460,15 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.buttonNode.shouldHighlightAtPoint = { [weak self] location in
guard let self else {
return true
}
if case .none = self.tapActionAtPoint(location, gesture: .tap, isEstimating: true).content {
return true
}
return false
}
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {

View File

@ -294,7 +294,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
}
var contentSize = CGSize(width: params.width, height: 4.0 + 4.0)
contentSize.height = 346.0
contentSize.height = 370.0
insets = itemListNeighborsGroupedInsets(neighbors, params)
if params.width <= 320.0 {
insets.top = 0.0

View File

@ -559,25 +559,42 @@ final class GiftSetupScreenComponent: Component {
self.hideName = true
}
var releasedBy: EnginePeer.Id?
if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil {
self.includeUpgrade = true
}
if case let .starGift(gift, _) = component.subject {
releasedBy = gift.releasedBy
}
let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId),
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId)
)
|> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer, sendPaidMessageStars in
var peerIds: [EnginePeer.Id] = [
component.context.account.peerId,
component.peerId
]
if let releasedBy {
peerIds.append(releasedBy)
}
let _ = combineLatest(queue: Queue.mainQueue(),
component.context.engine.data.get(EngineDataMap(
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
}
)),
component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId)
)
).start(next: { [weak self] peers, sendPaidMessageStars in
guard let self else {
return
}
if let peer {
self.peerMap[peer.id] = peer
}
if let accountPeer {
self.peerMap[accountPeer.id] = accountPeer
var peersMap: [EnginePeer.Id: EnginePeer] = [:]
for (peerId, maybePeer) in peers {
if let peer = maybePeer {
peersMap[peerId] = peer
}
}
self.peerMap = peersMap
self.sendPaidMessageStars = sendPaidMessageStars
self.state?.updated()
@ -847,7 +864,7 @@ final class GiftSetupScreenComponent: Component {
let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 370.0, tag: self.introPlaceholderTag))))
if self.sendPaidMessageStars == nil {
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
@ -965,6 +982,7 @@ final class GiftSetupScreenComponent: Component {
if let accountPeer = self.peerMap[component.context.account.peerId] {
var upgradeStars: Int64?
let subject: ChatGiftPreviewItem.Subject
var releasedBy: EnginePeer.Id?
switch component.subject {
case let .premium(product):
if self.payWithStars, let starsPrice = product.starsPrice {
@ -976,12 +994,16 @@ final class GiftSetupScreenComponent: Component {
case let .starGift(gift, _):
subject = .starGift(gift: gift)
upgradeStars = gift.upgradeStars
releasedBy = gift.releasedBy
}
var peers: [EnginePeer] = [accountPeer]
if let peer = self.peerMap[component.peerId] {
peers.append(peer)
}
if let releasedBy, let peer = self.peerMap[releasedBy] {
peers.append(peer)
}
let introContentSize = self.introContent.update(
transition: transition,

View File

@ -160,6 +160,9 @@ private final class GiftViewSheetContent: CombinedComponent {
}
if case let .unique(gift) = arguments.gift {
if let releasedBy = gift.releasedBy {
peerIds.append(releasedBy)
}
if case let .peerId(peerId) = gift.owner {
peerIds.append(peerId)
}
@ -192,6 +195,9 @@ private final class GiftViewSheetContent: CombinedComponent {
})
}
} else if case let .generic(gift) = arguments.gift {
if let releasedBy = gift.releasedBy {
peerIds.append(releasedBy)
}
if arguments.canUpgrade || arguments.upgradeStars != nil {
self.sampleDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
|> deliverOnMainQueue).start(next: { [weak self] attributes in
@ -1527,6 +1533,9 @@ private final class GiftViewSheetContent: CombinedComponent {
let buttons = Child(ButtonsComponent.self)
let animation = Child(GiftCompositionComponent.self)
let title = Child(MultilineTextComponent.self)
let subtitle = Child(MultilineTextComponent.self)
let descriptionButton = Child(PlainButtonComponent.self)
let description = Child(MultilineTextComponent.self)
let transferButton = Child(PlainButtonComponent.self)
@ -1570,6 +1579,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
var titleString: String
var subtitleString: String?
var animationFile: TelegramMediaFile?
let stars: Int64
let convertStars: Int64?
@ -1592,6 +1602,7 @@ private final class GiftViewSheetContent: CombinedComponent {
var isSelfGift = false
var isChannelGift = false
var isMyUniqueGift = false
var releasedByPeer: EnginePeer?
if case let .soldOutGift(gift) = subject {
animationFile = gift.file
@ -1606,6 +1617,11 @@ private final class GiftViewSheetContent: CombinedComponent {
} else if let arguments = subject.arguments {
switch arguments.gift {
case let .generic(gift):
if let releasedBy = gift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
subtitleString = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string
releasedByPeer = peer
}
animationFile = gift.file
stars = gift.price
text = arguments.text
@ -2145,9 +2161,16 @@ private final class GiftViewSheetContent: CombinedComponent {
}
} else {
var descriptionText: String
var hasDescriptionButton = false
if let uniqueGift {
titleString = uniqueGift.title
descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))"
if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string
hasDescriptionButton = true
releasedByPeer = peer
}
} else if soldOut {
descriptionText = strings.Gift_View_UnavailableDescription
} else if upgraded {
@ -2197,7 +2220,7 @@ private final class GiftViewSheetContent: CombinedComponent {
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: titleString,
font: uniqueGift != nil ? Font.bold(20.0) : Font.bold(25.0),
font: Font.bold(20.0),
textColor: uniqueGift != nil ? .white : theme.actionSheet.primaryTextColor,
paragraphAlignment: .center
)),
@ -2208,13 +2231,59 @@ private final class GiftViewSheetContent: CombinedComponent {
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 190.0 : 177.0))
.position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 190.0 : 173.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
var descriptionOffset: CGFloat = 0.0
if let subtitleString {
let textColor = theme.actionSheet.secondaryTextColor
let textFont = Font.regular(13.0)
let subtitleAttributedString = parseMarkdownIntoAttributedString(
subtitleString,
attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.controlAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}),
textAlignment: .center
)
let subtitle = subtitle.update(
component: MultilineTextComponent(
text: .plain(subtitleAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
highlightColor: theme.actionSheet.controlAccentColor.withAlphaComponent(0.1),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak state] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let peer = releasedByPeer {
state?.openPeer(peer)
}
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(subtitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 210.0 : 196.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
descriptionOffset += subtitle.size.height
}
if !descriptionText.isEmpty {
let linkColor = theme.actionSheet.controlAccentColor
var linkColor = theme.actionSheet.controlAccentColor
if hasDescriptionButton {
linkColor = UIColor.white
}
if state.cachedSmallStarImage == nil || state.cachedSmallStarImage?.1 !== environment.theme {
state.cachedSmallStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/ButtonStar"), color: .white)!, theme)
}
@ -2226,7 +2295,11 @@ private final class GiftViewSheetContent: CombinedComponent {
let textColor: UIColor
if let _ = uniqueGift {
textFont = Font.regular(13.0)
textColor = vibrantColor
if hasDescriptionButton {
textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.4)
} else {
textColor = vibrantColor
}
} else {
textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0)
textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor
@ -2245,6 +2318,7 @@ private final class GiftViewSheetContent: CombinedComponent {
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
}
let description = description.update(
component: MultilineTextComponent(
text: .plain(attributedString),
@ -2254,14 +2328,14 @@ private final class GiftViewSheetContent: CombinedComponent {
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak state] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
state?.openStarsIntro()
}
}
@ -2270,11 +2344,38 @@ private final class GiftViewSheetContent: CombinedComponent {
transition: .immediate
)
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + description.size.height / 2.0))
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
if hasDescriptionButton {
let descriptionButton = descriptionButton.update(
component: PlainButtonComponent(
content: AnyComponent(
RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5)
),
effectAlignment: .center,
action: { [weak state] in
if let releasedByPeer {
state?.openPeer(releasedByPeer)
}
},
animateScale: false
),
environment: {},
availableSize: CGSize(width: description.size.width + 18.0, height: 19.0),
transition: .immediate
)
context.add(descriptionButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0 - UIScreenPixel))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
}
originY += descriptionOffset
if uniqueGift != nil {
originY += 16.0
} else {

View File

@ -7603,7 +7603,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return
}
if cachedUserData.callsPrivate {
self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: self.presentationData.strings.Call_ConnectionErrorTitle, text: self.presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
self.controller?.push(self.context.sharedContext.makeSendInviteLinkScreen(context: self.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: false,
premiumRequiredToContact: false
)], theme: self.presentationData.theme))
return
}

View File

@ -289,7 +289,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)
)
} else {
mainPeer = .single(nil)
mainPeer = .single(EnginePeer.channel(peer))
}
let _ = (mainPeer |> deliverOnMainQueue).startStandalone(next: { [weak self] mainPeer in

View File

@ -118,6 +118,9 @@ private final class SendInviteLinkScreenComponent: Component {
private var topOffsetDistance: CGFloat?
private var createCallDisposable: Disposable?
private var isInProgress: Bool = false
override init(frame: CGRect) {
self.bottomOverscrollLimit = 200.0
@ -178,6 +181,10 @@ private final class SendInviteLinkScreenComponent: Component {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.createCallDisposable?.dispose()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
@ -790,8 +797,18 @@ private final class SendInviteLinkScreenComponent: Component {
}
}
}
case .groupCall:
text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink
case let .groupCall(groupCall):
switch groupCall {
case .create:
if component.peers.count == 1 {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
text = environment.strings.SendInviteLink_TextCallsRestrictedSendOneInviteLink(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string
} else {
text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink
}
case .existing:
text = environment.strings.SendInviteLink_TextCallsRestrictedSendInviteLink
}
}
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)
@ -828,79 +845,81 @@ private final class SendInviteLinkScreenComponent: Component {
var itemsHeight: CGFloat = 0.0
var validIds: [AnyHashable] = []
for i in 0 ..< component.peers.count {
let peer = component.peers[i]
for _ in 0 ..< 1 {
//let id: AnyHashable = AnyHashable("\(peer.id)_\(j)")
let id = AnyHashable(peer.peer.id)
validIds.append(id)
if case .chat = component.subject {
for i in 0 ..< component.peers.count {
let peer = component.peers[i]
let item: ComponentView<Empty>
var itemTransition = transition
if let current = self.items[id] {
item = current
} else {
itemTransition = .immediate
item = ComponentView()
self.items[id] = item
}
let itemSubtitle: PeerListItemComponent.Subtitle
let canBeSelected : Bool
switch component.subject {
case let .chat(_, link):
canBeSelected = link != nil && !peer.premiumRequiredToContact
case .groupCall:
canBeSelected = true
}
if peer.premiumRequiredToContact {
itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock)
} else {
itemSubtitle = .presence(component.peerPresences[peer.peer.id])
}
let itemSize = item.update(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
sideInset: 0.0,
title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
subtitle: itemSubtitle,
peer: peer.peer,
selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)),
hasNext: i != component.peers.count - 1,
action: { [weak self] peer in
guard let self else {
return
}
if !canBeSelected {
return
}
if self.selectedItems.contains(peer.id) {
self.selectedItems.remove(peer.id)
} else {
self.selectedItems.insert(peer.id)
}
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)))
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize)
if let itemView = item.view {
if itemView.superview == nil {
self.itemContainerView.addSubview(itemView)
for _ in 0 ..< 1 {
//let id: AnyHashable = AnyHashable("\(peer.id)_\(j)")
let id = AnyHashable(peer.peer.id)
validIds.append(id)
let item: ComponentView<Empty>
var itemTransition = transition
if let current = self.items[id] {
item = current
} else {
itemTransition = .immediate
item = ComponentView()
self.items[id] = item
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
let itemSubtitle: PeerListItemComponent.Subtitle
let canBeSelected : Bool
switch component.subject {
case let .chat(_, link):
canBeSelected = link != nil && !peer.premiumRequiredToContact
case .groupCall:
canBeSelected = true
}
if peer.premiumRequiredToContact {
itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock)
} else {
itemSubtitle = .presence(component.peerPresences[peer.peer.id])
}
let itemSize = item.update(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
sideInset: 0.0,
title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
subtitle: itemSubtitle,
peer: peer.peer,
selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)),
hasNext: i != component.peers.count - 1,
action: { [weak self] peer in
guard let self else {
return
}
if !canBeSelected {
return
}
if self.selectedItems.contains(peer.id) {
self.selectedItems.remove(peer.id)
} else {
self.selectedItems.insert(peer.id)
}
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)))
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize)
if let itemView = item.view {
if itemView.superview == nil {
self.itemContainerView.addSubview(itemView)
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
itemsHeight += itemSize.height
singleItemHeight = itemSize.height
}
itemsHeight += itemSize.height
singleItemHeight = itemSize.height
}
}
var removeIds: [AnyHashable] = []
@ -917,9 +936,13 @@ private final class SendInviteLinkScreenComponent: Component {
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
contentHeight += itemsHeight
contentHeight += 24.0
initialContentHeight += 24.0
if itemsHeight != 0.0 {
contentHeight += itemsHeight
contentHeight += 24.0
initialContentHeight += 24.0
} else {
contentHeight += 4.0
}
let actionButtonTitle: String
let actionButtonBadge: String?
@ -933,7 +956,7 @@ private final class SendInviteLinkScreenComponent: Component {
actionButtonBadge = (self.selectedItems.isEmpty || link == nil) ? nil : "\(self.selectedItems.count)"
case .groupCall:
actionButtonTitle = environment.strings.SendInviteLink_ActionInvite
actionButtonBadge = self.selectedItems.isEmpty ? nil : "\(self.selectedItems.count)"
actionButtonBadge = nil
}
let actionButtonSize = actionButton.update(
transition: transition,
@ -949,6 +972,7 @@ private final class SendInviteLinkScreenComponent: Component {
animationName: nil,
iconPosition: .right,
iconSpacing: 4.0,
isLoading: self.isInProgress,
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
@ -958,8 +982,62 @@ private final class SendInviteLinkScreenComponent: Component {
switch component.subject {
case let .chat(_, linkValue):
link = linkValue
case let .groupCall(linkValue):
link = linkValue
case let .groupCall(groupCall):
switch groupCall {
case .create:
self.isInProgress = true
self.state?.updated(transition: .immediate)
self.createCallDisposable = (component.context.engine.calls.createConferenceCall()
|> deliverOnMainQueue).startStrict(next: { [weak self] call in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
if self.selectedItems.isEmpty {
controller.dismiss()
} else {
let link = call.link
let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) }
self.presentPaidMessageAlertIfNeeded(
peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) },
requiresStars: component.sendPaidMessageStars,
completion: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
for peerId in Array(self.selectedItems) {
var messageAttributes: [EngineMessage.Attribute] = []
if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] {
messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone()
}
let text: String
if selectedPeers.count == 1 {
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else if selectedPeers.count == 2 {
text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
} else {
text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
controller.dismiss()
}
)
}
})
return
case let .existing(linkValue):
link = linkValue
}
}
if self.selectedItems.isEmpty {
@ -1082,7 +1160,6 @@ private final class SendInviteLinkScreenComponent: Component {
public class SendInviteLinkScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let link: String?
private let peers: [TelegramForbiddenInvitePeer]
private var isDismissed: Bool = false
@ -1092,17 +1169,6 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer {
public init(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme? = nil) {
self.context = context
switch subject {
case let .chat(peer, link):
var link = link
if link == nil, let addressName = peer.addressName {
link = "https://t.me/\(addressName)"
}
self.link = link
case let .groupCall(link):
self.link = link
}
#if DEBUG && false
var peers = peers

View File

@ -460,7 +460,8 @@ final class UserAppearanceScreenComponent: Component {
],
availability: StarGift.UniqueGift.Availability(issued: 0, total: 0),
giftAddress: nil,
resellStars: nil
resellStars: nil,
releasedBy: nil
)
signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate)
} else {

View File

@ -603,7 +603,7 @@ private final class SheetContent: CombinedComponent {
}
)).string
} else {
timeString = "Anytime"
timeString = strings.SuggestPost_SetTimeFormat_Any
}
let timestampSection = timestampSection.update(
@ -618,7 +618,7 @@ private final class SheetContent: CombinedComponent {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Time",
string: environment.strings.SuggestPost_SetTime_Label,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
@ -775,44 +775,54 @@ private final class SheetContent: CombinedComponent {
completion(amount.value)
case let .paidMessages(_, _, _, _, completion):
completion(amount.value)
case let .suggestedPost(_, _, _, completion):
switch state.currency {
case .stars:
if let balance = state.starsBalance, amount > balance {
guard let starsContext = state.context.starsContext else {
return
}
let _ = (state.context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak controller, weak state] options in
guard let controller, let state else {
case let .suggestedPost(mode, _, _, completion):
let needsLocalBalance: Bool
switch mode {
case .admin:
needsLocalBalance = false
case let .sender(_, isFromAdmin):
needsLocalBalance = !isFromAdmin
}
if needsLocalBalance {
switch state.currency {
case .stars:
if let balance = state.starsBalance, amount > balance {
guard let starsContext = state.context.starsContext else {
return
}
let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in
})
controller.push(purchaseController)
})
return
}
case .ton:
if let balance = state.tonBalance, amount > balance {
let needed = amount - balance
var fragmentUrl = "https://fragment.com/ads/topup"
if let data = state.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value
}
controller.push(BalanceNeededScreen(
context: state.context,
amount: needed,
buttonAction: { [weak state] in
guard let state else {
let _ = (state.context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak controller, weak state] options in
guard let controller, let state else {
return
}
state.context.sharedContext.applicationBindings.openUrl(fragmentUrl)
let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in
})
controller.push(purchaseController)
})
return
}
case .ton:
if let balance = state.tonBalance, amount > balance {
let needed = amount - balance
var fragmentUrl = "https://fragment.com/ads/topup"
if let data = state.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value
}
))
return
controller.push(BalanceNeededScreen(
context: state.context,
amount: needed,
buttonAction: { [weak state] in
guard let state else {
return
}
state.context.sharedContext.applicationBindings.openUrl(fragmentUrl)
}
))
return
}
}
}

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import Intents
import TelegramPresentationData
import TelegramUIPreferences

View File

@ -1811,7 +1811,7 @@ extension ChatControllerImpl {
return
}
if actions.options.contains(.deleteGlobally), let message = messages.first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute {
if actions.options.contains(.deleteGlobally), let message = messages.first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute, message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 {
let commit = { [weak self] in
guard let self else {
return

View File

@ -122,6 +122,7 @@ import PeerNameColorScreen
import ChatEmptyNode
import ChatMediaInputStickerGridItem
import AdsInfoScreen
import Photos
extension ChatControllerImpl {
public func presentThemeSelection() {

View File

@ -989,27 +989,64 @@ extension ChatControllerImpl {
let subject: StarsWithdrawalScreenSubject
if postSuggestionState.editingOriginalMessageId != nil {
subject = .postSuggestionModification(current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars), timestamp: postSuggestionState.timestamp, completion: { [weak self] price, timestamp in
guard let self else {
return
var isFromAdmin = false
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
isFromAdmin = true
}
let price: CurrencyAmount? = price.amount == .zero ? nil : price
self.updateChatPresentationInterfaceState(interactive: true, { state in
var state = state
state = state.updatedInterfaceState { interfaceState in
var interfaceState = interfaceState
interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState(
editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId,
price: price,
timestamp: timestamp
))
return interfaceState
}
if isFromAdmin {
subject = .postSuggestionModification(current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars), timestamp: postSuggestionState.timestamp, completion: { [weak self] price, timestamp in
guard let self else {
return
}
return state
let price: CurrencyAmount? = price.amount == .zero ? nil : price
self.updateChatPresentationInterfaceState(interactive: true, { state in
var state = state
state = state.updatedInterfaceState { interfaceState in
var interfaceState = interfaceState
interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState(
editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId,
price: price,
timestamp: timestamp
))
return interfaceState
}
return state
})
})
})
} else {
subject = .postSuggestion(
channel: .channel(channel),
isFromAdmin: false,
current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars),
timestamp: postSuggestionState.timestamp,
completion: { [weak self] price, timestamp in
guard let self else {
return
}
let price: CurrencyAmount? = price.amount == .zero ? nil : price
self.updateChatPresentationInterfaceState(interactive: true, { state in
var state = state
state = state.updatedInterfaceState { interfaceState in
var interfaceState = interfaceState
interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState(
editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId,
price: price,
timestamp: timestamp
))
return interfaceState
}
return state
})
}
)
}
} else {
var isFromAdmin = false
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {

View File

@ -138,6 +138,8 @@ import QuickShareScreen
import PostSuggestionsSettingsScreen
import PromptUI
import SuggestedPostApproveAlert
import AVFoundation
import BalanceNeededScreen
public final class ChatControllerOverlayPresentationData {
public let expandData: (ASDisplayNode?, () -> Void)
@ -2365,70 +2367,136 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
strongSelf.present(promptController, in: .window(.root))
case 1:
var timestamp: Int32?
var funds: (amount: CurrencyAmount, commissionPermille: Int)?
if let amount = attribute.amount {
let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille))
}
var isAdmin = false
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
isAdmin = true
}
if attribute.timestamp == nil {
let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true, isAdmin: isAdmin, funds: funds), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in
guard let strongSelf else {
return
}
progress?.set(.single(true))
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time != 0 ? time : nil)).startStandalone(completed: {
progress?.set(.single(false))
})
})
strongSelf.view.endEditing(true)
strongSelf.present(controller, in: .window(.root))
timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60
} else {
var textString: String
if isAdmin {
textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationText(message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "").string
if let funds {
var commissionValue: String
commissionValue = "\(Double(funds.commissionPermille) * 0.1)"
if commissionValue.hasSuffix(".0") {
commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -2)])
} else if commissionValue.hasSuffix(".00") {
commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -3)])
}
textString += "\n\n"
switch funds.amount.currency {
case .stars:
let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0
textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceStars("\(displayAmount)", "\(commissionValue)").string
case .ton:
let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0
textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceTon("\(displayAmount)", "\(commissionValue)").string
}
}
} else {
textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_UserConfirmationText
Task { @MainActor [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.present(SuggestedPostApproveAlert(presentationData: strongSelf.presentationData, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Title, text: textString, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Action, action: { [weak strongSelf] in
var timestamp: Int32?
var funds: (amount: CurrencyAmount, commissionPermille: Int)?
if let amount = attribute.amount {
let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille))
}
#if DEBUG
if "".isEmpty {
funds = nil
}
#endif
var isAdmin = false
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
isAdmin = true
}
if !isAdmin, let funds {
switch funds.amount.currency {
case .stars:
var balance: StarsAmount?
if let starsContext = strongSelf.context.starsContext {
let state = await (starsContext.state |> take(1) |> deliverOnMainQueue).get()
balance = state?.balance
}
if let balance, funds.amount.amount > balance {
guard let starsContext = strongSelf.context.starsContext else {
return
}
let _ = (strongSelf.context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in
guard let strongSelf else {
return
}
let purchaseController = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in
})
strongSelf.push(purchaseController)
})
return
}
case .ton:
var balance: StarsAmount?
if let tonContext = strongSelf.context.tonContext {
let state = await (tonContext.state |> take(1) |> deliverOnMainQueue).get()
balance = state?.balance
}
if let balance, funds.amount.amount > balance {
let needed = funds.amount.amount - balance
var fragmentUrl = "https://fragment.com/ads/topup"
if let data = strongSelf.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value
}
strongSelf.push(BalanceNeededScreen(
context: strongSelf.context,
amount: needed,
buttonAction: { [weak strongSelf] in
guard let strongSelf else {
return
}
strongSelf.context.sharedContext.applicationBindings.openUrl(fragmentUrl)
}
))
return
}
}
}
if attribute.timestamp == nil {
let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true, isAdmin: isAdmin, funds: funds), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in
guard let strongSelf else {
return
}
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone()
}),
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
], actionLayout: .vertical, parseMarkdown: true, toastText: funds?.amount.currency == .stars ? strongSelf.presentationData.strings.Chat_PostSuggestion_StarsDisclaimer : nil), in: .window(.root))
progress?.set(.single(true))
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time != 0 ? time : nil)).startStandalone(completed: {
progress?.set(.single(false))
})
})
strongSelf.view.endEditing(true)
strongSelf.present(controller, in: .window(.root))
timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60
} else {
var textString: String
if isAdmin {
textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationText(message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "").string
if let funds {
var commissionValue: String
commissionValue = "\(Double(funds.commissionPermille) * 0.1)"
if commissionValue.hasSuffix(".0") {
commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -2)])
} else if commissionValue.hasSuffix(".00") {
commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -3)])
}
textString += "\n\n"
switch funds.amount.currency {
case .stars:
let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0
textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceStars("\(displayAmount)", "\(commissionValue)").string
case .ton:
let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0
textString += strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_AdminConfirmationPriceTon("\(displayAmount)", "\(commissionValue)").string
}
}
} else {
textString = strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_UserConfirmationText
}
strongSelf.present(SuggestedPostApproveAlert(presentationData: strongSelf.presentationData, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Title, text: textString, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Chat_PostSuggestion_Approve_Action, action: { [weak strongSelf] in
guard let strongSelf else {
return
}
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone()
}),
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})
], actionLayout: .vertical, parseMarkdown: true, toastText: funds?.amount.currency == .stars ? strongSelf.presentationData.strings.Chat_PostSuggestion_StarsDisclaimer : nil), in: .window(.root))
}
}
case 2:
strongSelf.interfaceInteraction?.openSuggestPost(message, .default)
@ -2932,14 +3000,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return peerViewMainPeer(view)
}
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let peer = peer else {
guard let peer else {
return
}
if let cachedUserData = strongSelf.contentData?.state.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.push(strongSelf.context.sharedContext.makeSendInviteLinkScreen(context: strongSelf.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: false,
premiumRequiredToContact: false
)], theme: strongSelf.presentationData.theme))
return
}
@ -4991,6 +5061,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
self.dismissAllTooltips()
if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let _ = message.forwardInfo {
let controller = UndoOverlayController(
presentationData: self.presentationData,
content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: self.presentationData.strings.Chat_Todo_CompletionLimitedForward, customUndoText: nil, timeout: nil),
action: { _ in
return false
}
)
self.present(controller, in: .current)
return
}
guard self.presentationInterfaceState.subject != .scheduledMessages else {
self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.ScheduledMessages_TodoUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return

View File

@ -389,7 +389,7 @@ extension ChatControllerImpl {
return
}
if let message = messages.values.compactMap({ $0 }).first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute {
if let message = messages.values.compactMap({ $0 }).first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute, message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 {
let commit = { [weak self] in
guard let self else {
return

View File

@ -35,6 +35,7 @@ import CameraScreen
import ShareController
import ComposeTodoScreen
import ComposePollUI
import Photos
extension ChatControllerImpl {
enum AttachMenuSubject {
@ -2116,6 +2117,20 @@ extension ChatControllerImpl {
return
}
guard self.context.isPremium else {
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .todo, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .todo, forceDark: false, dismissed: nil)
replaceImpl?(controller)
}, dismissed: nil)
replaceImpl = { [weak demoController] c in
demoController?.replace(with: c)
}
self.push(demoController)
return
}
let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message)
let controller = ComposeTodoScreen(

View File

@ -124,7 +124,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo
if let _ = attribute as? InlineBotMessageAttribute {
hasUneditableAttributes = true
break
} else if let _ = attribute as? PublishedSuggestedPostMessageAttribute {
} else if let _ = attribute as? PublishedSuggestedPostMessageAttribute, message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 {
hasUneditableAttributes = true
break
}
@ -1924,7 +1924,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}), false))
} else if !isUnremovableAction {
var iconName: String = isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"
if message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) {
if message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) && message.timestamp > Int32(Date().timeIntervalSince1970) - 60 * 60 * 24 {
iconName = "Chat/Context Menu/DeletePaid"
}

View File

@ -21,12 +21,11 @@ import ComponentFlow
import MediaScrubberComponent
import AnimatedCountLabelNode
//Xcode 16
#if canImport(ContactProvider)
extension AudioWaveformNode: @retroactive CustomMediaPlayerScrubbingForegroundNode {
#if SWIFT_PACKAGE
extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode {
}
#else
extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode {
extension AudioWaveformNode: @retroactive CustomMediaPlayerScrubbingForegroundNode {
}
#endif

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SSignalKit
import SwiftSignalKit
import Postbox
import TelegramCore
@ -20,6 +21,7 @@ import LegacyMediaPickerUI
import TextFormat
import AvatarEditorScreen
import OldChannelsController
import AVFoundation
private struct CreateChannelArguments {
let context: AccountContext

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SSignalKit
import SwiftSignalKit
import Postbox
import TelegramCore
@ -33,6 +34,7 @@ import TextFormat
import AvatarEditorScreen
import SendInviteLinkScreen
import OldChannelsController
import AVFoundation
private struct CreateGroupArguments {
let context: AccountContext

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import AsyncDisplayKit

View File

@ -2007,7 +2007,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
parentController.present(textAlertController(context: context, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
parentController.push(context.sharedContext.makeSendInviteLinkScreen(context: context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer(
peer: EnginePeer(peer),
canInviteWithPremium: false,
premiumRequiredToContact: false
)], theme: presentationData.theme))
return
}

View File

@ -775,18 +775,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
}
//Xcode 16
#if canImport(ContactProvider)
extension MediaEditorScreenImpl.Result: @retroactive MediaEditorScreenResult {
public var target: Stories.PendingTarget {
if let sendAsPeerId = self.options.sendAsPeerId {
return .peer(sendAsPeerId)
} else {
return .myStories
}
}
}
#else
#if SWIFT_PACKAGE
extension MediaEditorScreenImpl.Result: MediaEditorScreenResult {
public var target: Stories.PendingTarget {
if let sendAsPeerId = self.options.sendAsPeerId {
@ -796,4 +785,14 @@ extension MediaEditorScreenImpl.Result: MediaEditorScreenResult {
}
}
}
#else
extension MediaEditorScreenImpl.Result: @retroactive MediaEditorScreenResult {
public var target: Stories.PendingTarget {
if let sendAsPeerId = self.options.sendAsPeerId {
return .peer(sendAsPeerId)
} else {
return .myStories
}
}
}
#endif

View File

@ -588,6 +588,8 @@ private let textUrlEdgeCharacters: CharacterSet = {
var set: CharacterSet = .alphanumerics
set.formUnion(.symbols)
set.formUnion(.punctuationCharacters)
set.remove("(")
set.remove(")")
return set
}()

View File

@ -153,7 +153,8 @@ objc_library(
"-DRTC_ENABLE_VP9",
"-DTGVOIP_NAMESPACE=tgvoip_webrtc",
"-std=c++17",
"-Werror",
"-w",
#"-Werror",
] + optimization_flags,
includes = [
"PublicHeaders",

@ -1 +1 @@
Subproject commit a8accf0f9058a8c3d01104225349c046b34a3e3b
Subproject commit 47d35343336fc93c012f15d104d0eb521a9f0f1f

View File

@ -3061,7 +3061,7 @@ objc_library(
"webrtc/rtc_base/system/gcd_helpers.m",
],
copts = [
"-Wno-all",
"-w",
"-Ithird-party/webrtc/webrtc/",
"-Ithird-party/webrtc/webrtc/sdk/objc",
"-Ithird-party/webrtc/webrtc/sdk/objc/base",
@ -3124,7 +3124,7 @@ objc_library(
module_name = "webrtc",
srcs = combined_sources,
copts = [
"-Wno-all",
"-w",
"-Ithird-party/webrtc/libsrtp/third_party/libsrtp/include",
"-Ithird-party/webrtc/libsrtp/third_party/libsrtp/crypto/include",
"-Ithird-party/webrtc/libsrtp",

View File

@ -1,5 +1,5 @@
{
"app": "11.13",
"app": "11.13.1",
"xcode": "16.2",
"bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3",
"macos": "15"