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

This commit is contained in:
Ilya Laktyushin 2025-06-28 23:51:21 +02:00
commit 09b7e77e88
139 changed files with 1627 additions and 190 deletions

View File

@ -1782,10 +1782,16 @@ ios_application(
#"//submodules/Display",
#"//submodules/TelegramCore",
#"//submodules/FFMpegBinding",
"//third-party/webrtc",
"//third-party/webrtc:webrtc_objc",
#"//third-party/webrtc",
#"//third-party/webrtc:webrtc_objc",
#"//submodules/AsyncDisplayKit",
#"//submodules/ObjCRuntimeUtils",
#"//submodules/OpusBinding",
#"//third-party/boringssl:ssl",
#"//third-party/boringssl:crypto",
#"//submodules/TelegramVoip",
#"//third-party/libprisma",
"//submodules/TelegramUI",
],
)

View File

@ -278,6 +278,9 @@ def _collect_spm_modules_impl(target, ctx):
if SPMModulesInfo in dep:
# Merge the modules dictionaries
for label, info in dep[SPMModulesInfo].modules.items():
if label in all_modules:
if all_modules[label]["path"] != info["path"]:
fail("Duplicate module name: {}".format(label))
all_modules[label] = info
# Add transitive sources depset from dependency to the list
dep_transitive_sources_list.append(dep[SPMModulesInfo].transitive_sources)

View File

@ -45,6 +45,29 @@ def escape_swift_string_literal_component(text: str) -> str:
# For non-define flags or defines without shell quoting, just escape for Swift string literal
return text.replace('\\', '\\\\').replace('"', '\\"')
# Parses -D flag into a tuple of (define_flag, define_value)
# Example: flag="ABC" -> (ABC, None)
# Example: flag="ABC=123" -> (ABC, 123)
# Example: flag="ABC=\"str\"" -> (ABC, "str")
def parse_define_flag(flag: str) -> tuple[str, str | None]:
if flag.startswith("-D"):
define_part = flag[2:]
else:
define_part = flag
# Check if there's an assignment
if "=" in define_part:
key, value = define_part.split("=", 1) # Split on first = only
# Handle quoted values - remove surrounding quotes if present
if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
value = value[1:-1] # Remove quotes
return (key, value)
else:
# No assignment, just a flag name
return (define_part, None)
parsed_modules = {}
for name, module in sorted(modules.items()):
is_empty = False
@ -60,28 +83,143 @@ for name, module in sorted(modules.items()):
"is_empty": is_empty,
}
spm_products = []
spm_targets = []
module_to_source_files = dict()
modulemaps = dict()
combined_lines = []
combined_lines.append("// swift-tools-version: 6.0")
combined_lines.append("// The swift-tools-version declares the minimum version of Swift required to build this package.")
combined_lines.append("")
combined_lines.append("import PackageDescription")
combined_lines.append("import Foundation")
combined_lines.append("""
func parseProduct(product: [String: Any]) -> Product {
let name = product[\"name\"] as! String
let targets = product[\"targets\"] as! [String]
return .library(name: name, targets: targets)
}""")
combined_lines.append("""
func parseTarget(target: [String: Any]) -> Target {
let name = target["name"] as! String
let dependencies = target["dependencies"] as! [String]
var swiftSettings: [SwiftSetting]?
if let swiftSettingList = target["swiftSettings"] as? [[String: Any]] {
var swiftSettingsValue: [SwiftSetting] = []
swiftSettingsValue.append(.swiftLanguageMode(.v5))
for swiftSetting in swiftSettingList {
if swiftSetting["type"] as! String == "define" {
swiftSettingsValue.append(.define(swiftSetting["name"] as! String))
} else if swiftSetting["type"] as! String == "unsafeFlags" {
swiftSettingsValue.append(.unsafeFlags(swiftSetting["flags"] as! [String]))
} else {
print("Unknown swift setting type: \\(swiftSetting["type"] as! String)")
preconditionFailure("Unknown swift setting type: \\(swiftSetting["type"] as! String)")
}
}
swiftSettings = swiftSettingsValue
}
var cSettings: [CSetting]?
if let cSettingList = target["cSettings"] as? [[String: Any]] {
var cSettingsValue: [CSetting] = []
for cSetting in cSettingList {
if cSetting["type"] as! String == "define" {
if let value = cSetting["value"] as? String {
cSettingsValue.append(.define(cSetting["name"] as! String, to: value))
} else {
cSettingsValue.append(.define(cSetting["name"] as! String))
}
} else if cSetting["type"] as! String == "unsafeFlags" {
cSettingsValue.append(.unsafeFlags(cSetting["flags"] as! [String]))
} else {
print("Unknown c setting type: \\(cSetting["type"] as! String)")
preconditionFailure("Unknown c setting type: \\(cSetting["type"] as! String)")
}
}
cSettings = cSettingsValue
}
var cxxSettings: [CXXSetting]?
if let cxxSettingList = target["cxxSettings"] as? [[String: Any]] {
var cxxSettingsValue: [CXXSetting] = []
for cxxSetting in cxxSettingList {
if cxxSetting["type"] as! String == "define" {
if let value = cxxSetting["value"] as? String {
cxxSettingsValue.append(.define(cxxSetting["name"] as! String, to: value))
} else {
cxxSettingsValue.append(.define(cxxSetting["name"] as! String))
}
} else if cxxSetting["type"] as! String == "unsafeFlags" {
cxxSettingsValue.append(.unsafeFlags(cxxSetting["flags"] as! [String]))
} else {
print("Unknown cxx setting type: \\(cxxSetting["type"] as! String)")
preconditionFailure("Unknown cxx setting type: \\(cxxSetting["type"] as! String)")
}
}
cxxSettings = cxxSettingsValue
}
var linkerSettings: [LinkerSetting]?
if let linkerSettingList = target["linkerSettings"] as? [[String: Any]] {
var linkerSettingsValue: [LinkerSetting] = []
for linkerSetting in linkerSettingList {
if linkerSetting["type"] as! String == "framework" {
linkerSettingsValue.append(.linkedFramework(linkerSetting["name"] as! String))
} else if linkerSetting["type"] as! String == "library" {
linkerSettingsValue.append(.linkedLibrary(linkerSetting["name"] as! String))
} else {
print("Unknown linker setting type: \\(linkerSetting["type"] as! String)")
preconditionFailure("Unknown linker setting type: \\(linkerSetting["type"] as! String)")
}
}
linkerSettings = linkerSettingsValue
}
return .target(
name: name,
dependencies: dependencies.map({ .target(name: $0) }),
path: (target["path"] as? String)!,
exclude: target["exclude"] as? [String] ?? [],
sources: sourceFileMap[name]!,
resources: nil,
publicHeadersPath: target["publicHeadersPath"] as? String,
packageAccess: true,
cSettings: cSettings,
cxxSettings: cxxSettings,
swiftSettings: swiftSettings,
linkerSettings: linkerSettings,
plugins: nil
)
}
""")
combined_lines.append("")
combined_lines.append("let packageData: [String: Any] = try! JSONSerialization.jsonObject(with: Data(contentsOf: URL(fileURLWithPath: \"PackageData.json\")), options: []) as! [String: Any]")
combined_lines.append("let sourceFileMap: [String: [String]] = packageData[\"sourceFileMap\"] as! [String: [String]]")
combined_lines.append("let products: [Product] = (packageData[\"products\"] as! [[String: Any]]).map(parseProduct)")
combined_lines.append("let targets: [Target] = (packageData[\"targets\"] as! [[String: Any]]).map(parseTarget)")
combined_lines.append("")
combined_lines.append("let package = Package(")
combined_lines.append(" name: \"Telegram\",")
combined_lines.append(" platforms: [")
combined_lines.append(" .iOS(.v12)")
combined_lines.append(" .iOS(.v13)")
combined_lines.append(" ],")
combined_lines.append(" products: [")
combined_lines.append(" products: products,")
for name, module in sorted(modules.items()):
if parsed_modules[name]["is_empty"]:
continue
if module["type"] == "objc_library" or module["type"] == "swift_library" or module["type"] == "cc_library":
combined_lines.append(" .library(name: \"%s\", targets: [\"%s\"])," % (module["name"], module["name"]))
spm_products.append({
"name": module["name"],
"targets": [module["name"]],
})
combined_lines.append(" ],")
combined_lines.append(" targets: [")
combined_lines.append(" targets: targets,")
for name, module in sorted(modules.items()):
if parsed_modules[name]["is_empty"]:
@ -89,22 +227,16 @@ for name, module in sorted(modules.items()):
module_type = module["type"]
if module_type == "objc_library" or module_type == "cc_library" or module_type == "swift_library":
combined_lines.append(" .target(")
combined_lines.append(" name: \"%s\"," % name)
spm_target = dict()
relative_module_path = module["path"] + "/Module_" + name
spm_target["name"] = name
relative_module_path = module["path"]
module_directory = spm_files_dir + "/" + relative_module_path
os.makedirs(module_directory, exist_ok=True)
module_sources_directory = module_directory + "/Sources"
if not os.path.exists(module_sources_directory):
os.makedirs(module_sources_directory)
module_public_includes_directory = module_directory + "/PublicIncludes"
if not os.path.exists(module_public_includes_directory):
os.makedirs(module_public_includes_directory)
module_public_headers_prefix = None
module_public_headers_prefix = ""
if module_type == "objc_library" or module_type == "cc_library":
if len(module["includes"]) > 1:
print("{}: Multiple includes are not yet supported: {}".format(name, module["includes"]))
sys.exit(1)
@ -115,20 +247,17 @@ for name, module in sorted(modules.items()):
module_public_headers_prefix = include_directory
break
combined_lines.append(" dependencies: [")
spm_target["dependencies"] = []
for dep in module["deps"]:
if not parsed_modules[dep]["is_empty"]:
combined_lines.append(" .target(name: \"%s\")," % dep)
combined_lines.append(" ],")
spm_target["dependencies"].append(dep)
# All modules now use the symlinked directory path
combined_lines.append(" path: \"%s\"," % relative_module_path)
spm_target["path"] = relative_module_path
# Since we control the entire directory structure, we don't need exclude logic
combined_lines.append(" exclude: [")
combined_lines.append(" ],")
include_source_files = []
exclude_source_files = []
public_include_files = []
combined_lines.append(" sources: [")
for source in module["sources"] + module.get("hdrs", []) + module.get("textual_hdrs", []):
# Process all sources (both regular and generated) with symlinks
if source.startswith("bazel-out/"):
@ -146,15 +275,7 @@ for name, module in sorted(modules.items()):
source_file_name = source[len(module["path"]) + 1:]
# Create symlink for this source file
is_public_include = False
if module_public_headers_prefix is not None:
if source_file_name.startswith(module_public_headers_prefix):
symlink_location = os.path.join(module_public_includes_directory, source_file_name[len(module_public_headers_prefix) + 1:])
#print("{}: Public include: {}".format(source_file_name, symlink_location))
is_public_include = True
if not is_public_include:
symlink_location = os.path.join(module_sources_directory, source_file_name)
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)
@ -162,7 +283,7 @@ for name, module in sorted(modules.items()):
os.makedirs(symlink_parent)
# Calculate relative path from symlink back to original file
# Count directory depth: spm-files/module_name/... -> workspace root
# 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
@ -173,12 +294,39 @@ for name, module in sorted(modules.items()):
os.symlink(symlink_target, symlink_location)
# Add to sources list (exclude certain file types)
if not source.endswith(('.h', '.hpp', '.a', '.inc')):
combined_lines.append(" \"%s\"," % ("Sources/" + source_file_name))
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 name in module_to_source_files:
print(f"{name}: duplicate module")
sys.exit(1)
module_to_source_files[name] = include_source_files
ignore_sub_folders = []
for other_name, other_module in sorted(modules.items()):
if other_module["path"] != module["path"] and other_module["path"].startswith(module["path"] + "/"):
exclude_path = other_module["path"][len(module["path"]) + 1:]
ignore_sub_folders.append(exclude_path)
if len(ignore_sub_folders) != 0:
spm_target["exclude"] = ignore_sub_folders
modulemap_path = os.path.join(os.path.join(os.path.join(module_directory), module_public_headers_prefix), "module.modulemap")
if modulemap_path not in modulemaps:
modulemaps[modulemap_path] = []
modulemaps[modulemap_path].append({
"name": name,
"public_include_files": public_include_files
})
combined_lines.append(" ],")
if module_type == "objc_library" or module_type == "cc_library":
combined_lines.append(" publicHeadersPath: \"PublicIncludes\",")
if module_public_headers_prefix is not None and len(module_public_headers_prefix) != 0:
spm_target["publicHeadersPath"] = module_public_headers_prefix
else:
spm_target["publicHeadersPath"] = ""
if len(module["includes"]) > 1:
print("{}: Multiple includes are not yet supported: {}".format(name, module["includes"]))
@ -188,58 +336,56 @@ for name, module in sorted(modules.items()):
cxxopts = module.get("cxxopts", [])
if defines or copts or (module_public_headers_prefix is not None):
combined_lines.append(" cSettings: [")
spm_target["cSettings"] = []
if defines:
for define in defines:
if "=" in define:
print("{}: Defines with = are not yet supported: {}".format(name, define))
sys.exit(1)
else:
combined_lines.append(f' .define("{define}"),')
spm_target["cSettings"].append({
"type": "define",
"name": define
})
if copts:
combined_lines.append(" .unsafeFlags([")
unsafe_flags = []
for flag in copts:
if flag.startswith("-D"):
define_flag, define_value = parse_define_flag(flag)
if define_value is None:
spm_target["cSettings"].append({
"type": "define",
"name": define_flag
})
else:
spm_target["cSettings"].append({
"type": "define",
"name": define_flag,
"value": define_value
})
else:
escaped_flag = escape_swift_string_literal_component(flag)
combined_lines.append(f' "{escaped_flag}",')
if escaped_flag.startswith("-I"):
include_path = escaped_flag[2:]
print("{}: Include path: {}".format(name, include_path))
for another_module_name, another_module in sorted(modules.items()):
another_module_path = another_module["path"]
if include_path.startswith(another_module_path):
relative_module_include_path = include_path[len(another_module_path) + 1:]
#print(" {}: Matches module: {}".format(another_module_name, another_module_path))
combined_lines.append(f' "-I{another_module_path}/Sources/{relative_module_include_path}",')
another_module_public_headers_prefix = None
if len(another_module["includes"]) == 1:
for include_directory in another_module["includes"]:
if include_directory != ".":
another_module_public_headers_prefix = another_module_path + "/" + include_directory
print(" {}: Another module public include: {}".format(another_module_name, another_module_public_headers_prefix))
if another_module_public_headers_prefix is not None:
if include_path.startswith(another_module_public_headers_prefix):
relative_module_include_path = include_path[len(another_module_public_headers_prefix) + 1:]
print(" {}: Matches module public include: {}".format(another_module_name, another_module_public_headers_prefix))
combined_lines.append(f' -"-I{another_module_path}/PublicIncludes/{relative_module_include_path}",')
combined_lines.append(" ]),")
if module_public_headers_prefix is not None:
combined_lines.append(f" .headerSearchPath(\"{module_public_headers_prefix}\"),")
combined_lines.append(" ],")
unsafe_flags.append(escaped_flag)
spm_target["cSettings"].append({
"type": "unsafeFlags",
"flags": unsafe_flags
})
if defines or cxxopts: # Check for defines OR cxxopts
combined_lines.append(" cxxSettings: [")
spm_target["cxxSettings"] = []
if defines: # Add defines again if present, for C++ context
for define in defines:
if "=" in define:
print("{}: Defines with = are not yet supported: {}".format(name, define))
sys.exit(1)
else:
combined_lines.append(f' .define("{define}"),')
spm_target["cxxSettings"].append({
"type": "define",
"name": define
})
if cxxopts:
combined_lines.append(" .unsafeFlags([")
unsafe_flags = []
for flag in cxxopts:
if flag.startswith("-std=") and True:
if flag != "-std=c++17":
@ -248,17 +394,28 @@ for name, module in sorted(modules.items()):
else:
continue
escaped_flag = escape_swift_string_literal_component(flag)
combined_lines.append(f' "{escaped_flag}",')
combined_lines.append(" ])")
combined_lines.append(" ],")
unsafe_flags.append(escaped_flag)
spm_target["cxxSettings"].append({
"type": "unsafeFlags",
"flags": unsafe_flags
})
combined_lines.append(" linkerSettings: [")
spm_target["linkerSettings"] = []
if module_type == "objc_library":
for framework in module["sdk_frameworks"]:
combined_lines.append(" .linkedFramework(\"%s\")," % framework)
spm_target["linkerSettings"].append({
"type": "framework",
"name": framework
})
for dylib in module["sdk_dylibs"]:
combined_lines.append(" .linkedLibrary(\"%s\")," % dylib)
combined_lines.append(" ]")
spm_target["linkerSettings"].append({
"type": "library",
"name": dylib
})
spm_target["linkerSettings"].append({
"type": "library",
"name": dylib
})
elif module_type == "swift_library":
defines = module.get("defines", [])
@ -266,40 +423,63 @@ for name, module in sorted(modules.items()):
# Handle cSettings for defines if they exist
if defines:
combined_lines.append(" cSettings: [")
spm_target["cSettings"] = []
for define in defines:
combined_lines.append(f' .define("{define}"),')
combined_lines.append(" ],")
spm_target["cSettings"].append({
"type": "define",
"name": define
})
spm_target["swiftSettings"] = []
# Handle swiftSettings
combined_lines.append(" swiftSettings: [")
combined_lines.append(" .swiftLanguageMode(.v5),")
# Add defines to swiftSettings as simple .define("STRING") flags
if defines:
for define in defines:
# For Swift settings, the define is passed as a single string, e.g., "KEY=VALUE" or "FLAG"
escaped_define = escape_swift_string_literal_component(define) # Escape the whole define string
combined_lines.append(f' .define("{escaped_define}"),')
spm_target["swiftSettings"].append({
"type": "define",
"name": escaped_define
})
# Add copts (swiftc flags) to unsafeFlags in swiftSettings
if swift_copts:
combined_lines.append(" .unsafeFlags([")
unsafe_flags = []
for flag in swift_copts:
escaped_flag = escape_swift_string_literal_component(flag)
combined_lines.append(f' "{escaped_flag}",')
combined_lines.append(" ])")
combined_lines.append(" ]")
combined_lines.append(" ),")
unsafe_flags.append(escaped_flag)
spm_target["swiftSettings"].append({
"type": "unsafeFlags",
"flags": unsafe_flags
})
spm_targets.append(spm_target)
elif module["type"] == "root":
pass
else:
print("Unknown module type: {}".format(module["type"]))
sys.exit(1)
combined_lines.append(" ],")
combined_lines.append(" cxxLanguageStandard: .cxx17")
combined_lines.append(")")
combined_lines.append("")
with open("spm-files/Package.swift", "w") as f:
f.write("\n".join(combined_lines))
with open("spm-files/PackageData.json", "w") as f:
package_data = {
"sourceFileMap": module_to_source_files,
"products": spm_products,
"targets": spm_targets
}
json.dump(package_data, f, indent=4)
for modulemap_path, modulemap in modulemaps.items():
module_map_contents = ""
for module in modulemap:
module_map_contents += "module {} {{\n".format(module["name"])
for public_include_file in module["public_include_files"]:
module_map_contents += " header \"{}\"\n".format(public_include_file)
module_map_contents += "}\n"
with open(modulemap_path, "w") as f:
f.write(module_map_contents)

View File

@ -1501,13 +1501,15 @@ public struct StarsSubscriptionConfiguration {
return StarsSubscriptionConfiguration(
maxFee: 2500,
usdWithdrawRate: 1200,
tonUsdRate: 0,
paidMessageMaxAmount: 10000,
paidMessageCommissionPermille: 850,
paidMessagesAvailable: false,
starGiftResaleMinAmount: 125,
starGiftResaleMaxAmount: 3500,
starGiftCommissionPermille: 80,
channelMessageSuggestionCommissionPermille: 850,
channelMessageSuggestionStarsCommissionPermille: 850,
channelMessageSuggestionTonCommissionPermille: 850,
channelMessageSuggestionMaxStarsAmount: 10000,
channelMessageSuggestionMaxTonAmount: 10000000000000
)
@ -1515,38 +1517,44 @@ public struct StarsSubscriptionConfiguration {
public let maxFee: Int64
public let usdWithdrawRate: Int64
public let tonUsdRate: Int64
public let paidMessageMaxAmount: Int64
public let paidMessageCommissionPermille: Int32
public let paidMessagesAvailable: Bool
public let starGiftResaleMinAmount: Int64
public let starGiftResaleMaxAmount: Int64
public let starGiftCommissionPermille: Int32
public let channelMessageSuggestionCommissionPermille: Int32
public let channelMessageSuggestionStarsCommissionPermille: Int32
public let channelMessageSuggestionTonCommissionPermille: Int32
public let channelMessageSuggestionMaxStarsAmount: Int64
public let channelMessageSuggestionMaxTonAmount: Int64
fileprivate init(
maxFee: Int64,
usdWithdrawRate: Int64,
tonUsdRate: Int64,
paidMessageMaxAmount: Int64,
paidMessageCommissionPermille: Int32,
paidMessagesAvailable: Bool,
starGiftResaleMinAmount: Int64,
starGiftResaleMaxAmount: Int64,
starGiftCommissionPermille: Int32,
channelMessageSuggestionCommissionPermille: Int32,
channelMessageSuggestionStarsCommissionPermille: Int32,
channelMessageSuggestionTonCommissionPermille: Int32,
channelMessageSuggestionMaxStarsAmount: Int64,
channelMessageSuggestionMaxTonAmount: Int64
) {
self.maxFee = maxFee
self.usdWithdrawRate = usdWithdrawRate
self.tonUsdRate = tonUsdRate
self.paidMessageMaxAmount = paidMessageMaxAmount
self.paidMessageCommissionPermille = paidMessageCommissionPermille
self.paidMessagesAvailable = paidMessagesAvailable
self.starGiftResaleMinAmount = starGiftResaleMinAmount
self.starGiftResaleMaxAmount = starGiftResaleMaxAmount
self.starGiftCommissionPermille = starGiftCommissionPermille
self.channelMessageSuggestionCommissionPermille = channelMessageSuggestionCommissionPermille
self.channelMessageSuggestionStarsCommissionPermille = channelMessageSuggestionStarsCommissionPermille
self.channelMessageSuggestionTonCommissionPermille = channelMessageSuggestionTonCommissionPermille
self.channelMessageSuggestionMaxStarsAmount = channelMessageSuggestionMaxStarsAmount
self.channelMessageSuggestionMaxTonAmount = channelMessageSuggestionMaxTonAmount
}
@ -1555,6 +1563,7 @@ public struct StarsSubscriptionConfiguration {
if let data = appConfiguration.data {
let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee
let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate
let tonUsdRate = (data["ton_usd_rate"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate
let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount
let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille
let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable
@ -1562,20 +1571,23 @@ public struct StarsSubscriptionConfiguration {
let starGiftResaleMaxAmount = (data["stars_stargift_resale_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftResaleMaxAmount
let starGiftCommissionPermille = (data["stars_stargift_resale_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftCommissionPermille
let channelMessageSuggestionCommissionPermille = (data["stars_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionCommissionPermille
let channelMessageSuggestionStarsCommissionPermille = (data["stars_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionStarsCommissionPermille
let channelMessageSuggestionTonCommissionPermille = (data["ton_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionTonCommissionPermille
let channelMessageSuggestionMaxStarsAmount = (data["stars_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxStarsAmount
let channelMessageSuggestionMaxTonAmount = (data["ton_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxTonAmount
return StarsSubscriptionConfiguration(
maxFee: maxFee,
usdWithdrawRate: usdWithdrawRate,
tonUsdRate: tonUsdRate,
paidMessageMaxAmount: paidMessageMaxAmount,
paidMessageCommissionPermille: paidMessageCommissionPermille,
paidMessagesAvailable: paidMessagesAvailable,
starGiftResaleMinAmount: starGiftResaleMinAmount,
starGiftResaleMaxAmount: starGiftResaleMaxAmount,
starGiftCommissionPermille: starGiftCommissionPermille,
channelMessageSuggestionCommissionPermille: channelMessageSuggestionCommissionPermille,
channelMessageSuggestionStarsCommissionPermille: channelMessageSuggestionStarsCommissionPermille,
channelMessageSuggestionTonCommissionPermille: channelMessageSuggestionTonCommissionPermille,
channelMessageSuggestionMaxStarsAmount: channelMessageSuggestionMaxStarsAmount,
channelMessageSuggestionMaxTonAmount: channelMessageSuggestionMaxTonAmount
)

View File

@ -6,6 +6,7 @@ import AsyncDisplayKit
import YuvConversion
import MediaResources
import AnimationCompression
import UIKit
private let sharedQueue = Queue()

View File

@ -1,5 +1,6 @@
import Foundation
import SwiftSignalKit
import UIKit
import AsyncDisplayKit
public enum AnimationRendererFrameType {

View File

@ -8,6 +8,7 @@ import ManagedFile
import Accelerate
import TelegramCore
import WebPBinding
import UIKit
private let sharedStoreQueue = Queue.concurrentDefaultQueue()

View File

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

View File

@ -14,6 +14,7 @@ swift_library(
"//submodules/TelegramCore:TelegramCore",
"//submodules/Postbox:Postbox",
"//submodules/Display:Display",
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown",

View File

@ -22,6 +22,7 @@ import Markdown
import AlertUI
import InAppPurchaseManager
import ObjectiveC
import AVFoundation
private var ObjCKey_Delegate: Int?

View File

@ -4,6 +4,7 @@ import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SSignalKit
import SwiftSignalKit
import TelegramPresentationData
import LegacyComponents

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore

View File

@ -1,4 +1,6 @@
import Foundation
import AsyncDisplayKit
import UIKit
import Display
import TelegramCore
import SwiftSignalKit

View File

@ -3,6 +3,7 @@ import ComponentFlow
import Lottie
import AppBundle
import HierarchyTrackingLayer
import UIKit
import Display
import GZip

View File

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

View File

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

View File

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

View File

@ -3,9 +3,12 @@
#import <CommonCrypto/CommonCrypto.h>
NSData * _Nonnull CryptoMD5(const void * _Nonnull bytes, int count) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSMutableData *result = [[NSMutableData alloc] initWithLength:(NSUInteger)CC_MD5_DIGEST_LENGTH];
CC_MD5(bytes, (CC_LONG)count, result.mutableBytes);
return result;
#pragma clang diagnostic pop
}
NSData * _Nonnull CryptoSHA1(const void * _Nonnull bytes, int count) {
@ -32,6 +35,9 @@ NSData * _Nonnull CryptoSHA512(const void * _Nonnull bytes, int count) {
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@implementation IncrementalMD5
- (instancetype _Nonnull)init {
@ -56,6 +62,8 @@ NSData * _Nonnull CryptoSHA512(const void * _Nonnull bytes, int count) {
return result;
}
#pragma clang diagnostic pop
@end
NSData * _Nullable CryptoAES(bool encrypt, NSData * _Nonnull key, NSData * _Nonnull iv, NSData * _Nonnull data) {

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
public final class ContextContentContainerNode: ASDisplayNode {

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
open class ContextReferenceContentNode: ASDisplayNode {

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
open class ContextControllerSourceNode: ContextReferenceContentNode {

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import UIKitRuntimeUtils
public enum Keyboard {

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
public protocol MinimizedContainer: ASDisplayNode {

View File

@ -9,6 +9,9 @@ objc_library(
hdrs = glob([
"Sources/**/*.h",
]),
includes = [
"Sources",
],
sdk_dylibs = [
"libz",
],

View File

@ -1,5 +1,6 @@
import Foundation
import Display
import UIKit
import AsyncDisplayKit
import SwiftSignalKit

View File

@ -1,8 +1,12 @@
#import <Foundation/Foundation.h>
#import <SSignalKit/SSignalKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TGGifConverter : NSObject
+ (SSignal *)convertGifToMp4:(NSData *)data;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,3 +1,4 @@
#import <Foundation/Foundation.h>
#import <SSignalKit/SSignalKit.h>
@protocol TGMediaSelectableItem

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",

View File

@ -10,6 +10,7 @@ import DeviceAccess
import AccountContext
import LegacyUI
import SaveToCameraRoll
import Photos
public func defaultVideoPresetForContext(_ context: AccountContext) -> TGMediaVideoConversionPreset {
var networkType: NetworkType = .wifi

View File

@ -14,6 +14,7 @@ import MimeTypes
import LocalMediaResources
import LegacyUI
import TextFormat
import Photos
public func guessMimeTypeByFileExtension(_ ext: String) -> String {
return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary"

View File

@ -1,4 +1,5 @@
import LegacyComponents
import UIKit
import Display
import Postbox
import SwiftSignalKit
@ -13,6 +14,7 @@ import MediaEditor
import DrawingUI
import TelegramPresentationData
import AnimatedCountLabelNode
import CoreMedia
protocol LegacyPaintEntity {
var position: CGPoint { get }
@ -723,17 +725,16 @@ private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButt
}
}
//Xcode 16
#if canImport(ContactProvider)
extension SolidRoundedButtonView: @retroactive TGPhotoSolidRoundedButtonView {
public func updateWidth(_ width: CGFloat) {
let _ = self.updateLayout(width: width, transition: .immediate)
}
}
#else
#if SWIFT_PACKAGE
extension SolidRoundedButtonView: TGPhotoSolidRoundedButtonView {
public func updateWidth(_ width: CGFloat) {
let _ = self.updateLayout(width: width, transition: .immediate)
}
}
#else
extension SolidRoundedButtonView: @retroactive TGPhotoSolidRoundedButtonView {
public func updateWidth(_ width: CGFloat) {
let _ = self.updateLayout(width: width, transition: .immediate)
}
}
#endif

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SSignalKit
import SwiftSignalKit
import TelegramCore
import LegacyComponents
@ -8,6 +9,7 @@ import TelegramPresentationData
import DeviceAccess
import AccountContext
import LocalMediaResources
import Photos
public func legacyWallpaperPicker(context: AccountContext, presentationData: PresentationData, subject: DeviceAccessMediaLibrarySubject = .wallpaper) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {
return Signal { subscriber in

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SSignalKit
import SwiftSignalKit
import LegacyComponents
import TelegramPresentationData

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import TelegramCore
import SSignalKit
import SwiftSignalKit
import MtProtoKit
import Display

View File

@ -27,6 +27,7 @@ objc_library(
],
hdrs = glob([
"lottiecpp/PublicHeaders/**/*.h",
"lottiecpp/PublicHeaders/**/*.hpp",
]),
includes = [
"lottiecpp/PublicHeaders",

View File

@ -5,6 +5,7 @@ import Postbox
import TelegramCore
import FFMpegBinding
import RangeSet
import CoreMedia
private func FFMpegLookaheadReader_readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer<UInt8>?, bufferSize: Int32) -> Int32 {
let context = Unmanaged<FFMpegLookaheadReader>.fromOpaque(userData!).takeUnretainedValue()

View File

@ -4,6 +4,7 @@ import Display
import SwiftSignalKit
import RangeSet
import TextFormat
import UIKit
public enum MediaPlayerScrubbingNodeCap {
case square

View File

@ -1,6 +1,7 @@
import Foundation
import AsyncDisplayKit
import SwiftSignalKit
import UIKit
import Display
public enum MediaPlayerTimeTextNodeMode {

View File

@ -9,6 +9,7 @@ import SwiftSignalKit
import Postbox
import TelegramCore
import FFMpegBinding
import CoreMedia
private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer<UInt8>?, bufferSize: Int32) -> Int32 {
let context = Unmanaged<UniversalSoftwareVideoSourceImpl>.fromOpaque(userData!).takeUnretainedValue()

View File

@ -1,3 +1,4 @@
#import <Foundation/Foundation.h>
#import <EncryptionProvider/EncryptionProvider.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -11,6 +11,9 @@ objc_library(
includes = [
"Sources",
],
copts = [
"-Werror",
],
visibility = [
"//visibility:public",
],

View File

@ -127,7 +127,7 @@ static bool ProgressWindowIsLight = true;
[self dismiss:animated completion:nil];
}
- (void)dismiss:(bool)animated completion:(void (^)())completion
- (void)dismiss:(bool)animated completion:(void (^)(void))completion
{
if (animated)
{

View File

@ -117,8 +117,6 @@ static void drawSvgPath(CGContextRef context, NSString *path) {
}
}
static bool ProxyWindowIsLight = true;
@interface ProxySpinnerView : UIView
@property (nonatomic, copy) void (^onSuccess)(void);

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import LegacyComponents
import Display
import SSignalKit
import SwiftSignalKit
import Postbox
import TelegramCore

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
#import <Foundation/Foundation.h>
#import <SSignalKit/SSubscriber.h>
@interface SSignal : NSObject

View File

@ -1,3 +1,4 @@
#import <Foundation/Foundation.h>
#import <SSignalKit/SDisposable.h>
@interface SSubscriber : NSObject <SDisposable>

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",

View File

@ -10,6 +10,8 @@ import LocalMediaResources
import AVFoundation
import LegacyComponents
import ShareItemsImpl
import UIKit
import SSignalKit
public enum UnpreparedShareItemContent {
case contact(DeviceContactExtendedData)
@ -206,6 +208,13 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
}
}
if isGif {
#if DEBUG
let signal = SSignal(generator: { _ in
return SBlockDisposable(block: {})
})
let _ = signal.start(next: nil, error: nil, completed: nil)
#endif
let convertedData = Signal<(Data, CGSize, Double, Bool), NoError> { subscriber in
let disposable = MetaDisposable()
let signalDisposable = TGGifConverter.convertGif(toMp4: data).start(next: { next in

View File

@ -1,5 +1,6 @@
import Foundation
import AsyncDisplayKit
import UIKit
import Display
import GenerateStickerPlaceholderImage

View File

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

View File

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

View File

@ -15,6 +15,7 @@ objc_library(
],
copts = [
"-I{}/PublicHeaders/Stripe".format(package_name()),
"-Werror",
],
sdk_frameworks = [
"Foundation",

View File

@ -123,8 +123,6 @@ static NSString *const STPSDKVersion = @"9.1.0";
*/
+ (BOOL)canSubmitPaymentRequest:(PKPaymentRequest *)paymentRequest NS_AVAILABLE_IOS(8_0);
+ (BOOL)deviceSupportsApplePay;
/**
* A convenience method to return a `PKPaymentRequest` with sane default values. You will still need to configure the `paymentSummaryItems` property to indicate
*what the user is purchasing, as well as the optional `requiredShippingAddressFields`, `requiredBillingAddressFields`, and `shippingMethods` properties to indicate
@ -201,8 +199,6 @@ typedef void (^STPCompletionBlock)(STPToken * __nullable token, NSError * __null
publishableKey:(NSString *)publishableKey
completion:(nullable STPCompletionBlock)handler __attribute__((deprecated));
+ (BOOL)deviceSupportsApplePay;
@end
NS_ASSUME_NONNULL_END

View File

@ -242,9 +242,6 @@ static NSString *const stripeAPIVersion = @"2015-10-12";
@implementation Stripe (ApplePay)
+ (BOOL)canSubmitPaymentRequest:(PKPaymentRequest *)paymentRequest {
if (![self deviceSupportsApplePay]) {
return NO;
}
if (paymentRequest == nil) {
return NO;
}
@ -256,16 +253,10 @@ static NSString *const stripeAPIVersion = @"2015-10-12";
+ (NSArray<NSString *> *)supportedPKPaymentNetworks {
NSArray *supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa];
if ((&PKPaymentNetworkDiscover) != NULL) {
supportedNetworks = [supportedNetworks arrayByAddingObject:PKPaymentNetworkDiscover];
}
return supportedNetworks;
}
+ (BOOL)deviceSupportsApplePay {
return [PKPaymentAuthorizationViewController class] && [PKPaymentAuthorizationViewController canMakePaymentsUsingNetworks:[self supportedPKPaymentNetworks]];
}
+ (PKPaymentRequest *)paymentRequestWithMerchantIdentifier:(NSString *)merchantIdentifier {
if (![PKPaymentRequest class]) {
return nil;

View File

@ -51,7 +51,6 @@ NS_ASSUME_NONNULL_BEGIN
* An icon representing Visa.
*/
+ (UIImage *)visaCardImage;
+ (UIImage *)otherCardImage;
/**
* An icon to use when the type of the card is unknown.

View File

@ -50,8 +50,7 @@
- (BOOL)applePayEnabled {
return self.appleMerchantIdentifier &&
(self.additionalPaymentMethods & STPPaymentMethodTypeApplePay) &&
[Stripe deviceSupportsApplePay];
(self.additionalPaymentMethods & STPPaymentMethodTypeApplePay);
}
@end

View File

@ -22,6 +22,13 @@
@implementation STPToken
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (NSString *)description {
return self.tokenId ?: @"Unknown token";
}

View File

@ -53,6 +53,7 @@ swift_library(
":TelegramCallsUIBundle",
],
deps = [
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Display:Display",
"//submodules/TelegramPresentationData:TelegramPresentationData",

View File

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

View File

@ -19,6 +19,7 @@ import AlertComponent
import TelegramPresentationData
import ComponentFlow
import MultilineTextComponent
import AVFoundation
private func resolvedEmojiKey(data: Data) -> [String] {
let resolvedKey = stringForEmojiHashOfData(data, 4) ?? []

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SSignalKit
import SwiftSignalKit
import AccountContext
import TelegramCore
@ -12,6 +13,7 @@ import WebSearchUI
import MapResourceToAvatarSizes
import LegacyUI
import LegacyMediaPickerUI
import AVFoundation
extension VideoChatScreenComponent.View {
func openParticipantContextMenu(id: EnginePeer.Id, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?) {

View File

@ -3,7 +3,7 @@ import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
import EncryptionProvider
struct SecretChatRequestData {
let g: Int32

View File

@ -3,6 +3,7 @@ import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
import EncryptionProvider
private func reactionGeneratedEvent(_ previousReactions: ReactionsMessageAttribute?, _ updatedReactions: ReactionsMessageAttribute?, message: Message, transaction: Transaction) -> (reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)? {
if let updatedReactions = updatedReactions, !message.flags.contains(.Incoming), message.id.peerId.namespace == Namespaces.Peer.CloudUser {

View File

@ -2,6 +2,7 @@ import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
import Foundation
public final class TelegramKeyPair: Equatable {
public let id: Int64

View File

@ -2,6 +2,7 @@ import SwiftSignalKit
import Postbox
import TelegramApi
import MtProtoKit
import Foundation
public struct EngineCallStreamState {
public struct Channel {

View File

@ -1,6 +1,7 @@
import Postbox
import TelegramApi
import MtProtoKit
import Foundation
public func smallestVideoRepresentation(_ representations: [TelegramMediaImage.VideoRepresentation]) -> TelegramMediaImage.VideoRepresentation? {
if representations.count == 0 {

View File

@ -1487,7 +1487,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
amountString = "\(amount.amount.value) Stars"
}
case .ton:
amountString = "\(formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat)) TON"
amountString = "\(formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat, maxDecimalPositions: 3)) TON"
}
attributedString = parseMarkdownIntoAttributedString("**\(channelName)** received **\(amountString)** for publishing this post", attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
case let .suggestedPostRefund(info):

View File

@ -28,7 +28,7 @@ public func formatTonUsdValue(_ value: Int64, divide: Bool = true, rate: Double
return "$\(formattedValue)"
}
public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false, maxDecimalPositions: Int = 2) -> String {
var balanceText = "\(abs(value))"
while balanceText.count < 10 {
balanceText.insert("0", at: balanceText.startIndex)
@ -49,7 +49,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
}
if let dotIndex = balanceText.range(of: dateTimeFormat.decimalSeparator) {
if let endIndex = balanceText.index(dotIndex.upperBound, offsetBy: 2, limitedBy: balanceText.endIndex) {
if let endIndex = balanceText.index(dotIndex.upperBound, offsetBy: maxDecimalPositions, limitedBy: balanceText.endIndex) {
balanceText = String(balanceText[balanceText.startIndex..<endIndex])
} else {
balanceText = String(balanceText[balanceText.startIndex..<balanceText.endIndex])

View File

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

View File

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

View File

@ -21,6 +21,12 @@ swift_library(
"//submodules/SolidRoundedButtonNode",
"//submodules/PresentationDataUtils",
"//submodules/UIKitRuntimeUtils",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/ToastComponent",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",

View File

@ -11,7 +11,7 @@ import TelegramPresentationData
public enum ChatScheduleTimeControllerMode {
case scheduledMessages(sendWhenOnlineAvailable: Bool)
case reminders
case suggestPost(needsTime: Bool)
case suggestPost(needsTime: Bool, isAdmin: Bool, funds: (amount: CurrencyAmount, commissionPermille: Int)?)
}
public enum ChatScheduleTimeControllerStyle {

View File

@ -10,6 +10,12 @@ import AccountContext
import SolidRoundedButtonNode
import PresentationDataUtils
import UIKitRuntimeUtils
import ComponentFlow
import ToastComponent
import Markdown
import LottieComponent
import MultilineTextComponent
import ComponentDisplayAdapters
class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDelegate {
private let context: AccountContext
@ -26,6 +32,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
private let backgroundNode: ASDisplayNode
private let contentBackgroundNode: ASDisplayNode
private let titleNode: ASTextNode
private let subtitleNode: ASTextNode?
private let textNode: ASTextNode?
private let cancelButton: HighlightableButtonNode
private let doneButton: SolidRoundedButtonNode
@ -36,6 +43,8 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
private var containerLayout: (ContainerViewLayout, CGFloat)?
private var toast: ComponentView<Empty>?
var completion: ((Int32) -> Void)?
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
@ -94,22 +103,43 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.contentBackgroundNode.backgroundColor = backgroundColor
let title: String
var subtitle: String?
var text: String?
switch mode {
case .scheduledMessages:
title = self.presentationData.strings.Conversation_ScheduleMessage_Title
case .reminders:
title = self.presentationData.strings.Conversation_SetReminder_Title
case let .suggestPost(needsTime):
case let .suggestPost(needsTime, isAdmin, funds):
if needsTime {
//TODO:localize
title = "Time"
title = "Accept Terms"
text = "Set the date and time you want\nthis message to be published."
} else {
//TODO:localize
title = "Time"
text = "Set the date and time you want\nyour message to be published."
}
//TODO:localize
if let funds, isAdmin {
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)])
}
switch funds.amount.currency {
case .stars:
let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0
subtitle = "You will receive \(displayAmount) Stars (\(commissionValue)%)\nfor publishing this post"
case .ton:
let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0
subtitle = "You will receive \(displayAmount) TON (\(commissionValue)%)\nfor publishing this post"
}
}
}
self.titleNode = ASTextNode()
@ -130,6 +160,19 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.textNode = nil
}
if let subtitle {
let subtitleNode = ASTextNode()
subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: textColor)
subtitleNode.maximumNumberOfLines = 0
subtitleNode.textAlignment = .center
subtitleNode.lineSpacing = 0.2
subtitleNode.accessibilityLabel = text
subtitleNode.accessibilityTraits = [.staticText]
self.subtitleNode = subtitleNode
} else {
self.subtitleNode = nil
}
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal)
self.cancelButton.accessibilityLabel = self.presentationData.strings.Common_Cancel
@ -139,7 +182,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.onlineButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false)
switch mode {
case let .suggestPost(needsTime):
case let .suggestPost(needsTime, _, _):
//TODO:localize
if needsTime {
self.onlineButton.title = "Post Now"
@ -172,6 +215,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode)
self.contentContainerNode.addSubnode(self.titleNode)
if let subtitleNode = self.subtitleNode {
self.contentContainerNode.addSubnode(subtitleNode)
}
if let textNode = self.textNode {
self.contentContainerNode.addSubnode(textNode)
}
@ -334,7 +380,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
} else {
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string
}
case let .suggestPost(needsTime):
case let .suggestPost(needsTime, _, _):
if needsTime {
if calendar.isDateInToday(date) {
self.doneButton.title = self.presentationData.strings.SuggestPost_Time_SendToday(time).string
@ -386,10 +432,14 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
transition.updateBounds(layer: self.layer, bounds: targetBounds)
transition.updatePosition(layer: self.dimNode.layer, position: dimPosition)
if let toastView = self.toast?.view {
toastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
transition.animatePositionAdditive(layer: toastView.layer, offset: CGPoint(x: 0.0, y: -offset))
}
}
func animateOut(completion: (() -> Void)? = nil) {
@ -415,6 +465,12 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
offsetCompleted = true
internalCompletion()
})
if let toastView = self.toast?.view {
toastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
})
toastView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -463,6 +519,17 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
let textControlSpacing: CGFloat = -8.0
let textDoneSpacing: CGFloat = 21.0
let subtitleTopSpacing: CGFloat = 22.0
let subtitleControlSpacing: CGFloat = 8.0
let subtitleSize = self.subtitleNode?.measure(CGSize(width: width, height: 1000.0))
var controlOffset: CGFloat = 0.0
if let subtitleSize {
contentHeight += subtitleSize.height + subtitleTopSpacing + subtitleControlSpacing
controlOffset += subtitleTopSpacing + subtitleControlSpacing + 20.0
}
let textSize = self.textNode?.measure(CGSize(width: width, height: 1000.0))
if let textSize {
contentHeight += textSize.height + textControlSpacing + textDoneSpacing
@ -486,6 +553,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
if let subtitleNode = self.subtitleNode, let subtitleSize {
let subtitleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + subtitleTopSpacing), size: subtitleSize)
transition.updateFrame(node: subtitleNode, frame: subtitleFrame)
}
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
@ -503,8 +575,52 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
let onlineButtonHeight = self.onlineButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.onlineButton, frame: CGRect(x: buttonInset, y: contentHeight - onlineButtonHeight - cleanInsets.bottom - 16.0, width: contentFrame.width, height: onlineButtonHeight))
self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))
self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0 + controlOffset), size: CGSize(width: contentFrame.width, height: pickerHeight))
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
if case let .suggestPost(_, isAdmin, funds) = self.mode, isAdmin, let funds, funds.amount.currency == .stars {
let toast: ComponentView<Empty>
if let current = self.toast {
toast = current
} else {
toast = ComponentView()
self.toast = toast
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
//TODO:localize
let playOnce = ActionSlot<Void>()
let toastSize = toast.update(
transition: ComponentTransition(transition),
component: AnyComponent(ToastContentComponent(
icon: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "anim_infotip"),
startingPosition: .begin,
size: CGSize(width: 32.0, height: 32.0),
playOnce: playOnce
)),
content: AnyComponent(VStack([
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .markdown(text: "Transactions in **Stars** may be reversed by the payment provider within **21** days. Only accept Stars from people you trust.", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })),
maximumNumberOfLines: 0
)))
], alignment: .left, spacing: 6.0)),
insets: UIEdgeInsets(top: 10.0, left: 12.0, bottom: 10.0, right: 10.0),
iconSpacing: 12.0
)),
environment: {},
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 12.0 * 2.0, height: 1000.0)
)
let toastFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 12.0, y: layout.insets(options: .statusBar).top + 4.0), size: toastSize)
if let toastView = toast.view {
if toastView.superview == nil {
self.view.addSubview(toastView)
playOnce.invoke(())
}
transition.updatePosition(layer: toastView.layer, position: toastFrame.center)
transition.updateBounds(layer: toastView.layer, bounds: CGRect(origin: CGPoint(), size: toastFrame.size))
}
}
}
}

View File

@ -10,6 +10,7 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/LegacyComponents",

View File

@ -4,6 +4,7 @@ import LegacyComponents
import Display
import TelegramCore
import Postbox
import SSignalKit
import SwiftSignalKit
import AccountContext
import ShareController

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore

View File

@ -1,3 +1,4 @@
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import SwiftSignalKit
import Postbox
import TelegramCore
import AsyncDisplayKit
import UIKit
import Display
import TelegramPresentationData

View File

@ -103,6 +103,7 @@ swift_library(
"//submodules/StickerResources",
"//submodules/TelegramUI/Components/StorageUsageScreen",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/SSignalKit/SSignalKit",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramBaseController",
"//submodules/TelegramCallsUI",

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit

View File

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

View File

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

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import AsyncDisplayKit
import UIKit
import Display
import TelegramCore
import SwiftSignalKit

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore

View File

@ -1,3 +1,4 @@
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore

View File

@ -1,3 +1,5 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SSignalKit
import SwiftSignalKit
import TelegramCore
import AccountContext

Some files were not shown because too many files have changed in this diff Show More