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

This commit is contained in:
Ilya Laktyushin 2025-06-29 14:55:07 +02:00
commit 12e85b67b8
5 changed files with 334 additions and 132 deletions

View File

@ -183,6 +183,53 @@ _SWIFT_LIBRARY_REQUIRED_ATTRS = [
"module_name", "module_name",
] ]
"""
["alwayslink", "aspect_hints", "compatible_with", "data", "deprecation", "deps", "exec_compatible_with", "exec_properties", "expect_failure", "features", "generator_function", "generator_location", "generator_name", "has_swift", "includes", "library_identifiers", "linkopts", "name", "package_metadata", "restricted_to", "sdk_dylibs", "sdk_frameworks", "tags", "target_compatible_with", "testonly", "toolchains", "transitive_configs", "visibility", "weak_sdk_frameworks", "xcframework_imports"]
"""
_IGNORE_APPLE_STATIC_XCFRAMEWORK_IMPORT_ATTRS = [
"name",
"alwayslink",
"aspect_hints",
"compatible_with",
"data",
"deprecation",
"exec_compatible_with",
"exec_properties",
"expect_failure",
"features",
"generator_function",
"generator_location",
"generator_name",
"has_swift",
"includes",
"library_identifiers",
"linkopts",
"package_metadata",
"restricted_to",
"tags",
"target_compatible_with",
"testonly",
"toolchains",
"transitive_configs",
"visibility",
"weak_sdk_frameworks",
]
_IGNORE_APPLE_STATIC_XCFRAMEWORK_IMPORT_EMPTY_ATTRS = [
"deps",
"sdk_dylibs",
"sdk_frameworks",
]
_APPLE_STATIC_XCFRAMEWORK_IMPORT_ATTRS = [
"xcframework_imports",
]
_APPLE_STATIC_XCFRAMEWORK_IMPORT_REQUIRED_ATTRS = [
"xcframework_imports",
]
_LIBRARY_CONFIGS = { _LIBRARY_CONFIGS = {
"cc_library": { "cc_library": {
"ignore_attrs": _IGNORE_CC_LIBRARY_ATTRS, "ignore_attrs": _IGNORE_CC_LIBRARY_ATTRS,
@ -202,6 +249,12 @@ _LIBRARY_CONFIGS = {
"handled_attrs": _SWIFT_LIBRARY_ATTRS, "handled_attrs": _SWIFT_LIBRARY_ATTRS,
"required_attrs": _SWIFT_LIBRARY_REQUIRED_ATTRS, "required_attrs": _SWIFT_LIBRARY_REQUIRED_ATTRS,
}, },
"apple_static_xcframework_import": {
"ignore_attrs": _IGNORE_APPLE_STATIC_XCFRAMEWORK_IMPORT_ATTRS,
"ignore_empty_attrs": _IGNORE_APPLE_STATIC_XCFRAMEWORK_IMPORT_EMPTY_ATTRS,
"handled_attrs": _APPLE_STATIC_XCFRAMEWORK_IMPORT_ATTRS,
"required_attrs": _APPLE_STATIC_XCFRAMEWORK_IMPORT_REQUIRED_ATTRS,
},
} }
def get_rule_atts(rule): def get_rule_atts(rule):
@ -225,6 +278,7 @@ def get_rule_atts(rule):
fail("Attribute {} is not empty: {}".format(attr_name, attr_value)) fail("Attribute {} is not empty: {}".format(attr_name, attr_value))
if attr_name in handled_attrs: if attr_name in handled_attrs:
continue continue
print("All attributes: {}".format(dir(rule.attr)))
fail("Unknown attribute: {}".format(attr_name)) fail("Unknown attribute: {}".format(attr_name))
result = dict() result = dict()
@ -288,13 +342,6 @@ def _collect_spm_modules_impl(target, ctx):
# Merge all transitive sources from dependencies # Merge all transitive sources from dependencies
transitive_sources_from_deps = depset(transitive = dep_transitive_sources_list) transitive_sources_from_deps = depset(transitive = dep_transitive_sources_list)
# Keep this for debugging later
# if result_attrs["type"] == "swift_library":
# print("Processing rule {}".format(ctx.label.name))
# print("ctx.rule.kind = {}".format(ctx.rule.kind))
# for attr_name in dir(ctx.rule.attr):
# print(" attr1: {}".format(attr_name))
result_attrs = get_rule_atts(ctx.rule) result_attrs = get_rule_atts(ctx.rule)
sources = [] sources = []
@ -331,12 +378,36 @@ def _collect_spm_modules_impl(target, ctx):
textual_hdrs.append(hdr_file.path) textual_hdrs.append(hdr_file.path)
current_target_textual_headers = depset(current_target_textual_hdr_files) current_target_textual_headers = depset(current_target_textual_hdr_files)
current_target_xcframework_import_files = []
if "xcframework_imports" in result_attrs:
for src_target in result_attrs["xcframework_imports"]:
src_files = src_target.files.to_list()
for f in src_files:
if f != ".DS_Store":
current_target_xcframework_import_files.append(f)
for src_file in src_files:
sources.append(src_file.path)
current_target_xcframework_imports = depset(current_target_xcframework_import_files)
module_type = result_attrs["type"] module_type = result_attrs["type"]
if module_type == "root": if module_type == "root":
pass pass
elif module_type == "apple_static_xcframework_import": elif module_type == "apple_static_xcframework_import":
pass if not str(ctx.label).startswith("@@//"):
fail("Invalid label: {}".format(ctx.label))
module_path = str(ctx.label).split(":")[0].split("@@//")[1]
module_info = {
"name": result_attrs["name"],
"type": module_type,
"path": module_path,
"sources": sorted(sources),
"module_name": module_name,
}
if result_attrs["name"] in all_modules:
fail("Duplicate module name: {}".format(result_attrs["name"]))
all_modules[result_attrs["name"]] = module_info
elif module_type == "objc_library" or module_type == "swift_library" or module_type == "cc_library": elif module_type == "objc_library" or module_type == "swift_library" or module_type == "cc_library":
# Collect dependency labels # Collect dependency labels
dep_names = [] dep_names = []
@ -416,6 +487,7 @@ def _collect_spm_modules_impl(target, ctx):
current_target_sources, current_target_sources,
current_target_headers, current_target_headers,
current_target_textual_headers, current_target_textual_headers,
current_target_xcframework_imports,
]) ])
# Return both the SPM output files and the provider with modules data and sources # Return both the SPM output files and the provider with modules data and sources

View File

@ -15,17 +15,73 @@ with open(modules_json_path, 'r') as f:
# Clean spm-files # Clean spm-files
spm_files_dir = "spm-files" spm_files_dir = "spm-files"
if os.path.exists(spm_files_dir):
for item in os.listdir(spm_files_dir): previous_spm_files = set()
if item != ".build":
item_path = os.path.join(spm_files_dir, item) def scan_spm_files(path: str):
if os.path.isfile(item_path): global previous_spm_files
os.unlink(item_path) if not os.path.exists(path):
return
for item in os.listdir(path):
if item == ".build":
continue
item_path = os.path.join(path, item)
if os.path.isfile(item_path) or os.path.islink(item_path):
previous_spm_files.add(item_path)
elif os.path.isdir(item_path): elif os.path.isdir(item_path):
shutil.rmtree(item_path) previous_spm_files.add(item_path)
scan_spm_files(item_path)
scan_spm_files(spm_files_dir)
current_spm_files = set()
def create_spm_file(path: str, contents: str):
global current_spm_files
current_spm_files.add(path)
# Track all parent directories
parent_dir = os.path.dirname(path)
while parent_dir and parent_dir != path:
current_spm_files.add(parent_dir)
parent_dir = os.path.dirname(parent_dir)
with open(path, "w") as f:
f.write(contents)
def link_spm_file(source_path: str, target_path: str):
global current_spm_files
current_spm_files.add(target_path)
# Track all parent directories
parent_dir = os.path.dirname(target_path)
while parent_dir and parent_dir != target_path:
current_spm_files.add(parent_dir)
parent_dir = os.path.dirname(parent_dir)
# Remove existing file/symlink if it exists and is different
if os.path.islink(target_path):
if os.readlink(target_path) != source_path:
os.unlink(target_path)
else:
return # Symlink already points to the correct target
elif os.path.exists(target_path):
os.unlink(target_path)
os.symlink(source_path, target_path)
def create_spm_directory(path: str):
global current_spm_files
current_spm_files.add(path)
if not os.path.exists(path):
os.makedirs(path)
if not os.path.exists(spm_files_dir): if not os.path.exists(spm_files_dir):
os.makedirs(spm_files_dir) os.makedirs(spm_files_dir)
# Track the root directory
current_spm_files.add(spm_files_dir)
def escape_swift_string_literal_component(text: str) -> str: def escape_swift_string_literal_component(text: str) -> str:
# Handle -D defines that use shell-style quoting like -DPACKAGE_STRING='""' # Handle -D defines that use shell-style quoting like -DPACKAGE_STRING='""'
# In Bazel, this gets processed by shell to become -DPACKAGE_STRING="" # In Bazel, this gets processed by shell to become -DPACKAGE_STRING=""
@ -103,8 +159,10 @@ func parseProduct(product: [String: Any]) -> Product {
combined_lines.append(""" combined_lines.append("""
func parseTarget(target: [String: Any]) -> Target { func parseTarget(target: [String: Any]) -> Target {
let name = target["name"] as! String let name = target["name"] as! String
let type = target["type"] as! String
let dependencies = target["dependencies"] as! [String] let dependencies = target["dependencies"] as! [String]
if type == "library" {
var swiftSettings: [SwiftSetting]? var swiftSettings: [SwiftSetting]?
if let swiftSettingList = target["swiftSettings"] as? [[String: Any]] { if let swiftSettingList = target["swiftSettings"] as? [[String: Any]] {
var swiftSettingsValue: [SwiftSetting] = [] var swiftSettingsValue: [SwiftSetting] = []
@ -194,6 +252,12 @@ func parseTarget(target: [String: Any]) -> Target {
linkerSettings: linkerSettings, linkerSettings: linkerSettings,
plugins: nil plugins: nil
) )
} else if type == "xcframework" {
return .binaryTarget(name: name, path: (target["path"] as? String)! + "/" + (target["name"] as? String)! + ".xcframework")
} else {
print("Unknown target type: \\(type)")
preconditionFailure("Unknown target type: \\(type)")
}
} }
""") """)
combined_lines.append("") combined_lines.append("")
@ -226,14 +290,14 @@ for name, module in sorted(modules.items()):
continue continue
module_type = module["type"] module_type = module["type"]
if module_type == "objc_library" or module_type == "cc_library" or module_type == "swift_library": if module_type == "objc_library" or module_type == "cc_library" or module_type == "swift_library" or module_type == "apple_static_xcframework_import":
spm_target = dict() spm_target = dict()
spm_target["name"] = name spm_target["name"] = name
relative_module_path = module["path"] relative_module_path = module["path"]
module_directory = spm_files_dir + "/" + relative_module_path module_directory = spm_files_dir + "/" + relative_module_path
os.makedirs(module_directory, exist_ok=True) create_spm_directory(module_directory)
module_public_headers_prefix = "" module_public_headers_prefix = ""
if module_type == "objc_library" or module_type == "cc_library": if module_type == "objc_library" or module_type == "cc_library":
@ -248,7 +312,7 @@ for name, module in sorted(modules.items()):
break break
spm_target["dependencies"] = [] spm_target["dependencies"] = []
for dep in module["deps"]: for dep in module.get("deps", []):
if not parsed_modules[dep]["is_empty"]: if not parsed_modules[dep]["is_empty"]:
spm_target["dependencies"].append(dep) spm_target["dependencies"].append(dep)
@ -279,8 +343,7 @@ for name, module in sorted(modules.items()):
# Create parent directory for symlink if it doesn't exist # Create parent directory for symlink if it doesn't exist
symlink_parent = os.path.dirname(symlink_location) symlink_parent = os.path.dirname(symlink_location)
if not os.path.exists(symlink_parent): create_spm_directory(symlink_parent)
os.makedirs(symlink_parent)
# Calculate relative path from symlink back to original file # Calculate relative path from symlink back to original file
# Count directory depth: spm-files/module_name/... -> spm-files # Count directory depth: spm-files/module_name/... -> spm-files
@ -289,9 +352,7 @@ for name, module in sorted(modules.items()):
symlink_target = relative_prefix + source symlink_target = relative_prefix + source
# Create the symlink # Create the symlink
if os.path.lexists(symlink_location): link_spm_file(symlink_target, symlink_location)
os.unlink(symlink_location)
os.symlink(symlink_target, symlink_location)
# Add to sources list (exclude certain file types) # Add to sources list (exclude certain file types)
if source.endswith(('.h', '.hpp', '.a', '.inc')): if source.endswith(('.h', '.hpp', '.a', '.inc')):
@ -314,6 +375,7 @@ for name, module in sorted(modules.items()):
if len(ignore_sub_folders) != 0: if len(ignore_sub_folders) != 0:
spm_target["exclude"] = ignore_sub_folders spm_target["exclude"] = ignore_sub_folders
if module_type == "objc_library" or module_type == "cc_library":
modulemap_path = os.path.join(os.path.join(os.path.join(module_directory), module_public_headers_prefix), "module.modulemap") 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: if modulemap_path not in modulemaps:
modulemaps[modulemap_path] = [] modulemaps[modulemap_path] = []
@ -452,7 +514,14 @@ for name, module in sorted(modules.items()):
"flags": unsafe_flags "flags": unsafe_flags
}) })
if module_type == "apple_static_xcframework_import":
spm_target["type"] = "xcframework"
else:
spm_target["type"] = "library"
spm_targets.append(spm_target) spm_targets.append(spm_target)
elif module["type"] == "apple_static_xcframework_import":
pass
elif module["type"] == "root": elif module["type"] == "root":
pass pass
else: else:
@ -463,16 +532,14 @@ combined_lines.append(" cxxLanguageStandard: .cxx17")
combined_lines.append(")") combined_lines.append(")")
combined_lines.append("") combined_lines.append("")
with open("spm-files/Package.swift", "w") as f: create_spm_file("spm-files/Package.swift", "\n".join(combined_lines))
f.write("\n".join(combined_lines))
with open("spm-files/PackageData.json", "w") as f:
package_data = { package_data = {
"sourceFileMap": module_to_source_files, "sourceFileMap": module_to_source_files,
"products": spm_products, "products": spm_products,
"targets": spm_targets "targets": spm_targets
} }
json.dump(package_data, f, indent=4) create_spm_file("spm-files/PackageData.json", json.dumps(package_data, indent=4))
for modulemap_path, modulemap in modulemaps.items(): for modulemap_path, modulemap in modulemaps.items():
module_map_contents = "" module_map_contents = ""
@ -481,5 +548,30 @@ for modulemap_path, modulemap in modulemaps.items():
for public_include_file in module["public_include_files"]: for public_include_file in module["public_include_files"]:
module_map_contents += " header \"{}\"\n".format(public_include_file) module_map_contents += " header \"{}\"\n".format(public_include_file)
module_map_contents += "}\n" module_map_contents += "}\n"
with open(modulemap_path, "w") as f: create_spm_file(modulemap_path, module_map_contents)
f.write(module_map_contents)
# Clean up files and directories that are no longer needed
files_to_remove = previous_spm_files - current_spm_files
# 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)
for file_path in sorted_files_to_remove:
try:
if os.path.islink(file_path):
os.unlink(file_path)
#print(f"Removed symlink: {file_path}")
elif os.path.isfile(file_path):
os.unlink(file_path)
#print(f"Removed file: {file_path}")
elif os.path.isdir(file_path):
# Try to remove directory if empty, otherwise use rmtree
try:
os.rmdir(file_path)
#print(f"Removed empty directory: {file_path}")
except OSError:
shutil.rmtree(file_path)
#print(f"Removed directory tree: {file_path}")
except OSError as e:
print(f"Failed to remove {file_path}: {e}")

View File

@ -1511,7 +1511,8 @@ public struct StarsSubscriptionConfiguration {
channelMessageSuggestionStarsCommissionPermille: 850, channelMessageSuggestionStarsCommissionPermille: 850,
channelMessageSuggestionTonCommissionPermille: 850, channelMessageSuggestionTonCommissionPermille: 850,
channelMessageSuggestionMaxStarsAmount: 10000, channelMessageSuggestionMaxStarsAmount: 10000,
channelMessageSuggestionMaxTonAmount: 10000000000000 channelMessageSuggestionMaxTonAmount: 10000000000000,
channelMessageSuggestionMinStarsAmount: 5
) )
} }
@ -1528,6 +1529,7 @@ public struct StarsSubscriptionConfiguration {
public let channelMessageSuggestionTonCommissionPermille: Int32 public let channelMessageSuggestionTonCommissionPermille: Int32
public let channelMessageSuggestionMaxStarsAmount: Int64 public let channelMessageSuggestionMaxStarsAmount: Int64
public let channelMessageSuggestionMaxTonAmount: Int64 public let channelMessageSuggestionMaxTonAmount: Int64
public let channelMessageSuggestionMinStarsAmount: Int64
fileprivate init( fileprivate init(
maxFee: Int64, maxFee: Int64,
@ -1542,7 +1544,8 @@ public struct StarsSubscriptionConfiguration {
channelMessageSuggestionStarsCommissionPermille: Int32, channelMessageSuggestionStarsCommissionPermille: Int32,
channelMessageSuggestionTonCommissionPermille: Int32, channelMessageSuggestionTonCommissionPermille: Int32,
channelMessageSuggestionMaxStarsAmount: Int64, channelMessageSuggestionMaxStarsAmount: Int64,
channelMessageSuggestionMaxTonAmount: Int64 channelMessageSuggestionMaxTonAmount: Int64,
channelMessageSuggestionMinStarsAmount: Int64
) { ) {
self.maxFee = maxFee self.maxFee = maxFee
self.usdWithdrawRate = usdWithdrawRate self.usdWithdrawRate = usdWithdrawRate
@ -1557,6 +1560,7 @@ public struct StarsSubscriptionConfiguration {
self.channelMessageSuggestionTonCommissionPermille = channelMessageSuggestionTonCommissionPermille self.channelMessageSuggestionTonCommissionPermille = channelMessageSuggestionTonCommissionPermille
self.channelMessageSuggestionMaxStarsAmount = channelMessageSuggestionMaxStarsAmount self.channelMessageSuggestionMaxStarsAmount = channelMessageSuggestionMaxStarsAmount
self.channelMessageSuggestionMaxTonAmount = channelMessageSuggestionMaxTonAmount self.channelMessageSuggestionMaxTonAmount = channelMessageSuggestionMaxTonAmount
self.channelMessageSuggestionMinStarsAmount = channelMessageSuggestionMinStarsAmount
} }
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
@ -1576,6 +1580,8 @@ public struct StarsSubscriptionConfiguration {
let channelMessageSuggestionMaxStarsAmount = (data["stars_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxStarsAmount 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 let channelMessageSuggestionMaxTonAmount = (data["ton_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxTonAmount
let channelMessageSuggestionMinStarsAmount = (data["stars_suggested_post_amount_min"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMinStarsAmount
return StarsSubscriptionConfiguration( return StarsSubscriptionConfiguration(
maxFee: maxFee, maxFee: maxFee,
usdWithdrawRate: usdWithdrawRate, usdWithdrawRate: usdWithdrawRate,
@ -1589,7 +1595,8 @@ public struct StarsSubscriptionConfiguration {
channelMessageSuggestionStarsCommissionPermille: channelMessageSuggestionStarsCommissionPermille, channelMessageSuggestionStarsCommissionPermille: channelMessageSuggestionStarsCommissionPermille,
channelMessageSuggestionTonCommissionPermille: channelMessageSuggestionTonCommissionPermille, channelMessageSuggestionTonCommissionPermille: channelMessageSuggestionTonCommissionPermille,
channelMessageSuggestionMaxStarsAmount: channelMessageSuggestionMaxStarsAmount, channelMessageSuggestionMaxStarsAmount: channelMessageSuggestionMaxStarsAmount,
channelMessageSuggestionMaxTonAmount: channelMessageSuggestionMaxTonAmount channelMessageSuggestionMaxTonAmount: channelMessageSuggestionMaxTonAmount,
channelMessageSuggestionMinStarsAmount: channelMessageSuggestionMinStarsAmount
) )
} else { } else {
return .defaultValue return .defaultValue

View File

@ -952,7 +952,7 @@ public final class ChatSideTopicsPanel: Component {
if let current = self.avatarNode { if let current = self.avatarNode {
avatarNode = current avatarNode = current
} else { } else {
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0)) avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
avatarNode.isUserInteractionEnabled = false avatarNode.isUserInteractionEnabled = false
self.avatarNode = avatarNode self.avatarNode = avatarNode
self.containerButton.addSubview(avatarNode.view) self.containerButton.addSubview(avatarNode.view)

View File

@ -157,6 +157,7 @@ private final class SheetContent: CombinedComponent {
var amountRightLabel: String? var amountRightLabel: String?
let minAmount: StarsAmount? let minAmount: StarsAmount?
var allowZero = false
let maxAmount: StarsAmount? let maxAmount: StarsAmount?
let withdrawConfiguration = StarsWithdrawConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) let withdrawConfiguration = StarsWithdrawConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
@ -222,13 +223,14 @@ private final class SheetContent: CombinedComponent {
case .stars: case .stars:
amountTitle = "ENTER A PRICE IN STARS" amountTitle = "ENTER A PRICE IN STARS"
maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxStarsAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxStarsAmount, nanos: 0)
minAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMinStarsAmount, nanos: 0)
case .ton: case .ton:
amountTitle = "ENTER A PRICE IN TON" amountTitle = "ENTER A PRICE IN TON"
maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxTonAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxTonAmount, nanos: 0)
minAmount = StarsAmount(value: 0, nanos: 0)
} }
amountPlaceholder = "Price" amountPlaceholder = "Price"
allowZero = true
minAmount = StarsAmount(value: 0, nanos: 0)
if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let tonUsdRate = withdrawConfiguration.tonUsdRate, let amount = state.amount, amount > StarsAmount.zero { if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let tonUsdRate = withdrawConfiguration.tonUsdRate, let amount = state.amount, amount > StarsAmount.zero {
switch state.currency { switch state.currency {
@ -535,6 +537,7 @@ private final class SheetContent: CombinedComponent {
accentColor: theme.list.itemAccentColor, accentColor: theme.list.itemAccentColor,
value: state.amount?.value, value: state.amount?.value,
minValue: minAmount?.value, minValue: minAmount?.value,
allowZero: allowZero,
maxValue: maxAmount?.value, maxValue: maxAmount?.value,
placeholderText: amountPlaceholder, placeholderText: amountPlaceholder,
labelText: amountLabel, labelText: amountLabel,
@ -762,8 +765,8 @@ private final class SheetContent: CombinedComponent {
if let controller = controller() as? StarsWithdrawScreen, let state { if let controller = controller() as? StarsWithdrawScreen, let state {
let amount = state.amount ?? StarsAmount.zero let amount = state.amount ?? StarsAmount.zero
if let minAmount, amount < minAmount { if let minAmount, amount < minAmount, (!allowZero || amount != .zero) {
controller.presentMinAmountTooltip(minAmount.value) controller.presentMinAmountTooltip(minAmount.value, currency: state.currency)
} else { } else {
switch state.mode { switch state.mode {
case let .withdraw(_, completion): case let .withdraw(_, completion):
@ -1113,12 +1116,30 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
} }
} }
func presentMinAmountTooltip(_ minAmount: Int64) { func presentMinAmountTooltip(_ minAmount: Int64, currency: CurrencyAmount.Currency) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string
if case .starGiftResell = self.mode { if case .starGiftResell = self.mode {
//TODO:localize //TODO:localize
text = "You cannot sell gift for less than \(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))." text = "You cannot sell gift for less than \(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))."
} else if case let .suggestedPost(mode, _, _, _) = self.mode {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
switch currency {
case .stars:
//TODO:localize
switch mode {
case .admin:
text = "You cannot request less than \(resaleConfiguration.channelMessageSuggestionMinStarsAmount) Stars."
case let .sender(_, isFromAdmin):
if isFromAdmin {
text = "You cannot request less than \(resaleConfiguration.channelMessageSuggestionMinStarsAmount) Stars."
} else {
text = "You cannot offer less than \(resaleConfiguration.channelMessageSuggestionMinStarsAmount) Stars."
}
}
case .ton:
break
}
} }
let resultController = UndoOverlayController( let resultController = UndoOverlayController(
@ -1153,17 +1174,19 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
private let textField: UITextField private let textField: UITextField
private let minValue: Int64 private let minValue: Int64
private let allowZero: Bool
private let maxValue: Int64 private let maxValue: Int64
private let updated: (Int64) -> Void private let updated: (Int64) -> Void
private let isEmptyUpdated: (Bool) -> Void private let isEmptyUpdated: (Bool) -> Void
private let animateError: () -> Void private let animateError: () -> Void
private let focusUpdated: (Bool) -> Void private let focusUpdated: (Bool) -> Void
init?(textField: UITextField, currency: CurrencyAmount.Currency, dateTimeFormat: PresentationDateTimeFormat, minValue: Int64, maxValue: Int64, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, animateError: @escaping () -> Void, focusUpdated: @escaping (Bool) -> Void) { init?(textField: UITextField, currency: CurrencyAmount.Currency, dateTimeFormat: PresentationDateTimeFormat, minValue: Int64, allowZero: Bool, maxValue: Int64, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, animateError: @escaping () -> Void, focusUpdated: @escaping (Bool) -> Void) {
self.textField = textField self.textField = textField
self.currency = currency self.currency = currency
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.minValue = minValue self.minValue = minValue
self.allowZero = allowZero
self.maxValue = maxValue self.maxValue = maxValue
self.updated = updated self.updated = updated
self.isEmptyUpdated = isEmptyUpdated self.isEmptyUpdated = isEmptyUpdated
@ -1307,6 +1330,7 @@ private final class AmountFieldComponent: Component {
let accentColor: UIColor let accentColor: UIColor
let value: Int64? let value: Int64?
let minValue: Int64? let minValue: Int64?
let allowZero: Bool
let maxValue: Int64? let maxValue: Int64?
let placeholderText: String let placeholderText: String
let labelText: String? let labelText: String?
@ -1322,6 +1346,7 @@ private final class AmountFieldComponent: Component {
accentColor: UIColor, accentColor: UIColor,
value: Int64?, value: Int64?,
minValue: Int64?, minValue: Int64?,
allowZero: Bool,
maxValue: Int64?, maxValue: Int64?,
placeholderText: String, placeholderText: String,
labelText: String?, labelText: String?,
@ -1336,6 +1361,7 @@ private final class AmountFieldComponent: Component {
self.accentColor = accentColor self.accentColor = accentColor
self.value = value self.value = value
self.minValue = minValue self.minValue = minValue
self.allowZero = allowZero
self.maxValue = maxValue self.maxValue = maxValue
self.placeholderText = placeholderText self.placeholderText = placeholderText
self.labelText = labelText self.labelText = labelText
@ -1364,6 +1390,9 @@ private final class AmountFieldComponent: Component {
if lhs.minValue != rhs.minValue { if lhs.minValue != rhs.minValue {
return false return false
} }
if lhs.allowZero != rhs.allowZero {
return false
}
if lhs.maxValue != rhs.maxValue { if lhs.maxValue != rhs.maxValue {
return false return false
} }
@ -1470,6 +1499,7 @@ private final class AmountFieldComponent: Component {
currency: component.currency, currency: component.currency,
dateTimeFormat: component.dateTimeFormat, dateTimeFormat: component.dateTimeFormat,
minValue: component.minValue ?? 0, minValue: component.minValue ?? 0,
allowZero: component.allowZero,
maxValue: component.maxValue ?? Int64.max, maxValue: component.maxValue ?? Int64.max,
updated: { [weak self] value in updated: { [weak self] value in
guard let self, let component = self.component else { guard let self, let component = self.component else {
@ -1505,6 +1535,7 @@ private final class AmountFieldComponent: Component {
currency: component.currency, currency: component.currency,
dateTimeFormat: component.dateTimeFormat, dateTimeFormat: component.dateTimeFormat,
minValue: component.minValue ?? 0, minValue: component.minValue ?? 0,
allowZero: component.allowZero,
maxValue: component.maxValue ?? 10000000, maxValue: component.maxValue ?? 10000000,
updated: { [weak self] value in updated: { [weak self] value in
guard let self, let component = self.component else { guard let self, let component = self.component else {