mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
6f4f3817a9
3
.bazelrc
3
.bazelrc
@ -35,6 +35,3 @@ build --spawn_strategy=standalone
|
||||
build --strategy=SwiftCompile=standalone
|
||||
build --define RULES_SWIFT_BUILD_DUMMY_WORKER=1
|
||||
|
||||
#build --linkopt=-fuse-ld=/Users/ali/Downloads/ld64.lld
|
||||
#build --linkopt=-fuse-ld=/Users/ali/build/zld/build/Build/Products/Release/zld
|
||||
#build --linkopt=-Wl,-zld_original_ld_path,__BAZEL_XCODE_DEVELOPER_DIR__/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -59,6 +59,7 @@ bazel-telegram-ios
|
||||
bazel-telegram-ios/*
|
||||
bazel-testlogs
|
||||
bazel-testlogs/*
|
||||
xcodeproj.bazelrc
|
||||
*/*.swp
|
||||
*.swp
|
||||
build-input/*
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -29,3 +29,6 @@ url=../tgcalls.git
|
||||
[submodule "third-party/libx264/x264"]
|
||||
path = third-party/libx264/x264
|
||||
url = https://github.com/mirror/x264.git
|
||||
[submodule "build-system/bazel-rules/rules_xcodeproj"]
|
||||
path = build-system/bazel-rules/rules_xcodeproj
|
||||
url = https://github.com/MobileNativeFoundation/rules_xcodeproj.git
|
||||
|
@ -25,12 +25,19 @@ load("@build_bazel_rules_swift//swift:swift.bzl",
|
||||
"swift_library",
|
||||
)
|
||||
|
||||
load(
|
||||
"@rules_xcodeproj//xcodeproj:defs.bzl",
|
||||
"top_level_target",
|
||||
"xcodeproj",
|
||||
)
|
||||
|
||||
load("//build-system/bazel-utils:plist_fragment.bzl",
|
||||
"plist_fragment",
|
||||
)
|
||||
|
||||
load(
|
||||
"@build_configuration//:variables.bzl",
|
||||
"telegram_bazel_path",
|
||||
"telegram_bundle_id",
|
||||
"telegram_aps_environment",
|
||||
"telegram_team_id",
|
||||
@ -1010,29 +1017,6 @@ plist_fragment(
|
||||
)
|
||||
)
|
||||
|
||||
ios_framework(
|
||||
name = "AsyncDisplayKitFramework",
|
||||
bundle_id = "{telegram_bundle_id}.AsyncDisplayKit".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = [
|
||||
"iphone",
|
||||
"ipad",
|
||||
],
|
||||
infoplists = [
|
||||
":AsyncDisplayKitInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":RequiredDeviceCapabilitiesPlist",
|
||||
],
|
||||
minimum_os_version = minimum_os_version,
|
||||
extension_safe = True,
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "DisplayInfoPlist",
|
||||
extension = "plist",
|
||||
@ -1094,33 +1078,6 @@ sh_binary(
|
||||
srcs = [":GenerateAddAlternateIcons"],
|
||||
)
|
||||
|
||||
ios_framework(
|
||||
name = "DisplayFramework",
|
||||
bundle_id = "{telegram_bundle_id}.Display".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = [
|
||||
"iphone",
|
||||
"ipad",
|
||||
],
|
||||
infoplists = [
|
||||
":DisplayInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":RequiredDeviceCapabilitiesPlist",
|
||||
],
|
||||
frameworks = [
|
||||
":SwiftSignalKitFramework",
|
||||
":AsyncDisplayKitFramework",
|
||||
],
|
||||
minimum_os_version = minimum_os_version,
|
||||
extension_safe = True,
|
||||
ipa_post_processor = strip_framework,
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "TelegramUIInfoPlist",
|
||||
extension = "plist",
|
||||
@ -1159,8 +1116,6 @@ ios_framework(
|
||||
":SwiftSignalKitFramework",
|
||||
":PostboxFramework",
|
||||
":TelegramCoreFramework",
|
||||
":AsyncDisplayKitFramework",
|
||||
":DisplayFramework",
|
||||
],
|
||||
minimum_os_version = minimum_os_version,
|
||||
extension_safe = True,
|
||||
@ -1997,8 +1952,6 @@ ios_application(
|
||||
":SwiftSignalKitFramework",
|
||||
":PostboxFramework",
|
||||
":TelegramCoreFramework",
|
||||
":AsyncDisplayKitFramework",
|
||||
":DisplayFramework",
|
||||
":TelegramUIFramework",
|
||||
],
|
||||
strings = [
|
||||
@ -2023,6 +1976,18 @@ ios_application(
|
||||
":Main",
|
||||
":Lib",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
xcodeproj(
|
||||
name = "Telegram_xcodeproj",
|
||||
build_mode = "bazel",
|
||||
bazel_path = telegram_bazel_path,
|
||||
project_name = "Telegram",
|
||||
tags = ["manual"],
|
||||
top_level_targets = [
|
||||
":Telegram",
|
||||
],
|
||||
)
|
||||
|
||||
# Temporary targets used to simplify webrtc build tests
|
||||
|
@ -9122,3 +9122,7 @@ Sorry for the inconvenience.";
|
||||
"Notification.ChangedWallpaper" = "%1$@ set a new background for this chat";
|
||||
"Notification.YouChangedWallpaper" = "You set a new background for this chat";
|
||||
"Notification.Wallpaper.View" = "View Background";
|
||||
|
||||
"Channel.AdminLog.JoinedViaFolderInviteLink" = "%1$@ joined via invite link %2$@ (community)";
|
||||
|
||||
"Conversation.OpenChatFolder" = "Open Shared Folder";
|
||||
|
22
WORKSPACE
22
WORKSPACE
@ -1,15 +1,15 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
|
||||
|
||||
http_archive(
|
||||
'''http_archive(
|
||||
name = "com_google_protobuf",
|
||||
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.14.0.zip"],
|
||||
sha256 = "bf0e5070b4b99240183b29df78155eee335885e53a8af8683964579c214ad301",
|
||||
strip_prefix = "protobuf-3.14.0",
|
||||
urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v22.2/protobuf-22.2.zip"],
|
||||
sha256 = "bf1f92aebd619651220711e97b3d560cdc2484718cd56d95161bfb2fadb8628e",
|
||||
strip_prefix = "protobuf-22.2",
|
||||
type = "zip",
|
||||
)
|
||||
|
||||
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
|
||||
protobuf_deps()
|
||||
protobuf_deps()'''
|
||||
|
||||
local_repository(
|
||||
name = "build_bazel_rules_apple",
|
||||
@ -26,6 +26,11 @@ local_repository(
|
||||
path = "build-system/bazel-rules/apple_support",
|
||||
)
|
||||
|
||||
local_repository(
|
||||
name = "rules_xcodeproj",
|
||||
path = "build-system/bazel-rules/rules_xcodeproj",
|
||||
)
|
||||
|
||||
load(
|
||||
"@build_bazel_rules_apple//apple:repositories.bzl",
|
||||
"apple_rules_dependencies",
|
||||
@ -47,6 +52,13 @@ load(
|
||||
|
||||
apple_support_dependencies()
|
||||
|
||||
load(
|
||||
"@rules_xcodeproj//xcodeproj:repositories.bzl",
|
||||
"xcodeproj_rules_dependencies",
|
||||
)
|
||||
|
||||
xcodeproj_rules_dependencies()
|
||||
|
||||
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
|
||||
|
||||
bazel_skylib_workspace()
|
||||
|
@ -35,8 +35,9 @@ class BuildConfiguration:
|
||||
self.enable_siri = enable_siri
|
||||
self.enable_icloud = enable_icloud
|
||||
|
||||
def write_to_variables_file(self, aps_environment, path):
|
||||
def write_to_variables_file(self, bazel_path, aps_environment, path):
|
||||
string = ''
|
||||
string += 'telegram_bazel_path = "{}"\n'.format(bazel_path)
|
||||
string += 'telegram_bundle_id = "{}"\n'.format(self.bundle_id)
|
||||
string += 'telegram_api_id = "{}"\n'.format(self.api_id)
|
||||
string += 'telegram_api_hash = "{}"\n'.format(self.api_hash)
|
||||
|
@ -30,6 +30,7 @@ class BazelCommandLine:
|
||||
override_bazel_version=override_bazel_version,
|
||||
override_xcode_version=override_xcode_version
|
||||
)
|
||||
self.bazel = bazel
|
||||
self.bazel_user_root = bazel_user_root
|
||||
self.remote_cache = None
|
||||
self.cache_dir = None
|
||||
@ -497,7 +498,8 @@ def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, argum
|
||||
print('Could not find a valid aps-environment entitlement in the provided provisioning profiles')
|
||||
sys.exit(1)
|
||||
|
||||
build_configuration.write_to_variables_file(aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl')
|
||||
if bazel_command_line is not None:
|
||||
build_configuration.write_to_variables_file(bazel_path=bazel_command_line.bazel, aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl')
|
||||
|
||||
provisioning_profile_files = []
|
||||
for file_name in os.listdir(provisioning_path):
|
||||
|
@ -9,8 +9,37 @@ def remove_directory(path):
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name):
|
||||
bazel_generate_arguments = [build_environment.bazel_path]
|
||||
bazel_generate_arguments += ['run', '//Telegram:Telegram_xcodeproj']
|
||||
bazel_generate_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)]
|
||||
#if disable_extensions:
|
||||
# bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)]
|
||||
#if disable_provisioning_profiles:
|
||||
# bazel_generate_arguments += ['--//{}:disableProvisioningProfiles'.format(app_target)]
|
||||
#if generate_dsym:
|
||||
# bazel_generate_arguments += ['--apple_generate_dsym']
|
||||
#bazel_generate_arguments += ['--//{}:disableStripping'.format('Telegram')]
|
||||
|
||||
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name):
|
||||
project_bazel_arguments = []
|
||||
for argument in bazel_app_arguments:
|
||||
project_bazel_arguments.append(argument)
|
||||
project_bazel_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)]
|
||||
|
||||
xcodeproj_bazelrc = os.path.join(build_environment.base_path, 'xcodeproj.bazelrc')
|
||||
if os.path.isfile(xcodeproj_bazelrc):
|
||||
os.unlink(xcodeproj_bazelrc)
|
||||
with open(xcodeproj_bazelrc, 'w') as file:
|
||||
for argument in project_bazel_arguments:
|
||||
file.write('build ' + argument + '\n')
|
||||
|
||||
call_executable(bazel_generate_arguments)
|
||||
|
||||
xcodeproj_path = 'Telegram/Telegram.xcodeproj'
|
||||
call_executable(['open', xcodeproj_path])
|
||||
|
||||
|
||||
def generate_tulsi(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name):
|
||||
project_path = os.path.join(build_environment.base_path, 'build-input/gen/project')
|
||||
|
||||
if '/' in target_name:
|
||||
@ -141,3 +170,9 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
|
||||
xcodeproj_path = '{project}/{target}.xcodeproj'.format(project=project_path, target=app_target_clean)
|
||||
|
||||
call_executable(['open', xcodeproj_path])
|
||||
|
||||
|
||||
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name):
|
||||
generate_xcodeproj(build_environment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name)
|
||||
#generate_tulsi(build_environment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name)
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 2659bae1f561e34b89fcc230df26aaf6dada2646
|
||||
Subproject commit 7cecc126925fef811c274b10b99329179574f9cb
|
@ -1 +1 @@
|
||||
Subproject commit 782fa6bb135b5a9a31ab7884e9ebfaac29ef71d9
|
||||
Subproject commit 488633f6fa04cbc9310079e368955b9863754e64
|
@ -1 +1 @@
|
||||
Subproject commit 371ab0507ab2318f0936adde3bcebbb1ccacf3a8
|
||||
Subproject commit e01b8f76e7666ac254f1780b00de33c1296da8ff
|
1
build-system/bazel-rules/rules_xcodeproj
Submodule
1
build-system/bazel-rules/rules_xcodeproj
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit dc226d129aca2237982b98a95c80ed1ccc74f0c5
|
@ -22,15 +22,11 @@ def _plist_fragment(ctx):
|
||||
fail("Expected value for --define={} was not found".format(key))
|
||||
resolved_values[key] = value
|
||||
|
||||
plist_string = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
""" + template.format(**resolved_values) + """
|
||||
</dict>
|
||||
</plist>
|
||||
"""
|
||||
plist_string = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>""" + template.format(**resolved_values) + """</dict>
|
||||
</plist>"""
|
||||
|
||||
ctx.actions.write(
|
||||
output = output,
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 2b8fbf5a95d43dd43ba20b5a690c2bf8e1146063
|
||||
Subproject commit a0bf60e1645869c6452c9f3b128362d433764f19
|
@ -20,4 +20,6 @@ public protocol ChatListController: ViewController {
|
||||
func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
|
||||
|
||||
func playSignUpCompletedAnimation()
|
||||
|
||||
func navigateToFolder(folderId: Int32, completion: @escaping () -> Void)
|
||||
}
|
||||
|
@ -1546,7 +1546,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
for filter in filters {
|
||||
if filter.id == filterId, case let .filter(_, title, _, data) = filter {
|
||||
if !data.includePeers.peers.isEmpty {
|
||||
if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty {
|
||||
items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, badge: ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, f in
|
||||
@ -2709,6 +2709,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
})
|
||||
}
|
||||
|
||||
public func navigateToFolder(folderId: Int32, completion: @escaping () -> Void) {
|
||||
let _ = (self.chatListDisplayNode.mainContainerNode.availableFiltersSignal
|
||||
|> filter { filters in
|
||||
return filters.contains(where: { item in
|
||||
if case let .filter(filter) = item, filter.id == folderId {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ -> Bool in
|
||||
return true
|
||||
}
|
||||
|> timeout(1.0, queue: .mainQueue(), alternate: .single(false))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id != folderId {
|
||||
self.chatListDisplayNode.mainContainerNode.switchToFilter(id: .filter(folderId), completion: {
|
||||
completion()
|
||||
})
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func askForFilterRemoval(id: Int32) {
|
||||
let apply: () -> Void = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@ -2753,17 +2784,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||
let _ = (combineLatest(
|
||||
self.context.engine.data.get(
|
||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||
EngineDataMap(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init(id:)))
|
||||
),
|
||||
self.context.engine.peers.getExportedChatFolderLinks(id: id)
|
||||
self.context.engine.peers.getExportedChatFolderLinks(id: id),
|
||||
self.context.engine.peers.requestLeaveChatFolderSuggestions(folderId: id)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peers, links in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerData, links, defaultSelectedPeerIds in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = self.presentationData
|
||||
|
||||
let peers = peerData.0
|
||||
|
||||
var memberCounts: [EnginePeer.Id: Int] = [:]
|
||||
for (id, count) in peerData.1 {
|
||||
if let count {
|
||||
memberCounts[id] = count
|
||||
}
|
||||
}
|
||||
|
||||
var hasLinks = false
|
||||
if let links, !links.isEmpty {
|
||||
hasLinks = true
|
||||
@ -2776,7 +2818,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let previewScreen = ChatFolderLinkPreviewScreen(
|
||||
context: self.context,
|
||||
subject: .remove(folderId: id),
|
||||
subject: .remove(folderId: id, defaultSelectedPeerIds: defaultSelectedPeerIds),
|
||||
contents: ChatFolderLinkContents(
|
||||
localFilterId: id,
|
||||
title: title,
|
||||
@ -2787,7 +2829,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return false
|
||||
}
|
||||
},
|
||||
alreadyMemberPeerIds: Set()
|
||||
alreadyMemberPeerIds: Set(),
|
||||
memberCounts: memberCounts
|
||||
),
|
||||
completion: { [weak self] in
|
||||
guard let self else {
|
||||
|
@ -513,7 +513,16 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
|
||||
private var itemNodes: [ChatListFilterTabEntryId: ChatListContainerItemNode] = [:]
|
||||
private var pendingItemNode: (ChatListFilterTabEntryId, ChatListContainerItemNode, Disposable)?
|
||||
private(set) var availableFilters: [ChatListContainerNodeFilter] = [.all]
|
||||
private(set) var availableFilters: [ChatListContainerNodeFilter] = [.all] {
|
||||
didSet {
|
||||
self.availableFiltersPromise.set(self.availableFilters)
|
||||
}
|
||||
}
|
||||
private let availableFiltersPromise = ValuePromise<[ChatListContainerNodeFilter]>([.all], ignoreRepeated: true)
|
||||
var availableFiltersSignal: Signal<[ChatListContainerNodeFilter], NoError> {
|
||||
return self.availableFiltersPromise.get()
|
||||
}
|
||||
|
||||
private var filtersLimit: Int32? = nil
|
||||
private var selectedId: ChatListFilterTabEntryId
|
||||
|
||||
|
@ -31,10 +31,12 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
let setItemIdWithRevealedOptions: (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void
|
||||
let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void
|
||||
let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void
|
||||
let clearFocus: () -> Void
|
||||
let focusOnName: () -> Void
|
||||
let expandSection: (FilterSection) -> Void
|
||||
let createLink: () -> Void
|
||||
let openLink: (ExportedChatFolderLink) -> Void
|
||||
let removeLink: (ExportedChatFolderLink) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -46,10 +48,12 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void,
|
||||
deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void,
|
||||
deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void,
|
||||
clearFocus: @escaping () -> Void,
|
||||
focusOnName: @escaping () -> Void,
|
||||
expandSection: @escaping (FilterSection) -> Void,
|
||||
createLink: @escaping () -> Void,
|
||||
openLink: @escaping (ExportedChatFolderLink) -> Void
|
||||
openLink: @escaping (ExportedChatFolderLink) -> Void,
|
||||
removeLink: @escaping (ExportedChatFolderLink) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
@ -60,10 +64,12 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions
|
||||
self.deleteIncludeCategory = deleteIncludeCategory
|
||||
self.deleteExcludeCategory = deleteExcludeCategory
|
||||
self.clearFocus = clearFocus
|
||||
self.focusOnName = focusOnName
|
||||
self.expandSection = expandSection
|
||||
self.createLink = createLink
|
||||
self.openLink = openLink
|
||||
self.removeLink = removeLink
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,10 +322,10 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
case excludePeerInfo(String)
|
||||
case includeExpand(String)
|
||||
case excludeExpand(String)
|
||||
case inviteLinkHeader
|
||||
case inviteLinkHeader(hasLinks: Bool)
|
||||
case inviteLinkCreate(hasLinks: Bool)
|
||||
case inviteLink(Int, ExportedChatFolderLink)
|
||||
case inviteLinkInfo
|
||||
case inviteLinkInfo(text: String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -434,14 +440,16 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
case let .nameHeader(title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .name(placeholder, value):
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), clearType: .always, maxLength: 12, sectionId: self.section, textUpdated: { value in
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .done, clearType: .always, maxLength: 12, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.name = value
|
||||
state.changedName = true
|
||||
return state
|
||||
}
|
||||
}, action: {}, cleared: {
|
||||
}, action: {
|
||||
arguments.clearFocus()
|
||||
}, cleared: {
|
||||
arguments.focusOnName()
|
||||
})
|
||||
case .includePeersHeader(let text), .excludePeersHeader(let text):
|
||||
@ -516,23 +524,23 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandSection(.exclude)
|
||||
})
|
||||
case .inviteLinkHeader:
|
||||
case let .inviteLinkHeader(hasLinks):
|
||||
//TODO:localize
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", badge: "NEW", sectionId: self.section)
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "SHARE FOLDER", badge: hasLinks ? nil : "NEW", sectionId: self.section)
|
||||
case let .inviteLinkCreate(hasLinks):
|
||||
//TODO:localize
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? "Create a New Link" : "Share Folder", sectionId: self.section, editing: false, action: {
|
||||
let _ = hasLinks
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "Create an Invite Link", sectionId: self.section, editing: false, action: {
|
||||
arguments.createLink()
|
||||
})
|
||||
case let .inviteLink(_, link):
|
||||
return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in
|
||||
arguments.openLink(invite)
|
||||
} contextAction: { invite, node, gesture in
|
||||
//arguments.linkContextAction(invite, canEdit, node, gesture)
|
||||
}
|
||||
case .inviteLinkInfo:
|
||||
//TODO:localize
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("Share access to some of this folder's groups and channels with others."), sectionId: self.section)
|
||||
}, removeAction: { invite in
|
||||
arguments.removeLink(invite)
|
||||
}, contextAction: nil)
|
||||
case let .inviteLinkInfo(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -654,26 +662,25 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
||||
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
||||
}
|
||||
|
||||
if !isNewFilter {
|
||||
entries.append(.inviteLinkHeader)
|
||||
|
||||
var hasLinks = false
|
||||
if let inviteLinks, !inviteLinks.isEmpty {
|
||||
hasLinks = true
|
||||
}
|
||||
entries.append(.inviteLinkCreate(hasLinks: hasLinks))
|
||||
|
||||
if let inviteLinks {
|
||||
var index = 0
|
||||
for link in inviteLinks {
|
||||
entries.append(.inviteLink(index, link))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
entries.append(.inviteLinkInfo)
|
||||
var hasLinks = false
|
||||
if let inviteLinks, !inviteLinks.isEmpty {
|
||||
hasLinks = true
|
||||
}
|
||||
|
||||
entries.append(.inviteLinkHeader(hasLinks: hasLinks))
|
||||
entries.append(.inviteLinkCreate(hasLinks: hasLinks))
|
||||
|
||||
if let inviteLinks {
|
||||
var index = 0
|
||||
for link in inviteLinks {
|
||||
entries.append(.inviteLink(index, link))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
entries.append(.inviteLinkInfo(text: hasLinks ? "Create more links to set up different access levels for different people." : "Share access to some of this folder's groups and channels with others."))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -1072,6 +1079,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var focusOnNameImpl: (() -> Void)?
|
||||
var clearFocusImpl: (() -> Void)?
|
||||
var applyImpl: ((@escaping () -> Void) -> Void)?
|
||||
|
||||
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
||||
@ -1270,6 +1278,9 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
return state
|
||||
}
|
||||
},
|
||||
clearFocus: {
|
||||
clearFocusImpl?()
|
||||
},
|
||||
focusOnName: {
|
||||
focusOnNameImpl?()
|
||||
},
|
||||
@ -1281,44 +1292,62 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}
|
||||
},
|
||||
createLink: {
|
||||
applyImpl?({
|
||||
let state = stateValue.with({ $0 })
|
||||
|
||||
if let currentPreset, let data = currentPreset.data {
|
||||
//TODO:localize
|
||||
var unavailableText: String?
|
||||
if !data.categories.isEmpty || data.excludeArchived || data.excludeRead || data.excludeMuted || !data.excludePeers.isEmpty {
|
||||
unavailableText = "You can't share a link to this folder."
|
||||
}
|
||||
if let unavailableText {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
|
||||
return
|
||||
}
|
||||
if currentPreset == nil {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text = "Please finish creating this folder to share it."
|
||||
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
} else {
|
||||
applyImpl?({
|
||||
let state = stateValue.with({ $0 })
|
||||
|
||||
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, checkIfExists: false, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
|
||||
pushControllerImpl?(c)
|
||||
}, presentController: { c in
|
||||
presentControllerImpl?(c, nil)
|
||||
}, linkUpdated: { updatedLink in
|
||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||
guard var links else {
|
||||
return
|
||||
}
|
||||
if let currentPreset, let data = currentPreset.data {
|
||||
//TODO:localize
|
||||
var unavailableText: String?
|
||||
if !data.categories.isEmpty {
|
||||
unavailableText = "You can’t share folders with include chat types."
|
||||
} else if data.excludeArchived || data.excludeRead || data.excludeMuted {
|
||||
unavailableText = "You can only share folders without chat types and excluded chats."
|
||||
} else if !data.excludePeers.isEmpty {
|
||||
unavailableText = "You can’t share folders with excluded chats"
|
||||
}
|
||||
if let unavailableText {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
|
||||
if let updatedLink {
|
||||
if let index = links.firstIndex(where: { $0.link == updatedLink.link }) {
|
||||
links[index] = updatedLink
|
||||
} else {
|
||||
links.insert(updatedLink, at: 0)
|
||||
return
|
||||
}
|
||||
|
||||
var previousLink: ExportedChatFolderLink?
|
||||
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, checkIfExists: false, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
|
||||
pushControllerImpl?(c)
|
||||
}, presentController: { c in
|
||||
presentControllerImpl?(c, nil)
|
||||
}, linkUpdated: { updatedLink in
|
||||
let previousLinkValue = previousLink
|
||||
previousLink = updatedLink
|
||||
|
||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||
guard var links else {
|
||||
return
|
||||
}
|
||||
|
||||
if let updatedLink {
|
||||
if let index = links.firstIndex(where: { $0.link == updatedLink.link }) {
|
||||
links[index] = updatedLink
|
||||
} else {
|
||||
links.insert(updatedLink, at: 0)
|
||||
}
|
||||
} else if let previousLinkValue {
|
||||
if let index = links.firstIndex(where: { $0.link == previousLinkValue.link }) {
|
||||
links.remove(at: index)
|
||||
}
|
||||
}
|
||||
sharedLinks.set(.single(links))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, openLink: { link in
|
||||
if let currentPreset, let _ = currentPreset.data {
|
||||
applyImpl?({
|
||||
@ -1331,13 +1360,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}
|
||||
|
||||
if let updatedLink {
|
||||
if let index = links.firstIndex(where: { $0 == link }) {
|
||||
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||
links.remove(at: index)
|
||||
}
|
||||
links.insert(updatedLink, at: 0)
|
||||
sharedLinks.set(.single(links))
|
||||
} else {
|
||||
if let index = links.firstIndex(where: { $0 == link }) {
|
||||
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||
links.remove(at: index)
|
||||
sharedLinks.set(.single(links))
|
||||
}
|
||||
@ -1347,6 +1376,22 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
removeLink: { link in
|
||||
if let currentPreset {
|
||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||
guard var links else {
|
||||
return
|
||||
}
|
||||
|
||||
if let index = links.firstIndex(where: { $0.link == link.link }) {
|
||||
links.remove(at: index)
|
||||
}
|
||||
sharedLinks.set(.single(links))
|
||||
|
||||
actionsDisposable.add(context.engine.peers.deleteChatFolderLink(filterId: currentPreset.id, link: link).start())
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -1462,6 +1507,12 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}
|
||||
}
|
||||
}
|
||||
clearFocusImpl = { [weak controller] in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
controller.view.endEditing(true)
|
||||
}
|
||||
controller.attemptNavigation = { _ in
|
||||
return attemptNavigationImpl?() ?? true
|
||||
}
|
||||
@ -1493,7 +1544,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if state.isComplete {
|
||||
if currentPreset != nil, state.isComplete {
|
||||
displaySaveAlert()
|
||||
return false
|
||||
}
|
||||
@ -1575,15 +1626,18 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
|
||||
switch error {
|
||||
case .generic:
|
||||
text = "An error occurred"
|
||||
case let .limitExceeded(limit, premiumLimit):
|
||||
if limit < premiumLimit {
|
||||
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
|
||||
})
|
||||
pushController(limitController)
|
||||
|
||||
return
|
||||
}
|
||||
text = "You can't create more links."
|
||||
case let .sharedFolderLimitExceeded(limit, _):
|
||||
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .membershipInSharedFolders, count: limit, action: {
|
||||
})
|
||||
pushController(limitController)
|
||||
|
||||
return
|
||||
case let .limitExceeded(limit, _):
|
||||
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
|
||||
})
|
||||
pushController(limitController)
|
||||
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
|
||||
|
@ -393,13 +393,23 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||
let _ = (combineLatest(
|
||||
context.engine.data.get(
|
||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||
EngineDataMap(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init(id:)))
|
||||
),
|
||||
context.engine.peers.getExportedChatFolderLinks(id: id)
|
||||
context.engine.peers.getExportedChatFolderLinks(id: id),
|
||||
context.engine.peers.requestLeaveChatFolderSuggestions(folderId: id)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { peers, links in
|
||||
|> deliverOnMainQueue).start(next: { peerData, links, defaultSelectedPeerIds in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let peers = peerData.0
|
||||
var memberCounts: [EnginePeer.Id: Int] = [:]
|
||||
for (id, count) in peerData.1 {
|
||||
if let count {
|
||||
memberCounts[id] = count
|
||||
}
|
||||
}
|
||||
|
||||
var hasLinks = false
|
||||
if let links, !links.isEmpty {
|
||||
hasLinks = true
|
||||
@ -408,7 +418,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
let confirmDeleteFolder: () -> Void = {
|
||||
let previewScreen = ChatFolderLinkPreviewScreen(
|
||||
context: context,
|
||||
subject: .remove(folderId: id),
|
||||
subject: .remove(folderId: id, defaultSelectedPeerIds: defaultSelectedPeerIds),
|
||||
contents: ChatFolderLinkContents(
|
||||
localFilterId: id,
|
||||
title: title,
|
||||
@ -419,7 +429,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
return false
|
||||
}
|
||||
},
|
||||
alreadyMemberPeerIds: Set()
|
||||
alreadyMemberPeerIds: Set(),
|
||||
memberCounts: memberCounts
|
||||
)
|
||||
)
|
||||
pushControllerImpl?(previewScreen)
|
||||
|
@ -215,7 +215,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
} else {
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
}
|
||||
updatedSharedIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Share"), color: item.presentationData.theme.list.disclosureArrowColor)
|
||||
updatedSharedIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/SharedFolderListIcon"), color: item.presentationData.theme.list.disclosureArrowColor)
|
||||
}
|
||||
|
||||
let peerRevealOptions: [ItemListRevealOption]
|
||||
|
@ -69,7 +69,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
|
||||
|
||||
case peersHeader(String)
|
||||
case peer(index: Int, peer: EnginePeer, isSelected: Bool, isEnabled: Bool)
|
||||
case peer(index: Int, peer: EnginePeer, isSelected: Bool, disabledReasonText: String?)
|
||||
case peersInfo(String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
@ -149,8 +149,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(index, peer, isSelected, isEnabled):
|
||||
if case .peer(index, peer, isSelected, isEnabled) = rhs {
|
||||
case let .peer(index, peer, isSelected, disabledReasonText):
|
||||
if case .peer(index, peer, isSelected, disabledReasonText) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -195,7 +195,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .peersInfo(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .peer(_, peer, isSelected, isEnabled):
|
||||
case let .peer(_, peer, isSelected, disabledReasonText):
|
||||
//TODO:localize
|
||||
return ItemListPeerItem(
|
||||
presentationData: presentationData,
|
||||
@ -204,16 +204,16 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
context: arguments.context,
|
||||
peer: peer,
|
||||
presence: nil,
|
||||
text: .text(isEnabled ? "you can invite others here" : "you can't invite others here", .secondary),
|
||||
text: .text(disabledReasonText ?? "you can invite others here", .secondary),
|
||||
label: .none,
|
||||
editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false),
|
||||
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: isEnabled),
|
||||
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: disabledReasonText == nil),
|
||||
enabled: true,
|
||||
selectable: true,
|
||||
highlightable: false,
|
||||
sectionId: self.section,
|
||||
action: {
|
||||
arguments.peerAction(peer, isEnabled)
|
||||
arguments.peerAction(peer, disabledReasonText == nil)
|
||||
},
|
||||
setPeerIdWithRevealedOptions: { _, _ in
|
||||
},
|
||||
@ -274,8 +274,19 @@ private func folderInviteLinkListControllerEntries(
|
||||
}
|
||||
|
||||
for peer in sortedPeers {
|
||||
let isEnabled = canShareLinkToPeer(peer: peer)
|
||||
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), isEnabled: isEnabled))
|
||||
var disabledReasonText: String?
|
||||
if !canShareLinkToPeer(peer: peer) {
|
||||
if case let .user(user) = peer {
|
||||
if user.botInfo != nil {
|
||||
disabledReasonText = "you can't share chats with bots"
|
||||
} else {
|
||||
disabledReasonText = "you can't share private chats"
|
||||
}
|
||||
} else {
|
||||
disabledReasonText = "you can't invite other here"
|
||||
}
|
||||
}
|
||||
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), disabledReasonText: disabledReasonText))
|
||||
}
|
||||
|
||||
if let infoString {
|
||||
@ -391,7 +402,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
|
||||
let state = stateValue.with({ $0 })
|
||||
|
||||
let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Link title", value: state.title ?? "", apply: { value in
|
||||
let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Name This Link", titleFont: .bold, value: state.title ?? "", apply: { value in
|
||||
if let value {
|
||||
updateState { state in
|
||||
var state = state
|
||||
@ -458,7 +469,10 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
state.selectedPeerIds.remove(peer.id)
|
||||
} else {
|
||||
state.selectedPeerIds.insert(peer.id)
|
||||
added = true
|
||||
|
||||
if let currentInvitation, !currentInvitation.peerIds.contains(peer.id) {
|
||||
added = true
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
@ -469,7 +483,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
|
||||
dismissTooltipsImpl?()
|
||||
//TODO:localize
|
||||
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats."))
|
||||
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats.", timeout: 8))
|
||||
}
|
||||
} else {
|
||||
//TODO:localize
|
||||
@ -549,7 +563,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
dismissTooltipsImpl?()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
//TODO:localize
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred.", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
}, completed: {
|
||||
linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false))
|
||||
dismissImpl?()
|
||||
@ -587,6 +601,8 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
}
|
||||
})
|
||||
|
||||
let previousState = Atomic<FolderInviteLinkListControllerState?>(value: nil)
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
presentationData,
|
||||
@ -595,7 +611,13 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
)
|
||||
|> map { presentationData, state, allPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let crossfade = false
|
||||
let animateChanges = false
|
||||
|
||||
var animateChanges = false
|
||||
|
||||
let previousStateValue = previousState.swap(state)
|
||||
if let previousStateValue, previousStateValue.selectedPeerIds != state.selectedPeerIds {
|
||||
animateChanges = true
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let title: ItemListControllerTitle
|
||||
@ -610,7 +632,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
if state.isSaving {
|
||||
doneButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||
} else {
|
||||
doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
||||
doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Save), style: .bold, enabled: !state.selectedPeerIds.isEmpty, action: {
|
||||
applyChangesImpl?()
|
||||
})
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
case requestHeader(PresentationTheme, String, String, Bool)
|
||||
case request(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
||||
case importerHeader(PresentationTheme, String, String, Bool)
|
||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool, Bool)
|
||||
|
||||
var stableId: InviteLinkViewEntryId {
|
||||
switch self {
|
||||
@ -82,7 +82,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
return .request(peer.id)
|
||||
case .importerHeader:
|
||||
return .importerHeader
|
||||
case let .importer(_, _, _, peer, _, _):
|
||||
case let .importer(_, _, _, peer, _, _, _):
|
||||
return .importer(peer.id)
|
||||
}
|
||||
}
|
||||
@ -125,8 +125,8 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsLoading):
|
||||
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsLoading == rhsLoading {
|
||||
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsJoinedViaFolderLink, lhsLoading):
|
||||
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsJoinedViaFolderLink, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsJoinedViaFolderLink == rhsJoinedViaFolderLink, lhsLoading == rhsLoading {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -180,11 +180,11 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
case .importer:
|
||||
return true
|
||||
}
|
||||
case let .importer(lhsIndex, _, _, _, _, _):
|
||||
case let .importer(lhsIndex, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
||||
return false
|
||||
case let .importer(rhsIndex, _, _, _, _, _):
|
||||
case let .importer(rhsIndex, _, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
@ -224,7 +224,18 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
additionalText = .none
|
||||
}
|
||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title, additionalText: additionalText)
|
||||
case let .importer(_, _, dateTimeFormat, peer, date, loading), let .request(_, _, dateTimeFormat, peer, date, loading):
|
||||
case let .importer(_, _, dateTimeFormat, peer, date, joinedViaFolderLink, loading):
|
||||
let dateString: String
|
||||
if joinedViaFolderLink {
|
||||
//TODO:localize
|
||||
dateString = "joined via a folder invite link"
|
||||
} else {
|
||||
dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
}
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||
case let .request(_, _, dateTimeFormat, peer, date, loading):
|
||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
@ -753,14 +764,14 @@ public final class InviteLinkViewController: ViewController {
|
||||
loading = true
|
||||
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])
|
||||
for i in 0 ..< count {
|
||||
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, true))
|
||||
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, false, true))
|
||||
}
|
||||
} else {
|
||||
count = min(4, Int32(state.importers.count))
|
||||
loading = false
|
||||
for importer in state.importers {
|
||||
if let peer = importer.peer.peer {
|
||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, false))
|
||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, importer.joinedViaFolderLink, false))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
||||
public let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let tapAction: ((ExportedChatFolderLink) -> Void)?
|
||||
let removeAction: ((ExportedChatFolderLink) -> Void)?
|
||||
let contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
@ -48,6 +49,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
||||
sectionId: ItemListSectionId,
|
||||
style: ItemListStyle,
|
||||
tapAction: ((ExportedChatFolderLink) -> Void)?,
|
||||
removeAction: ((ExportedChatFolderLink) -> Void)?,
|
||||
contextAction: ((ExportedChatFolderLink, ASDisplayNode, ContextGesture?) -> Void)?,
|
||||
tag: ItemListItemTag? = nil
|
||||
) {
|
||||
@ -57,6 +59,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
self.tapAction = tapAction
|
||||
self.removeAction = removeAction
|
||||
self.contextAction = contextAction
|
||||
self.tag = tag
|
||||
}
|
||||
@ -125,7 +128,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListItemNode {
|
||||
public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
@ -506,6 +509,15 @@ public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListIte
|
||||
strongSelf.placeholderNode = nil
|
||||
shimmerNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
|
||||
if item.removeAction != nil {
|
||||
//TODO:localize
|
||||
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Delete", icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
|
||||
} else {
|
||||
strongSelf.setRevealOptions((left: [], right: []))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -565,6 +577,21 @@ public class ItemListFolderInviteLinkListItemNode: ListViewItemNode, ItemListIte
|
||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.updateRevealOffset(offset: offset, transition: transition)
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: self.containerNode.layer, offset: CGPoint(x: offset, y: 0.0))
|
||||
}
|
||||
|
||||
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||
if let item = self.layoutParams?.0, let invite = item.invite {
|
||||
item.removeAction?(invite)
|
||||
}
|
||||
|
||||
self.setRevealOptionsOpened(false, animated: true)
|
||||
self.revealOptionsInteractivelyClosed()
|
||||
}
|
||||
}
|
||||
|
||||
private struct ContentParticle {
|
||||
|
@ -541,7 +541,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
if data.count > settings.maxSize {
|
||||
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string))
|
||||
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string, timeout: nil))
|
||||
|
||||
souceUrl.stopAccessingSecurityScopedResource()
|
||||
TempBox.shared.dispose(tempFile)
|
||||
@ -594,7 +594,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co
|
||||
if duration > Double(settings.maxDuration) {
|
||||
souceUrl.stopAccessingSecurityScopedResource()
|
||||
|
||||
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
|
||||
presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string, timeout: nil))
|
||||
} else {
|
||||
Logger.shared.log("NotificationSoundSelection", "Uploading sound")
|
||||
|
||||
|
@ -150,6 +150,7 @@ private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDeleg
|
||||
private final class PromptAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let text: String
|
||||
private let titleFont: PromptControllerTitleFont
|
||||
|
||||
private let textNode: ASTextNode
|
||||
let inputFieldNode: PromptInputFieldNode
|
||||
@ -174,9 +175,10 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, value: String?) {
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?) {
|
||||
self.strings = strings
|
||||
self.text = text
|
||||
self.titleFont = titleFont
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
@ -244,7 +246,14 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
let titleFontValue: UIFont
|
||||
switch self.titleFont {
|
||||
case .regular:
|
||||
titleFontValue = Font.regular(13.0)
|
||||
case .bold:
|
||||
titleFontValue = Font.semibold(17.0)
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: titleFontValue, textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
@ -379,7 +388,12 @@ private final class PromptAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
public enum PromptControllerTitleFont {
|
||||
case regular
|
||||
case bold
|
||||
}
|
||||
|
||||
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
@ -393,7 +407,7 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, value: value)
|
||||
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
|
@ -238,6 +238,10 @@ public final class QrCodeScreen: ViewController {
|
||||
case let .invite(_, isGroup):
|
||||
title = self.presentationData.strings.InviteLink_QRCode_Title
|
||||
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
|
||||
case .chatFolder:
|
||||
//TODO:localize
|
||||
title = "Invite by QR Code"
|
||||
text = "Everyone on Telegram can scan this code to join your folder."
|
||||
default:
|
||||
title = ""
|
||||
text = ""
|
||||
|
@ -470,7 +470,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat
|
||||
let presentGlobalController = context.sharedContext.presentGlobalController
|
||||
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: false).start(completed: {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
presentGlobalController(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.DeleteAccount_Success), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
presentGlobalController(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.DeleteAccount_Success, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -920,7 +920,7 @@ public func privacyAndSecurityController(
|
||||
hapticFeedback.impact()
|
||||
|
||||
var alreadyPresented = false
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
if action == .info {
|
||||
if !alreadyPresented {
|
||||
let controller = PremiumIntroScreen(context: context, source: .settings)
|
||||
|
@ -1487,7 +1487,7 @@ public final class SolidRoundedButtonView: UIView {
|
||||
badgeNode = current
|
||||
} else {
|
||||
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
|
||||
badgeNode.alpha = self.titleNode.alpha
|
||||
badgeNode.alpha = self.titleNode.alpha == 0.0 ? 0.0 : 1.0
|
||||
self.badgeNode = badgeNode
|
||||
self.addSubnode(badgeNode)
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1091179342] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteRevoke($0) }
|
||||
dict[-484690728] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantInvite($0) }
|
||||
dict[405815507] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoin($0) }
|
||||
dict[1557846647] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByInvite($0) }
|
||||
dict[-23084712] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByInvite($0) }
|
||||
dict[-1347021750] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByRequest($0) }
|
||||
dict[-124291086] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantLeave($0) }
|
||||
dict[-115071790] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantMute($0) }
|
||||
|
@ -700,7 +700,7 @@ public extension Api {
|
||||
case channelAdminLogEventActionExportedInviteRevoke(invite: Api.ExportedChatInvite)
|
||||
case channelAdminLogEventActionParticipantInvite(participant: Api.ChannelParticipant)
|
||||
case channelAdminLogEventActionParticipantJoin
|
||||
case channelAdminLogEventActionParticipantJoinByInvite(invite: Api.ExportedChatInvite)
|
||||
case channelAdminLogEventActionParticipantJoinByInvite(flags: Int32, invite: Api.ExportedChatInvite)
|
||||
case channelAdminLogEventActionParticipantJoinByRequest(invite: Api.ExportedChatInvite, approvedBy: Int64)
|
||||
case channelAdminLogEventActionParticipantLeave
|
||||
case channelAdminLogEventActionParticipantMute(participant: Api.GroupCallParticipant)
|
||||
@ -878,10 +878,11 @@ public extension Api {
|
||||
}
|
||||
|
||||
break
|
||||
case .channelAdminLogEventActionParticipantJoinByInvite(let invite):
|
||||
case .channelAdminLogEventActionParticipantJoinByInvite(let flags, let invite):
|
||||
if boxed {
|
||||
buffer.appendInt32(1557846647)
|
||||
buffer.appendInt32(-23084712)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
invite.serialize(buffer, true)
|
||||
break
|
||||
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
||||
@ -1059,8 +1060,8 @@ public extension Api {
|
||||
return ("channelAdminLogEventActionParticipantInvite", [("participant", participant as Any)])
|
||||
case .channelAdminLogEventActionParticipantJoin:
|
||||
return ("channelAdminLogEventActionParticipantJoin", [])
|
||||
case .channelAdminLogEventActionParticipantJoinByInvite(let invite):
|
||||
return ("channelAdminLogEventActionParticipantJoinByInvite", [("invite", invite as Any)])
|
||||
case .channelAdminLogEventActionParticipantJoinByInvite(let flags, let invite):
|
||||
return ("channelAdminLogEventActionParticipantJoinByInvite", [("flags", flags as Any), ("invite", invite as Any)])
|
||||
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
||||
return ("channelAdminLogEventActionParticipantJoinByRequest", [("invite", invite as Any), ("approvedBy", approvedBy as Any)])
|
||||
case .channelAdminLogEventActionParticipantLeave:
|
||||
@ -1431,13 +1432,16 @@ public extension Api {
|
||||
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoin
|
||||
}
|
||||
public static func parse_channelAdminLogEventActionParticipantJoinByInvite(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
||||
var _1: Api.ExportedChatInvite?
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.ExportedChatInvite?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByInvite(invite: _1!)
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByInvite(flags: _1!, invite: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -1574,7 +1574,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
}).start()
|
||||
}
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess, timeout: nil), action: { _ in return false })
|
||||
}
|
||||
})
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
@ -1595,7 +1595,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
if let strongSelf = self, let (firstName, lastName) = firstAndLastName {
|
||||
let _ = context.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).start()
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .info(title: nil, text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess, timeout: nil), action: { _ in return false })
|
||||
}
|
||||
})
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
|
@ -73,7 +73,7 @@ public enum AdminLogEventAction {
|
||||
case deleteExportedInvitation(ExportedInvitation)
|
||||
case revokeExportedInvitation(ExportedInvitation)
|
||||
case editExportedInvitation(previous: ExportedInvitation, updated: ExportedInvitation)
|
||||
case participantJoinedViaInvite(ExportedInvitation)
|
||||
case participantJoinedViaInvite(invitation: ExportedInvitation, joinedViaFolderLink: Bool)
|
||||
case changeHistoryTTL(previousValue: Int32?, updatedValue: Int32?)
|
||||
case changeTheme(previous: String?, updated: String?)
|
||||
case participantJoinByRequest(invitation: ExportedInvitation, approvedBy: PeerId)
|
||||
@ -268,8 +268,8 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m
|
||||
action = .revokeExportedInvitation(ExportedInvitation(apiExportedInvite: invite))
|
||||
case let .channelAdminLogEventActionExportedInviteEdit(prevInvite, newInvite):
|
||||
action = .editExportedInvitation(previous: ExportedInvitation(apiExportedInvite: prevInvite), updated: ExportedInvitation(apiExportedInvite: newInvite))
|
||||
case let .channelAdminLogEventActionParticipantJoinByInvite(invite):
|
||||
action = .participantJoinedViaInvite(ExportedInvitation(apiExportedInvite: invite))
|
||||
case let .channelAdminLogEventActionParticipantJoinByInvite(flags, invite):
|
||||
action = .participantJoinedViaInvite(invitation: ExportedInvitation(apiExportedInvite: invite), joinedViaFolderLink: (flags & (1 << 0)) != 0)
|
||||
case let .channelAdminLogEventActionParticipantVolume(participant):
|
||||
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
|
||||
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
||||
|
@ -389,6 +389,20 @@ extension ChatListFilter {
|
||||
case .allChats:
|
||||
return nil
|
||||
case let .filter(id, title, emoticon, data):
|
||||
if data.isShared {
|
||||
var flags: Int32 = 0
|
||||
if emoticon != nil {
|
||||
flags |= 1 << 25
|
||||
}
|
||||
return .dialogFilterCommunity(flags: flags, id: id, title: title, emoticon: emoticon, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||
if data.includePeers.pinnedPeers.contains(peerId) {
|
||||
return nil
|
||||
}
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
})
|
||||
} else {
|
||||
var flags: Int32 = 0
|
||||
if data.excludeMuted {
|
||||
flags |= 1 << 11
|
||||
@ -413,6 +427,7 @@ extension ChatListFilter {
|
||||
}, excludePeers: data.excludePeers.compactMap { peerId -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -873,14 +888,21 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
|
||||
struct ChatListFiltersState: Codable, Equatable {
|
||||
struct ChatListFilterUpdates: Codable, Equatable {
|
||||
struct MemberCount: Codable, Equatable {
|
||||
var id: PeerId
|
||||
var count: Int32
|
||||
}
|
||||
|
||||
var folderId: Int32
|
||||
var timestamp: Int32
|
||||
var peerIds: [PeerId]
|
||||
var memberCounts: [MemberCount]
|
||||
|
||||
init(folderId: Int32, timestamp: Int32, peerIds: [PeerId]) {
|
||||
init(folderId: Int32, timestamp: Int32, peerIds: [PeerId], memberCounts: [MemberCount]) {
|
||||
self.folderId = folderId
|
||||
self.timestamp = timestamp
|
||||
self.peerIds = peerIds
|
||||
self.memberCounts = memberCounts
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
|
||||
var isEnabled = false
|
||||
switch peer {
|
||||
case let .channel(channel):
|
||||
if channel.adminRights != nil && channel.hasPermission(.inviteMembers) {
|
||||
if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
||||
isEnabled = true
|
||||
} else if channel.username != nil {
|
||||
isEnabled = true
|
||||
@ -24,6 +24,7 @@ public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
|
||||
|
||||
public enum ExportChatFolderError {
|
||||
case generic
|
||||
case sharedFolderLimitExceeded(limit: Int32, premiumLimit: Int32)
|
||||
case limitExceeded(limit: Int32, premiumLimit: Int32)
|
||||
}
|
||||
|
||||
@ -75,9 +76,9 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
|
||||
|
||||
if error.errorDescription == "COMMUNITIES_TOO_MUCH" {
|
||||
if isPremium {
|
||||
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||
return .fail(.sharedFolderLimitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||
} else {
|
||||
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||
return .fail(.sharedFolderLimitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||
}
|
||||
} else {
|
||||
if isPremium {
|
||||
@ -236,17 +237,20 @@ public final class ChatFolderLinkContents {
|
||||
public let title: String?
|
||||
public let peers: [EnginePeer]
|
||||
public let alreadyMemberPeerIds: Set<EnginePeer.Id>
|
||||
public let memberCounts: [EnginePeer.Id: Int]
|
||||
|
||||
public init(
|
||||
localFilterId: Int32?,
|
||||
title: String?,
|
||||
peers: [EnginePeer],
|
||||
alreadyMemberPeerIds: Set<EnginePeer.Id>
|
||||
alreadyMemberPeerIds: Set<EnginePeer.Id>,
|
||||
memberCounts: [EnginePeer.Id: Int]
|
||||
) {
|
||||
self.localFilterId = localFilterId
|
||||
self.title = title
|
||||
self.peers = peers
|
||||
self.alreadyMemberPeerIds = alreadyMemberPeerIds
|
||||
self.memberCounts = memberCounts
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,6 +267,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
|
||||
var allPeers: [Peer] = []
|
||||
var peerPresences: [PeerId: Api.User] = [:]
|
||||
var memberCounts: [PeerId: Int] = [:]
|
||||
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
@ -273,6 +278,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
allPeers.append(peer)
|
||||
}
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||
if let participantsCount = participantsCount {
|
||||
memberCounts[chat.peerId] = Int(participantsCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: allPeers, update: { _, updated -> Peer in
|
||||
@ -293,12 +303,13 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
}
|
||||
alreadyMemberPeerIds.removeAll()
|
||||
|
||||
return ChatFolderLinkContents(localFilterId: nil, title: title, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
||||
return ChatFolderLinkContents(localFilterId: nil, title: title, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
|
||||
case let .communityInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
|
||||
let _ = alreadyPeers
|
||||
|
||||
var allPeers: [Peer] = []
|
||||
var peerPresences: [PeerId: Api.User] = [:]
|
||||
var memberCounts: [PeerId: Int] = [:]
|
||||
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
@ -309,6 +320,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
allPeers.append(peer)
|
||||
}
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||
if let participantsCount = participantsCount {
|
||||
memberCounts[chat.peerId] = Int(participantsCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: allPeers, update: { _, updated -> Peer in
|
||||
@ -355,7 +371,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
}
|
||||
}
|
||||
|
||||
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
||||
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
|
||||
}
|
||||
}
|
||||
|> castError(CheckChatFolderLinkError.self)
|
||||
@ -369,12 +385,31 @@ public enum JoinChatFolderLinkError {
|
||||
case tooManyChannels(limit: Int32, premiumLimit: Int32)
|
||||
}
|
||||
|
||||
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
||||
return account.postbox.transaction { transaction -> [Api.InputPeer] in
|
||||
return peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
|
||||
public final class JoinChatFolderResult {
|
||||
public let folderId: Int32
|
||||
public let title: String
|
||||
public let newChatCount: Int
|
||||
|
||||
public init(folderId: Int32, title: String, newChatCount: Int) {
|
||||
self.folderId = folderId
|
||||
self.title = title
|
||||
self.newChatCount = newChatCount
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> {
|
||||
return account.postbox.transaction { transaction -> ([Api.InputPeer], Int) in
|
||||
var newChatCount = 0
|
||||
for peerId in peerIds {
|
||||
if transaction.getPeerChatListIndex(peerId) != nil {
|
||||
newChatCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
return (peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer), newChatCount)
|
||||
}
|
||||
|> castError(JoinChatFolderLinkError.self)
|
||||
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
|
||||
|> mapToSignal { inputPeers, newChatCount -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> in
|
||||
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|
||||
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||
if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
|
||||
@ -426,10 +461,36 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
|
||||
|> mapToSignal { result -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
|
||||
return .complete()
|
||||
var folderResult: JoinChatFolderResult?
|
||||
for update in result.allUpdates {
|
||||
if case let .updateDialogFilter(_, id, data) = update {
|
||||
if let data = data, case let .filter(_, title, _, _) = ChatListFilter(apiFilter: data) {
|
||||
folderResult = JoinChatFolderResult(folderId: id, title: title, newChatCount: newChatCount)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let folderResult = folderResult {
|
||||
return _internal_updatedChatListFilters(postbox: account.postbox)
|
||||
|> castError(JoinChatFolderLinkError.self)
|
||||
|> filter { filters -> Bool in
|
||||
if filters.contains(where: { $0.id == folderResult.folderId }) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
|> map { _ -> JoinChatFolderResult in
|
||||
return folderResult
|
||||
}
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -438,23 +499,26 @@ public final class ChatFolderUpdates: Equatable {
|
||||
fileprivate let folderId: Int32
|
||||
fileprivate let title: String
|
||||
fileprivate let missingPeers: [EnginePeer]
|
||||
fileprivate let memberCounts: [EnginePeer.Id: Int]
|
||||
|
||||
public var availableChatsToJoin: Int {
|
||||
return self.missingPeers.count
|
||||
}
|
||||
|
||||
public var chatFolderLinkContents: ChatFolderLinkContents {
|
||||
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set())
|
||||
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set(), memberCounts: self.memberCounts)
|
||||
}
|
||||
|
||||
fileprivate init(
|
||||
folderId: Int32,
|
||||
title: String,
|
||||
missingPeers: [EnginePeer]
|
||||
missingPeers: [EnginePeer],
|
||||
memberCounts: [EnginePeer.Id: Int]
|
||||
) {
|
||||
self.folderId = folderId
|
||||
self.title = title
|
||||
self.missingPeers = missingPeers
|
||||
self.memberCounts = memberCounts
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
|
||||
@ -512,7 +576,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
||||
var state = state
|
||||
|
||||
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: []))
|
||||
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: [], memberCounts: []))
|
||||
|
||||
return state
|
||||
})
|
||||
@ -524,6 +588,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: Api.User] = [:]
|
||||
var memberCounts: [ChatListFiltersState.ChatListFilterUpdates.MemberCount] = []
|
||||
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
@ -534,6 +599,11 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(peer)
|
||||
}
|
||||
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _) = chat {
|
||||
if let participantsCount = participantsCount {
|
||||
memberCounts.append(ChatListFiltersState.ChatListFilterUpdates.MemberCount(id: chat.peerId, count: participantsCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
@ -545,7 +615,7 @@ func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> S
|
||||
var state = state
|
||||
|
||||
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId)))
|
||||
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId), memberCounts: memberCounts))
|
||||
|
||||
return state
|
||||
})
|
||||
@ -560,6 +630,7 @@ func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) ->
|
||||
struct InternalData: Equatable {
|
||||
var title: String
|
||||
var peerIds: [EnginePeer.Id]
|
||||
var memberCounts: [EnginePeer.Id: Int]
|
||||
}
|
||||
|
||||
return _internal_updatedChatListFiltersState(postbox: account.postbox)
|
||||
@ -574,7 +645,11 @@ func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) ->
|
||||
return nil
|
||||
}
|
||||
let filteredPeerIds: [PeerId] = update.peerIds.filter { !data.includePeers.peers.contains($0) }
|
||||
return InternalData(title: title, peerIds: filteredPeerIds)
|
||||
var memberCounts: [PeerId: Int] = [:]
|
||||
for item in update.memberCounts {
|
||||
memberCounts[item.id] = Int(item.count)
|
||||
}
|
||||
return InternalData(title: title, peerIds: filteredPeerIds, memberCounts: memberCounts)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { internalData -> Signal<ChatFolderUpdates?, NoError> in
|
||||
@ -591,7 +666,7 @@ func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) ->
|
||||
peers.append(EnginePeer(peer))
|
||||
}
|
||||
}
|
||||
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers)
|
||||
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers, memberCounts: internalData.memberCounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -685,3 +760,13 @@ func _internal_leaveChatFolder(account: Account, folderId: Int32, removePeerIds:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_requestLeaveChatFolderSuggestions(account: Account, folderId: Int32) -> Signal<[EnginePeer.Id], NoError> {
|
||||
return account.network.request(Api.functions.communities.getLeaveCommunitySuggestions(community: .inputCommunityDialogFilter(filterId: folderId)))
|
||||
|> map { result -> [EnginePeer.Id] in
|
||||
return result.map(\.peerId)
|
||||
}
|
||||
|> `catch` { _ -> Signal<[EnginePeer.Id], NoError> in
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
|
@ -664,6 +664,7 @@ public struct PeerInvitationImportersState: Equatable {
|
||||
public var date: Int32
|
||||
public var about: String?
|
||||
public var approvedBy: PeerId?
|
||||
public var joinedViaFolderLink: Bool
|
||||
}
|
||||
public var importers: [Importer]
|
||||
public var isLoadingMore: Bool
|
||||
@ -708,6 +709,7 @@ final class CachedPeerInvitationImporters: Codable {
|
||||
let peerIds: [PeerId]
|
||||
let dates: [PeerId: Int32]
|
||||
let abouts: [PeerId: String]
|
||||
let joinedViaFolderLink: [PeerId: Bool]
|
||||
let count: Int32
|
||||
|
||||
static func key(peerId: PeerId, link: String, requested: Bool) -> ValueBoxKey {
|
||||
@ -727,13 +729,17 @@ final class CachedPeerInvitationImporters: Codable {
|
||||
$0[$1.peer.peerId] = about
|
||||
}
|
||||
}
|
||||
self.joinedViaFolderLink = importers.reduce(into: [PeerId: Bool]()) {
|
||||
$0[$1.peer.peerId] = $1.joinedViaFolderLink
|
||||
}
|
||||
self.count = count
|
||||
}
|
||||
|
||||
init(peerIds: [PeerId], dates: [PeerId: Int32], abouts: [PeerId: String], count: Int32) {
|
||||
init(peerIds: [PeerId], dates: [PeerId: Int32], abouts: [PeerId: String], joinedViaFolderLink: [PeerId: Bool], count: Int32) {
|
||||
self.peerIds = peerIds
|
||||
self.dates = dates
|
||||
self.abouts = abouts
|
||||
self.joinedViaFolderLink = joinedViaFolderLink
|
||||
self.count = count
|
||||
}
|
||||
|
||||
@ -752,6 +758,16 @@ final class CachedPeerInvitationImporters: Codable {
|
||||
}
|
||||
self.dates = dates
|
||||
|
||||
var joinedViaFolderLink: [PeerId: Bool] = [:]
|
||||
let joinedViaFolderLinkArray = try container.decode([Int64].self, forKey: "joinedViaFolderLink")
|
||||
for index in stride(from: 0, to: joinedViaFolderLinkArray.endIndex, by: 2) {
|
||||
let userId = datesArray[index]
|
||||
let value = datesArray[index + 1]
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
joinedViaFolderLink[peerId] = value != 0
|
||||
}
|
||||
self.joinedViaFolderLink = joinedViaFolderLink
|
||||
|
||||
var abouts: [PeerId: String] = [:]
|
||||
let aboutsArray = try container.decodeIfPresent([DictionaryPair].self, forKey: "abouts")
|
||||
if let aboutsArray = aboutsArray {
|
||||
@ -777,6 +793,13 @@ final class CachedPeerInvitationImporters: Codable {
|
||||
}
|
||||
try container.encode(dates, forKey: "dates")
|
||||
|
||||
var joinedViaFolderLink: [Int64] = []
|
||||
for (peerId, value) in self.joinedViaFolderLink {
|
||||
joinedViaFolderLink.append(peerId.id._internalGetInt64Value())
|
||||
joinedViaFolderLink.append(Int64(value ? 1 : 0))
|
||||
}
|
||||
try container.encode(joinedViaFolderLink, forKey: "joinedViaFolderLink")
|
||||
|
||||
var abouts: [DictionaryPair] = []
|
||||
for (peerId, about) in self.abouts {
|
||||
abouts.append(DictionaryPair(peerId.id._internalGetInt64Value(), value: about))
|
||||
@ -847,7 +870,7 @@ private final class PeerInvitationImportersContextImpl {
|
||||
var result: [PeerInvitationImportersState.Importer] = []
|
||||
for peerId in cachedResult.peerIds {
|
||||
if let peer = transaction.getPeer(peerId), let date = cachedResult.dates[peerId] {
|
||||
result.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: cachedResult.abouts[peerId]))
|
||||
result.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: cachedResult.abouts[peerId], joinedViaFolderLink: cachedResult.joinedViaFolderLink[peerId] ?? false))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -958,15 +981,17 @@ private final class PeerInvitationImportersContextImpl {
|
||||
let date: Int32
|
||||
let about: String?
|
||||
let approvedBy: PeerId?
|
||||
let joinedViaFolderLink: Bool
|
||||
switch importer {
|
||||
case let .chatInviteImporter(_, userId, dateValue, aboutValue, approvedByValue):
|
||||
case let .chatInviteImporter(flags, userId, dateValue, aboutValue, approvedByValue):
|
||||
joinedViaFolderLink = (flags & (1 << 3)) != 0
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
date = dateValue
|
||||
about = aboutValue
|
||||
approvedBy = approvedByValue.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }
|
||||
}
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
resultImporters.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: about, approvedBy: approvedBy))
|
||||
resultImporters.append(PeerInvitationImportersState.Importer(peer: RenderedPeer(peer: peer), date: date, about: about, approvedBy: approvedBy, joinedViaFolderLink: joinedViaFolderLink))
|
||||
}
|
||||
}
|
||||
if populateCache && query == nil {
|
||||
|
@ -1054,7 +1054,7 @@ public extension TelegramEngine {
|
||||
return _internal_checkChatFolderLink(account: self.account, slug: slug)
|
||||
}
|
||||
|
||||
public func joinChatFolderLink(slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
||||
public func joinChatFolderLink(slug: String, peerIds: [EnginePeer.Id]) -> Signal<JoinChatFolderResult, JoinChatFolderLinkError> {
|
||||
return _internal_joinChatFolderLink(account: self.account, slug: slug, peerIds: peerIds)
|
||||
}
|
||||
|
||||
@ -1085,6 +1085,10 @@ public extension TelegramEngine {
|
||||
public func leaveChatFolder(folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
||||
return _internal_leaveChatFolder(account: self.account, folderId: folderId, removePeerIds: removePeerIds)
|
||||
}
|
||||
|
||||
public func requestLeaveChatFolderSuggestions(folderId: Int32) -> Signal<[EnginePeer.Id], NoError> {
|
||||
return _internal_requestLeaveChatFolderSuggestions(account: self.account, folderId: folderId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,12 +55,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
var containerInset: CGFloat
|
||||
var bottomInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
var contentHeight: CGFloat
|
||||
|
||||
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
|
||||
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat, contentHeight: CGFloat) {
|
||||
self.containerSize = containerSize
|
||||
self.containerInset = containerInset
|
||||
self.bottomInset = bottomInset
|
||||
self.topInset = topInset
|
||||
self.contentHeight = contentHeight
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +84,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
private let scrollView: ScrollView
|
||||
private let scrollContentClippingView: SparseContainerView
|
||||
private let scrollContentView: UIView
|
||||
private let bottomBackgroundLayer: SimpleLayer
|
||||
private let bottomSeparatorLayer: SimpleLayer
|
||||
|
||||
private let topIcon = ComponentView<Empty>()
|
||||
|
||||
@ -134,6 +138,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
self.itemContainerView.clipsToBounds = true
|
||||
self.itemContainerView.layer.cornerRadius = 10.0
|
||||
|
||||
self.bottomBackgroundLayer = SimpleLayer()
|
||||
self.bottomSeparatorLayer = SimpleLayer()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
@ -165,6 +172,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
|
||||
self.scrollContentView.addSubview(self.itemContainerView)
|
||||
|
||||
self.layer.addSublayer(self.bottomBackgroundLayer)
|
||||
self.layer.addSublayer(self.bottomSeparatorLayer)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
|
||||
@ -231,6 +241,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
topOffset = max(0.0, topOffset)
|
||||
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||
|
||||
let bottomDistance = itemLayout.contentHeight - self.scrollView.bounds.maxY
|
||||
let bottomAlphaDistance: CGFloat = 30.0
|
||||
var bottomAlpha: CGFloat = bottomDistance / bottomAlphaDistance
|
||||
bottomAlpha = max(0.0, min(1.0, bottomAlpha))
|
||||
|
||||
let bottomOverlayAlpha: CGFloat = bottomAlpha
|
||||
transition.setAlpha(layer: self.bottomBackgroundLayer, alpha: bottomOverlayAlpha)
|
||||
transition.setAlpha(layer: self.bottomSeparatorLayer, alpha: bottomOverlayAlpha)
|
||||
|
||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||
|
||||
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
||||
@ -244,10 +263,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
|
||||
func animateIn() {
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
let animateOffset: CGFloat = self.backgroundLayer.frame.minY
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.bottomBackgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.bottomSeparatorLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
@ -265,6 +286,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
completion()
|
||||
})
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.bottomBackgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.bottomSeparatorLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
@ -287,8 +310,16 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
if self.component?.linkContents == nil, let linkContents = component.linkContents {
|
||||
for peer in linkContents.peers {
|
||||
self.selectedItems.insert(peer.id)
|
||||
if case let .remove(_, defaultSelectedPeerIds) = component.subject {
|
||||
for peer in linkContents.peers {
|
||||
if defaultSelectedPeerIds.contains(peer.id) {
|
||||
self.selectedItems.insert(peer.id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for peer in linkContents.peers {
|
||||
self.selectedItems.insert(peer.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,6 +331,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
|
||||
self.itemContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor
|
||||
self.bottomBackgroundLayer.backgroundColor = environment.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor
|
||||
self.bottomSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
@ -477,6 +510,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
self.items[id] = item
|
||||
}
|
||||
|
||||
var subtitle: String?
|
||||
if linkContents.alreadyMemberPeerIds.contains(peer.id) {
|
||||
subtitle = "You are already a member"
|
||||
} else if let memberCount = linkContents.memberCounts[peer.id] {
|
||||
subtitle = "\(memberCount) participants"
|
||||
}
|
||||
|
||||
let itemSize = item.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(PeerListItemComponent(
|
||||
@ -486,7 +526,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
sideInset: 0.0,
|
||||
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
peer: peer,
|
||||
subtitle: nil,
|
||||
subtitle: subtitle,
|
||||
selectionState: .editing(isSelected: self.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)),
|
||||
hasNext: i != linkContents.peers.count - 1,
|
||||
action: { [weak self] peer in
|
||||
@ -639,7 +679,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
self.selectedItems.insert(peerId)
|
||||
}
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .easeInOut)))
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
@ -712,7 +752,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .remove(folderId) = component.subject {
|
||||
if case let .remove(folderId, _) = component.subject {
|
||||
self.inProgress = true
|
||||
self.state?.updated(transition: .immediate)
|
||||
|
||||
@ -729,21 +769,72 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
controller.dismiss()
|
||||
} else if let _ = component.linkContents {
|
||||
if self.joinDisposable == nil, !self.selectedItems.isEmpty {
|
||||
let joinSignal: Signal<Never, JoinChatFolderLinkError>
|
||||
let joinSignal: Signal<JoinChatFolderResult?, JoinChatFolderLinkError>
|
||||
switch component.subject {
|
||||
case .remove:
|
||||
return
|
||||
case let .slug(slug):
|
||||
joinSignal = component.context.engine.peers.joinChatFolderLink(slug: slug, peerIds: Array(self.selectedItems))
|
||||
|> map(Optional.init)
|
||||
case let .updates(updates):
|
||||
var result: JoinChatFolderResult?
|
||||
if let localFilterId = updates.chatFolderLinkContents.localFilterId, let title = updates.chatFolderLinkContents.title {
|
||||
result = JoinChatFolderResult(folderId: localFilterId, title: title, newChatCount: self.selectedItems.count)
|
||||
}
|
||||
joinSignal = component.context.engine.peers.joinAvailableChatsInFolder(updates: updates, peerIds: Array(self.selectedItems))
|
||||
|> map { _ -> JoinChatFolderResult? in
|
||||
}
|
||||
|> then(Signal<JoinChatFolderResult?, JoinChatFolderLinkError>.single(result))
|
||||
}
|
||||
|
||||
self.inProgress = true
|
||||
self.state?.updated(transition: .immediate)
|
||||
|
||||
self.joinDisposable = (joinSignal
|
||||
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
|
||||
if let result, let navigationController = controller.navigationController as? NavigationController {
|
||||
var chatListController: ChatListController?
|
||||
for viewController in navigationController.viewControllers {
|
||||
if let rootController = viewController as? TabBarController {
|
||||
for c in rootController.controllers {
|
||||
if let c = c as? ChatListController {
|
||||
chatListController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let c = viewController as? ChatListController {
|
||||
chatListController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let chatListController {
|
||||
navigationController.popToRoot(animated: true)
|
||||
let context = component.context
|
||||
chatListController.navigateToFolder(folderId: result.folderId, completion: { [weak context, weak chatListController] in
|
||||
guard let context, let chatListController else {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
if case .updates = component.subject {
|
||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: "Folder \(result.title) Updated", text: "You have joined \(result.newChatCount) new chats", timeout: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||
} else if result.newChatCount != 0 {
|
||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: "Folder \(result.title) Added", text: "You also joined \(result.newChatCount) chats", timeout: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||
} else {
|
||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Folder \(result.title) Added", timeout: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
controller.dismiss()
|
||||
}, error: { [weak self] error in
|
||||
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
@ -764,11 +855,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
controller.push(limitController)
|
||||
controller.dismiss()
|
||||
}
|
||||
}, completed: { [weak self] in
|
||||
guard let self, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
})
|
||||
} else {
|
||||
controller.dismiss()
|
||||
@ -788,6 +874,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||
}
|
||||
|
||||
transition.setFrame(layer: self.bottomBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0), size: CGSize(width: availableSize.width, height: bottomPanelHeight)))
|
||||
transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
if let controller = environment.controller() {
|
||||
let subLayout = ContainerViewLayout(
|
||||
size: availableSize, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset),
|
||||
@ -809,16 +898,16 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
|
||||
let scrollContentHeight = max(topInset + contentHeight, availableSize.height - containerInset)
|
||||
|
||||
self.scrollContentClippingView.layer.cornerRadius = 10.0
|
||||
//self.scrollContentClippingView.layer.cornerRadius = 10.0
|
||||
|
||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset)
|
||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset, contentHeight: scrollContentHeight)
|
||||
|
||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: actionButtonFrame.minY - 24.0 - (containerInset + 56.0)))
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: actionButtonFrame.minY - 8.0 - (containerInset + 56.0)))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||
|
||||
@ -851,7 +940,7 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
|
||||
public enum Subject: Equatable {
|
||||
case slug(String)
|
||||
case updates(ChatFolderUpdates)
|
||||
case remove(folderId: Int32)
|
||||
case remove(folderId: Int32, defaultSelectedPeerIds: [EnginePeer.Id])
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
@ -868,6 +957,7 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
|
||||
self.navigationPresentation = .flatModal
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
self.lockOrientation = true
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -182,7 +182,7 @@ final class PeerListItemComponent: Component {
|
||||
if themeUpdated {
|
||||
var theme = CheckNodeTheme(theme: component.theme, style: .plain)
|
||||
if isTinted {
|
||||
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.35)
|
||||
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5)
|
||||
}
|
||||
checkLayer.theme = theme
|
||||
}
|
||||
@ -190,7 +190,7 @@ final class PeerListItemComponent: Component {
|
||||
} else {
|
||||
var theme = CheckNodeTheme(theme: component.theme, style: .plain)
|
||||
if isTinted {
|
||||
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.35)
|
||||
theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5)
|
||||
}
|
||||
checkLayer = CheckLayer(theme: theme)
|
||||
self.checkLayer = checkLayer
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "link_18.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
101
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/link_18.pdf
vendored
Normal file
101
submodules/TelegramUI/Images.xcassets/Chat List/SharedFolderListIcon.imageset/link_18.pdf
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.304932 1.549805 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
7.766377 12.077935 m
|
||||
8.749319 13.060877 10.342982 13.060877 11.325925 12.077935 c
|
||||
12.308867 11.094994 12.308867 9.501329 11.325925 8.518387 c
|
||||
9.825925 7.018387 l
|
||||
8.842983 6.035446 7.249319 6.035446 6.266377 7.018387 c
|
||||
6.135469 7.149295 6.022435 7.290437 5.926912 7.438976 c
|
||||
5.728258 7.747883 5.316799 7.837261 5.007892 7.638608 c
|
||||
4.698985 7.439953 4.609607 7.028494 4.808261 6.719588 c
|
||||
4.954801 6.491719 5.127476 6.276385 5.325925 6.077936 c
|
||||
6.828264 4.575597 9.264038 4.575597 10.766377 6.077936 c
|
||||
12.266377 7.577936 l
|
||||
13.768716 9.080275 13.768716 11.516048 12.266377 13.018387 c
|
||||
10.764037 14.520726 8.328264 14.520726 6.825925 13.018387 c
|
||||
5.325925 11.518387 l
|
||||
5.066226 11.258689 5.066226 10.837634 5.325925 10.577935 c
|
||||
5.585624 10.318236 6.006679 10.318236 6.266377 10.577935 c
|
||||
7.766377 12.077935 l
|
||||
h
|
||||
5.626755 2.818445 m
|
||||
4.643812 1.835504 3.050149 1.835504 2.067207 2.818445 c
|
||||
1.084265 3.801387 1.084265 5.395051 2.067207 6.377992 c
|
||||
3.567207 7.877992 l
|
||||
4.550149 8.860934 6.143812 8.860934 7.126754 7.877992 c
|
||||
7.257662 7.747084 7.370696 7.605942 7.466219 7.457404 c
|
||||
7.664873 7.148497 8.076332 7.059119 8.385240 7.257772 c
|
||||
8.694146 7.456426 8.783525 7.867885 8.584870 8.176792 c
|
||||
8.438332 8.404661 8.265656 8.619995 8.067206 8.818444 c
|
||||
6.564867 10.320784 4.129094 10.320784 2.626755 8.818444 c
|
||||
1.126755 7.318444 l
|
||||
-0.375585 5.816106 -0.375585 3.380332 1.126755 1.877994 c
|
||||
2.629094 0.375654 5.064867 0.375654 6.567206 1.877994 c
|
||||
8.067206 3.377994 l
|
||||
8.326905 3.637691 8.326905 4.058746 8.067206 4.318445 c
|
||||
7.807508 4.578144 7.386453 4.578144 7.126754 4.318445 c
|
||||
5.626755 2.818445 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1707
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 18.000000 18.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001797 00000 n
|
||||
0000001820 00000 n
|
||||
0000001993 00000 n
|
||||
0000002067 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2126
|
||||
%%EOF
|
@ -7330,7 +7330,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self, case let .message(index) = toIndex {
|
||||
if case let .message(messageSubject, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id {
|
||||
if messageId.peerId == index.id.peerId {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
}
|
||||
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
||||
@ -8809,12 +8809,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
bannedMediaInput = true
|
||||
} else if channel.hasBannedPermission(.banSendVoice) != nil {
|
||||
if !isVideo {
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||
return
|
||||
}
|
||||
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
|
||||
if isVideo {
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -8823,12 +8823,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
bannedMediaInput = true
|
||||
} else if group.hasBannedPermission(.banSendVoice) {
|
||||
if !isVideo {
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||
return
|
||||
}
|
||||
} else if group.hasBannedPermission(.banSendInstantVideos) {
|
||||
if isVideo {
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText()))
|
||||
strongSelf.controllerInteraction?.displayUndo(.info(title: nil, text: strongSelf.restrictedSendingContentsText(), timeout: nil))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -10130,7 +10130,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
presentAddMembersImpl(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: peer, selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable)
|
||||
}, presentGigagroupHelp: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
}
|
||||
}, editMessageMedia: { [weak self] messageId, draw in
|
||||
if let strongSelf = self {
|
||||
@ -11204,7 +11204,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||
|
||||
let controller = richTextAlertController(context: strongSelf.context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_SettingsTip), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_SettingsTip, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_LearnMore, action: {
|
||||
|
||||
let context = strongSelf.context
|
||||
@ -12997,7 +12997,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return false
|
||||
}
|
||||
}), case let .app(_, botName, _) = button {
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: botJustInstalled ? strongSelf.presentationData.strings.WebApp_AddToAttachmentSucceeded(botName).string : strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: botJustInstalled ? strongSelf.presentationData.strings.WebApp_AddToAttachmentSucceeded(botName).string : strongSelf.presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError, timeout: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
} else {
|
||||
let _ = (context.engine.messages.getAttachMenuBot(botId: botId)
|
||||
|> deliverOnMainQueue).start(next: { bot in
|
||||
|
@ -815,7 +815,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
let _ = context.engine.messages.rateAudioTranscription(messageId: message.id, id: audioTranscription.id, isGood: value).start()
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let content: UndoOverlayContent = .info(title: nil, text: presentationData.strings.Chat_AudioTranscriptionFeedbackTip)
|
||||
let content: UndoOverlayContent = .info(title: nil, text: presentationData.strings.Chat_AudioTranscriptionFeedbackTip, timeout: nil)
|
||||
controllerInteraction.displayUndo(content)
|
||||
}), false), at: 0)
|
||||
actions.insert(.separator, at: 1)
|
||||
@ -842,9 +842,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
|
||||
let settings = NotificationSoundSettings.extract(from: context.currentAppConfiguration.with({ $0 }))
|
||||
if size > settings.maxSize {
|
||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string))
|
||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string, timeout: nil))
|
||||
} else if Double(duration) > Double(settings.maxDuration) {
|
||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
|
||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string, timeout: nil))
|
||||
} else {
|
||||
let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
|
@ -321,6 +321,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
case "telegram_botapp":
|
||||
title = item.presentationData.strings.Conversation_BotApp
|
||||
actionTitle = item.presentationData.strings.Conversation_OpenBotApp
|
||||
case "telegram_community":
|
||||
actionTitle = item.presentationData.strings.Conversation_OpenChatFolder
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: new, stickerPacks: [new], parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|
||||
return true
|
||||
}
|
||||
case let .editExportedInvitation(_, invite), let .revokeExportedInvitation(invite), let .deleteExportedInvitation(invite), let .participantJoinedViaInvite(invite), let .participantJoinByRequest(invite, _):
|
||||
case let .editExportedInvitation(_, invite), let .revokeExportedInvitation(invite), let .deleteExportedInvitation(invite), let .participantJoinedViaInvite(invite, _), let .participantJoinByRequest(invite, _):
|
||||
if let inviteLink = invite.link, !inviteLink.hasSuffix("...") {
|
||||
if invite.isPermanent {
|
||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
|
@ -1418,7 +1418,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
case let .participantJoinedViaInvite(invite):
|
||||
case let .participantJoinedViaInvite(invite, joinedViaFolderLink):
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
var author: Peer?
|
||||
if let peer = self.entry.peers[self.entry.event.peerId] {
|
||||
@ -1429,7 +1429,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var text: String = ""
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
let rawText: PresentationStrings.FormattedString = self.presentationData.strings.Channel_AdminLog_JoinedViaInviteLink(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link?.replacingOccurrences(of: "https://", with: "") ?? "")
|
||||
let rawText: PresentationStrings.FormattedString
|
||||
if joinedViaFolderLink {
|
||||
rawText = self.presentationData.strings.Channel_AdminLog_JoinedViaFolderInviteLink(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link?.replacingOccurrences(of: "https://", with: "") ?? "")
|
||||
} else {
|
||||
rawText = self.presentationData.strings.Channel_AdminLog_JoinedViaInviteLink(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link?.replacingOccurrences(of: "https://", with: "") ?? "")
|
||||
}
|
||||
|
||||
appendAttributedText(text: rawText, generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
|
@ -593,7 +593,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}
|
||||
case let .startAttach(peerId, payload, choose):
|
||||
let presentError: (String) -> Void = { errorText in
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: errorText), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: errorText, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), nil)
|
||||
}
|
||||
|
@ -6080,7 +6080,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
if let strongSelf = self, let peer = strongSelf.data?.peer {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })
|
||||
controller.keepOnParentDismissal = true
|
||||
strongSelf.controller?.present(controller, in: .window(.root))
|
||||
|
||||
|
@ -13,7 +13,6 @@ swift_library(
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Display:Display",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -3,7 +3,12 @@ import UIKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
|
||||
private extension UIColor {
|
||||
convenience init(rgb: UInt32) {
|
||||
self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
public enum PresentationBuiltinThemeReference: Int32 {
|
||||
case dayClassic = 0
|
||||
|
@ -12,7 +12,7 @@ public enum UndoOverlayContent {
|
||||
case hidArchive(title: String, text: String, undo: Bool)
|
||||
case revealedArchive(title: String, text: String, undo: Bool)
|
||||
case succeed(text: String)
|
||||
case info(title: String?, text: String)
|
||||
case info(title: String?, text: String, timeout: Double?)
|
||||
case emoji(name: String, text: String)
|
||||
case swipeToReply(title: String, text: String)
|
||||
case actionSucceeded(title: String, text: String, cancel: String)
|
||||
|
@ -173,7 +173,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.maximumNumberOfLines = 5
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
case let .info(title, text):
|
||||
case let .info(title, text, timeout):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
@ -193,11 +193,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 10
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
|
||||
if let timeout {
|
||||
self.originalRemainingSeconds = timeout
|
||||
} else {
|
||||
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
|
||||
}
|
||||
|
||||
if text.contains("](") {
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
case let .actionSucceeded(title, text, cancel):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "9.5.4",
|
||||
"bazel": "5.3.1",
|
||||
"bazel": "6.1.1",
|
||||
"xcode": "14.2"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user