Various improvements

This commit is contained in:
Isaac 2025-07-01 14:57:33 +02:00
parent deed7905b6
commit adbc7bad61
40 changed files with 319 additions and 152 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

@ -14542,8 +14542,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";

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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,130 @@ 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))
}
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)

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 {

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

@ -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