mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
b64d4a23fe
40 changed files with 704 additions and 349 deletions
35
.bazelrc
35
.bazelrc
|
|
@ -22,14 +22,36 @@ build --strategy=SwiftCompile=worker
|
|||
|
||||
#common --registry=https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/
|
||||
|
||||
# SourceKit BSP: Swift indexing features
|
||||
common --features=swift.index_while_building
|
||||
common --features=swift.use_global_index_store
|
||||
common --features=swift.use_global_module_cache
|
||||
common --features=oso_prefix_is_pwd
|
||||
# rules_swift flags migration
|
||||
# --swiftcopt and --host_swiftcopt are deprecated
|
||||
common --flag_alias=swiftcopt=@build_bazel_rules_swift//swift:copt
|
||||
common --flag_alias=host_swiftcopt=@build_bazel_rules_swift//swift:exec_copt
|
||||
#common --swiftcopt=-whole-module-optimization
|
||||
#common --host_swiftcopt=-whole-module-optimization
|
||||
|
||||
# SourceKit BSP: Index build config (used for background indexing)
|
||||
common --check_visibility=false
|
||||
|
||||
# All of the following are Debug/Index setup configs inspired by the default rules_xcodeproj template
|
||||
|
||||
common --verbose_failures
|
||||
common --cache_computed_file_digests=500000
|
||||
common --action_cache_store_output_metadata
|
||||
common --experimental_use_cpp_compile_action_args_params_file
|
||||
common --define=apple.experimental.tree_artifact_outputs=1
|
||||
common --features=apple.swizzle_absolute_xcttestsourcelocation
|
||||
common --features=oso_prefix_is_pwd
|
||||
common --features=relative_ast_path
|
||||
common --features=swift.cacheable_swiftmodules
|
||||
common --features=swift.index_while_building
|
||||
common --features=swift.use_global_module_cache
|
||||
common --features=swift.emit_swiftsourceinfo
|
||||
common --nolegacy_important_outputs
|
||||
build --noworker_sandboxing
|
||||
build --spawn_strategy=remote,worker,local
|
||||
|
||||
# Only for BSP builds
|
||||
common:index_build --experimental_convenience_symlinks=ignore
|
||||
common:index_build --bes_backend= --bes_results_url=
|
||||
common:index_build --show_result=0
|
||||
common:index_build --noshow_loading_progress
|
||||
common:index_build --noshow_progress
|
||||
|
|
@ -37,3 +59,4 @@ common:index_build --noannounce_rc
|
|||
common:index_build --noshow_timestamps
|
||||
common:index_build --curses=no
|
||||
common:index_build --color=no
|
||||
|
||||
|
|
|
|||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -23,5 +23,7 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"swift.sourcekit-lsp.serverPath": "${workspaceFolder}/build-input/sourcekit-lsp"
|
||||
"swift.sourcekit-lsp.serverPath": "${workspaceFolder}/build-input/sourcekit-lsp",
|
||||
"sourcekit-bazel-bsp.rulesAppleName": "build_bazel_rules_apple",
|
||||
"cmake.sourceDirectory": "/Users/ali/build/telegram/telegram-ios/submodules/rlottie/rlottie/test"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
load("@sourcekit_bazel_bsp//rules:setup_sourcekit_bsp.bzl", "setup_sourcekit_bsp")
|
||||
|
||||
exports_files(["versions.json"])
|
||||
|
||||
setup_sourcekit_bsp(
|
||||
name = "setup_sourcekit_bsp",
|
||||
bazel_wrapper = "./build-input/bazel-8.4.2-darwin-arm64",
|
||||
|
|
@ -18,17 +20,12 @@ setup_sourcekit_bsp(
|
|||
"submodules/**/*.cpp",
|
||||
],
|
||||
aquery_flags = [
|
||||
"define=telegramVersion=12.5",
|
||||
"define=buildNumber=100000",
|
||||
],
|
||||
index_flags = [
|
||||
"config=index_build",
|
||||
"define=telegramVersion=12.5",
|
||||
"define=buildNumber=100000",
|
||||
],
|
||||
index_build_batch_size = 10,
|
||||
targets = [
|
||||
"//Telegram:Telegram",
|
||||
],
|
||||
merge_lsp_config = False,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -385,7 +385,8 @@ plist_fragment(
|
|||
"""
|
||||
<key>CFBundleVersion</key>
|
||||
<string>{buildNumber}</string>
|
||||
"""
|
||||
""",
|
||||
defaults = {"buildNumber": "1"},
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
|
|
@ -574,14 +575,23 @@ plist_fragment(
|
|||
])
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
genrule(
|
||||
name = "VersionInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
srcs = ["//:versions.json"],
|
||||
outs = ["VersionInfoPlist.plist"],
|
||||
cmd = """
|
||||
version=$$(python3 -c "import json; print(json.load(open('$(location //:versions.json)'))['app'])")
|
||||
cat > $@ <<PLIST
|
||||
<?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>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{telegramVersion}</string>
|
||||
"""
|
||||
<string>$$version</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
""",
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
|
|
|
|||
|
|
@ -43,14 +43,23 @@ plist_fragment(
|
|||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
genrule(
|
||||
name = "VersionInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
srcs = ["//:versions.json"],
|
||||
outs = ["VersionInfoPlist.plist"],
|
||||
cmd = """
|
||||
version=$$(python3 -c "import json; print(json.load(open('$(location //:versions.json)'))['app'])")
|
||||
cat > $@ <<PLIST
|
||||
<?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>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{telegramVersion}</string>
|
||||
"""
|
||||
<string>$$version</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
""",
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ class BazelCommandLine:
|
|||
def get_define_arguments(self):
|
||||
return [
|
||||
'--define=buildNumber={}'.format(self.build_number),
|
||||
'--define=telegramVersion={}'.format(self.build_environment.app_version)
|
||||
]
|
||||
|
||||
def get_project_generation_arguments(self):
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 39cc2529241335d20afcb6700c587d064e7a2870
|
||||
Subproject commit f6aef7db5b649ffc3b7497ecc83a08ad3ab19559
|
||||
|
|
@ -18,6 +18,8 @@ def _plist_fragment(ctx):
|
|||
resolved_values = dict()
|
||||
for key in found_keys:
|
||||
value = ctx.var.get(key, None)
|
||||
if value == None:
|
||||
value = ctx.attr.defaults.get(key, None)
|
||||
if value == None:
|
||||
fail("Expected value for --define={} was not found".format(key))
|
||||
resolved_values[key] = value
|
||||
|
|
@ -38,6 +40,7 @@ plist_fragment = rule(
|
|||
attrs = {
|
||||
"extension": attr.string(mandatory = True),
|
||||
"template": attr.string(mandatory = True),
|
||||
"defaults": attr.string_dict(),
|
||||
},
|
||||
outputs = {
|
||||
"out": "%{name}.%{extension}"
|
||||
|
|
|
|||
|
|
@ -10,12 +10,9 @@ WORKSPACE_ROOT=$(pwd)
|
|||
BAZEL_CMD="./build-input/bazel-8.4.2-darwin-arm64"
|
||||
|
||||
export ADDITIONAL_FLAGS=()
|
||||
TELEGRAM_VERSION=$(python3 -c "import json; print(json.load(open('${WORKSPACE_ROOT}/versions.json'))['app'])")
|
||||
|
||||
ADDITIONAL_FLAGS+=("--keep_going")
|
||||
ADDITIONAL_FLAGS+=("--color=yes")
|
||||
ADDITIONAL_FLAGS+=("--define=telegramVersion=${TELEGRAM_VERSION}")
|
||||
ADDITIONAL_FLAGS+=("--define=buildNumber=100000")
|
||||
|
||||
if [ -n "${BAZEL_EXTRA_BUILD_FLAGS:-}" ]; then
|
||||
ADDITIONAL_FLAGS+=("${BAZEL_EXTRA_BUILD_FLAGS[@]}")
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ public final class PinchSourceContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
|
||||
public var maxPinchScale: CGFloat = 10.0
|
||||
|
||||
private var isActive: Bool = false
|
||||
public private(set) var isActive: Bool = false
|
||||
|
||||
public var activate: ((PinchSourceContainerNode) -> Void)?
|
||||
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
|
@ -223,4 +223,4 @@ public final class PinchSourceContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
}
|
||||
self.contentNode.frame = naturalContentFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public protocol ListView: ASDisplayNode {
|
|||
|
||||
// MARK: - Read-only State Properties
|
||||
var insets: UIEdgeInsets { get }
|
||||
var visibleSize: CGSize { get }
|
||||
var isTracking: Bool { get }
|
||||
var trackingOffset: CGFloat { get }
|
||||
var beganTrackingAtTopOrigin: Bool { get }
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
self.currentThumbnailPanelHeight = thumbnailPanelHeight
|
||||
self.currentFooterContentNode = footerContentNode
|
||||
if let footerContentNode = footerContentNode {
|
||||
footerContentNode.setVisibilityAlpha(self.visibilityAlpha, animated: transition.isAnimated)
|
||||
footerContentNode.setVisibilityAlpha(self.visibilityAlpha, animated: false)
|
||||
footerContentNode.controllerInteraction = self.controllerInteraction
|
||||
footerContentNode.requestLayout = { [weak self] transition in
|
||||
if let strongSelf = self, let (currentLayout, navigationBarHeight, currentThumbnailPanelHeight, isHidden) = strongSelf.currentLayout {
|
||||
|
|
|
|||
|
|
@ -928,6 +928,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var scrubbingFrameDisposable: Disposable?
|
||||
|
||||
private var isPlaying = false
|
||||
private var hasStartedOnce = false
|
||||
private let isPlayingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private var areControlsVisible: Bool = true
|
||||
|
|
@ -1679,10 +1680,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
var isPlaying = false
|
||||
var isPaused = true
|
||||
var seekable = hintSeekable
|
||||
var hasStarted = false
|
||||
var hasStarted = strongSelf.hasStartedOnce
|
||||
var displayProgress = true
|
||||
if let value = value {
|
||||
hasStarted = value.timestamp > 0
|
||||
if value.timestamp > 0 {
|
||||
hasStarted = true
|
||||
}
|
||||
|
||||
if let zoomableContent = strongSelf.zoomableContent, !value.dimensions.width.isZero && !value.dimensions.height.isZero {
|
||||
let videoSize = CGSize(width: value.dimensions.width * 2.0, height: value.dimensions.height * 2.0)
|
||||
|
|
@ -1750,6 +1753,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
strongSelf.hasStartedOnce = hasStarted
|
||||
|
||||
if !disablePlayerControls && strongSelf.isCentral == true && isPlaying {
|
||||
strongSelf.isPlayingPromise.set(true)
|
||||
strongSelf.isPlaying = true
|
||||
|
|
|
|||
|
|
@ -268,7 +268,11 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
|
|||
self.foregroundNode.isLayerBacked = true
|
||||
self.foregroundNode.displayWithoutProcessing = true
|
||||
self.foregroundNode.displaysAsynchronously = false
|
||||
self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: color)
|
||||
if case .round = lineCap {
|
||||
self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: color)
|
||||
} else {
|
||||
self.foregroundNode.backgroundColor = color
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -278,6 +282,9 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
|
|||
|
||||
func updateStatus(_ ranges: RangeSet<Int64>, _ size: Int64) {
|
||||
self.ranges = (ranges, size)
|
||||
/*#if DEBUG
|
||||
self.ranges = (RangeSet(0 ..< size / 2), size)
|
||||
#endif*/
|
||||
if !self.bounds.width.isZero {
|
||||
self.updateLayout(size: self.bounds.size, transition: .animated(duration: 0.15, curve: .easeInOut))
|
||||
}
|
||||
|
|
@ -424,7 +431,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
backgroundNode.displaysAsynchronously = false
|
||||
backgroundNode.displayWithoutProcessing = true
|
||||
|
||||
let bufferingNode = MediaPlayerScrubbingBufferingNode(color: bufferingColor, lineCap: lineCap, lineHeight: lineHeight)
|
||||
let bufferingNode = MediaPlayerScrubbingBufferingNode(color: bufferingColor, lineCap: scrubberHandle == .none ? .square : lineCap, lineHeight: lineHeight)
|
||||
|
||||
let foregroundContentNode = ASImageNode()
|
||||
foregroundContentNode.isLayerBacked = true
|
||||
|
|
@ -434,7 +441,11 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
switch lineCap {
|
||||
case .round:
|
||||
backgroundNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: backgroundColor)
|
||||
foregroundContentNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: foregroundColor)
|
||||
if case .none = scrubberHandle {
|
||||
foregroundContentNode.backgroundColor = foregroundColor
|
||||
} else {
|
||||
foregroundContentNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: foregroundColor)
|
||||
}
|
||||
case .square:
|
||||
backgroundNode.backgroundColor = backgroundColor
|
||||
foregroundContentNode.backgroundColor = foregroundColor
|
||||
|
|
@ -443,13 +454,16 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
let foregroundNode = MediaPlayerScrubbingForegroundNode()
|
||||
foregroundNode.isLayerBacked = true
|
||||
foregroundNode.clipsToBounds = true
|
||||
if case .round = lineCap {
|
||||
foregroundNode.layer.cornerRadius = lineHeight * 0.5
|
||||
}
|
||||
|
||||
var handleNodeImpl: ASImageNode?
|
||||
var highlightedHandleNodeImpl: ASImageNode?
|
||||
var handleNodeContainerImpl: MediaPlayerScrubbingNodeButton?
|
||||
|
||||
switch scrubberHandle {
|
||||
case .none:
|
||||
case .none:
|
||||
let handleNode = ASImageNode()
|
||||
handleNode.isLayerBacked = true
|
||||
handleNodeImpl = handleNode
|
||||
|
|
@ -750,7 +764,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
switch self.contentNodes {
|
||||
case let .standard(node):
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate
|
||||
node.foregroundContentNode.backgroundColor = collapsed ? .white : nil
|
||||
node.foregroundContentNode.backgroundColor = collapsed ? .white : (node.handle == .none ? .white : nil)
|
||||
|
||||
if let handleNode = node.handleNodeContainer {
|
||||
transition.updateAlpha(node: node.foregroundContentNode, alpha: collapsed ? 0.45 : 1.0)
|
||||
|
|
|
|||
|
|
@ -198,87 +198,6 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu
|
|||
return result
|
||||
}
|
||||
|
||||
/*private func mapFiles(paths: [String], inodes: inout [InodeInfo], removeSize: UInt64, mainStoragePath: String, storageBox: StorageBox) {
|
||||
var removedSize: UInt64 = 0
|
||||
inodes.sort(by: { lhs, rhs in
|
||||
return lhs.timestamp < rhs.timestamp
|
||||
})
|
||||
|
||||
var inodesToDelete = Set<__darwin_ino64_t>()
|
||||
|
||||
for inode in inodes {
|
||||
inodesToDelete.insert(inode.inode)
|
||||
removedSize += UInt64(inode.size)
|
||||
if removedSize >= removeSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if inodesToDelete.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let pathBuffer = malloc(2048).assumingMemoryBound(to: Int8.self)
|
||||
defer {
|
||||
free(pathBuffer)
|
||||
}
|
||||
|
||||
var unlinkedResourceIds: [Data] = []
|
||||
|
||||
for path in paths {
|
||||
let isMainPath = path == mainStoragePath
|
||||
if let dp = opendir(path) {
|
||||
while true {
|
||||
guard let dirp = readdir(dp) else {
|
||||
break
|
||||
}
|
||||
|
||||
if strncmp(&dirp.pointee.d_name.0, ".", 1024) == 0 {
|
||||
continue
|
||||
}
|
||||
if strncmp(&dirp.pointee.d_name.0, "..", 1024) == 0 {
|
||||
continue
|
||||
}
|
||||
strncpy(pathBuffer, path, 1024)
|
||||
strncat(pathBuffer, "/", 1024)
|
||||
strncat(pathBuffer, &dirp.pointee.d_name.0, 1024)
|
||||
|
||||
//puts(pathBuffer)
|
||||
//puts("\n")
|
||||
|
||||
var value = stat()
|
||||
if stat(pathBuffer, &value) == 0 {
|
||||
if (((value.st_mode) & S_IFMT) == S_IFDIR) {
|
||||
if let subPath = String(data: Data(bytes: pathBuffer, count: strnlen(pathBuffer, 1024)), encoding: .utf8) {
|
||||
mapFiles(paths: <#T##[String]#>, inodes: &<#T##[InodeInfo]#>, removeSize: remov, mainStoragePath: mainStoragePath, storageBox: storageBox)
|
||||
}
|
||||
} else {
|
||||
if inodesToDelete.contains(value.st_ino) {
|
||||
if isMainPath {
|
||||
let nameLength = strnlen(&dirp.pointee.d_name.0, 1024)
|
||||
let nameData = Data(bytesNoCopy: &dirp.pointee.d_name.0, count: Int(nameLength), deallocator: .none)
|
||||
withExtendedLifetime(nameData, {
|
||||
if let fileName = String(data: nameData, encoding: .utf8) {
|
||||
if let idData = MediaBox.idForFileName(name: fileName).data(using: .utf8) {
|
||||
unlinkedResourceIds.append(idData)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
unlink(pathBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dp)
|
||||
}
|
||||
}
|
||||
|
||||
if !unlinkedResourceIds.isEmpty {
|
||||
storageBox.remove(ids: unlinkedResourceIds)
|
||||
}
|
||||
}*/
|
||||
|
||||
private func statForDirectory(path: String) -> Int64 {
|
||||
if #available(macOS 10.13, *) {
|
||||
var s = darwin_dirstat()
|
||||
|
|
@ -482,10 +401,10 @@ private final class TimeBasedCleanupImpl {
|
|||
}
|
||||
//let fileName = filePath.lastPathComponent
|
||||
|
||||
if remainingSize >= bytesLimit {
|
||||
if remainingSize <= Int64(bytesLimit) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,6 +213,10 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||
private var referenceLocation: ReferenceLocation?
|
||||
private var loadingEffectView: VideoChatVideoLoadingEffectView?
|
||||
|
||||
public var isPinchToZoomActive: Bool {
|
||||
return self.pinchContainerNode.isActive
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundGradientView = UIImageView()
|
||||
self.pinchContainerNode = PinchSourceContainerNode()
|
||||
|
|
|
|||
|
|
@ -693,7 +693,7 @@ final class VideoChatParticipantsComponent: Component {
|
|||
|
||||
private var appliedGridIsEmpty: Bool = true
|
||||
|
||||
private var isPinchToZoomActive: Bool = false
|
||||
private(set) var isPinchToZoomActive: Bool = false
|
||||
|
||||
private var stopRequestingNonCentralVideo: Bool = false
|
||||
private var stopRequestingNonCentralVideoTimer: Foundation.Timer?
|
||||
|
|
|
|||
|
|
@ -337,6 +337,13 @@ final class VideoChatScreenComponent: Component {
|
|||
|
||||
var maxVideoQuality: Int = Int.max
|
||||
|
||||
private var isPinchToZoomActive: Bool {
|
||||
if let participantsView = self.participants.view as? VideoChatParticipantsComponent.View {
|
||||
return participantsView.isPinchToZoomActive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.containerView = UIView()
|
||||
self.containerView.clipsToBounds = true
|
||||
|
|
@ -1609,7 +1616,7 @@ final class VideoChatScreenComponent: Component {
|
|||
}
|
||||
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members {
|
||||
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in
|
||||
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, !self.isPinchToZoomActive, let participant = members.participants.first(where: { participant in
|
||||
if let callState = self.callState, participant.id == .peer(callState.myPeerId) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -1966,7 +1973,7 @@ final class VideoChatScreenComponent: Component {
|
|||
}
|
||||
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState {
|
||||
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in
|
||||
if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, !self.isPinchToZoomActive, let participant = members.participants.first(where: { participant in
|
||||
if let callState = self.callState, participant.id == .peer(callState.myPeerId) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,8 +316,8 @@ extension VideoChatScreenComponent.View {
|
|||
]
|
||||
|
||||
let videoQualityTitle = qualityList.first(where: { $0.0 == self.maxVideoQuality })?.1 ?? ""
|
||||
items.append(.action(ContextMenuActionItem(text: environment.strings.VideoChat_IncomingVideoQuality_Title, textColor: .primary, textLayout: .secondLineWithValue(videoQualityTitle), icon: { _ in
|
||||
return nil
|
||||
items.append(.action(ContextMenuActionItem(text: environment.strings.VideoChat_IncomingVideoQuality_Title, textColor: .primary, textLayout: .secondLineWithValue(videoQualityTitle), icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
guard let self else {
|
||||
c?.dismiss(completion: nil)
|
||||
|
|
|
|||
|
|
@ -121,11 +121,10 @@ private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextM
|
|||
let standardIconWidth: CGFloat = 32.0
|
||||
var rightTextInset: CGFloat = sideInset
|
||||
if !iconSize.width.isZero {
|
||||
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset
|
||||
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset
|
||||
}
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
|
||||
|
||||
|
||||
let verticalSpacing: CGFloat = 2.0
|
||||
let combinedTextHeight = textSize.height + verticalSpacing
|
||||
|
|
|
|||
|
|
@ -96,23 +96,29 @@ func _internal_reinstateNoPaidMessagesException(account: Account, scopePeerId: P
|
|||
return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser))
|
||||
}
|
||||
|> mapToSignal { scopeInputPeer, inputUser -> Signal<Never, NoError> in
|
||||
var scopeInputPeer = scopeInputPeer
|
||||
if scopePeerId != account.peerId {
|
||||
if scopeInputPeer == nil {
|
||||
return .never()
|
||||
}
|
||||
} else {
|
||||
return .never()
|
||||
scopeInputPeer = nil
|
||||
}
|
||||
guard let inputUser else {
|
||||
return .never()
|
||||
}
|
||||
var flags: Int32 = 0
|
||||
flags |= (1 << 2)
|
||||
flags |= (1 << 1)
|
||||
if scopeInputPeer != nil {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: scopeInputPeer, userId: inputUser))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
} |> mapToSignal { _ in
|
||||
if scopePeerId == account.peerId {
|
||||
account.viewTracker.forceUpdateCachedPeerData(peerId: peerId)
|
||||
}
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if scopePeerId != account.peerId {
|
||||
guard var data = transaction.getMessageHistoryThreadInfo(peerId: scopePeerId, threadId: peerId.toInt64())?.data.get(MessageHistoryThreadData.self) else {
|
||||
|
|
|
|||
|
|
@ -325,6 +325,31 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU
|
|||
}
|
||||
|
||||
func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal<Float, NoError> {
|
||||
#if DEBUG
|
||||
if "".isEmpty {
|
||||
return Signal { subscriber in
|
||||
var value: Float = 0.0
|
||||
subscriber.putNext(value)
|
||||
let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: true, completion: {
|
||||
if value != 1.0 {
|
||||
value += 0.1
|
||||
if value >= 1.0 {
|
||||
value = 1.0
|
||||
subscriber.putNext(value)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putNext(value)
|
||||
}
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
timer.start()
|
||||
return ActionDisposable {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
let mediaBox = account.postbox.mediaBox
|
||||
return Signal { subscriber in
|
||||
var includeResourceIds = Set<MediaResourceId>()
|
||||
|
|
|
|||
|
|
@ -134,115 +134,119 @@ final class AutomaticCacheEvictionContext {
|
|||
}
|
||||
}
|
||||
|
||||
if timeout == Int32.max {
|
||||
return .complete()
|
||||
}
|
||||
let storyTimeout: Int32 = settings.categoryStorageTimeout[.stories] ?? (2 * 24 * 60 * 60)
|
||||
|
||||
let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout
|
||||
//let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
|
||||
let allSignal = mediaBox.storageBox.all(peerId: peerId, excludeType: MediaResourceUserContentType.story.rawValue)
|
||||
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
|
||||
return Signal { subscriber in
|
||||
var isCancelled = false
|
||||
|
||||
processingQueue.justDispatch {
|
||||
var removeIds: [MediaResourceId] = []
|
||||
var removeRawIds: [Data] = []
|
||||
var localCounter = 0
|
||||
for resourceId in peerResourceIds {
|
||||
localCounter += 1
|
||||
if localCounter % 100 == 0 {
|
||||
if isCancelled {
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
removeRawIds.append(resourceId)
|
||||
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
|
||||
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
|
||||
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
|
||||
removeIds.append(id)
|
||||
}
|
||||
}
|
||||
let allSignal: Signal<Never, NoError>
|
||||
if timeout != Int32.max {
|
||||
let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout
|
||||
|
||||
allSignal = mediaBox.storageBox.all(peerId: peerId, excludeType: MediaResourceUserContentType.story.rawValue)
|
||||
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
|
||||
return Signal { subscriber in
|
||||
var isCancelled = false
|
||||
|
||||
if !removeIds.isEmpty {
|
||||
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
|
||||
|
||||
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
|
||||
var actualRawIds: [Data] = []
|
||||
for id in actualIds {
|
||||
if let data = id.stringRepresentation.data(using: .utf8) {
|
||||
actualRawIds.append(data)
|
||||
processingQueue.justDispatch {
|
||||
var removeIds: [MediaResourceId] = []
|
||||
var localCounter = 0
|
||||
for resourceId in peerResourceIds {
|
||||
localCounter += 1
|
||||
if localCounter % 100 == 0 {
|
||||
if isCancelled {
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mediaBox.storageBox.remove(ids: actualRawIds)
|
||||
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} else {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let storySignal = mediaBox.storageBox.all(peerId: peerId, onlyType: MediaResourceUserContentType.story.rawValue)
|
||||
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
|
||||
return Signal { subscriber in
|
||||
var isCancelled = false
|
||||
|
||||
processingQueue.justDispatch {
|
||||
var removeIds: [MediaResourceId] = []
|
||||
var removeRawIds: [Data] = []
|
||||
var localCounter = 0
|
||||
for resourceId in peerResourceIds {
|
||||
localCounter += 1
|
||||
if localCounter % 100 == 0 {
|
||||
if isCancelled {
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
|
||||
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
|
||||
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
|
||||
removeIds.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
removeRawIds.append(resourceId)
|
||||
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
|
||||
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
|
||||
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
|
||||
removeIds.append(id)
|
||||
if !removeIds.isEmpty {
|
||||
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
|
||||
|
||||
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
|
||||
var actualRawIds: [Data] = []
|
||||
for id in actualIds {
|
||||
if let data = id.stringRepresentation.data(using: .utf8) {
|
||||
actualRawIds.append(data)
|
||||
}
|
||||
}
|
||||
|
||||
mediaBox.storageBox.remove(ids: actualRawIds)
|
||||
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} else {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
if !removeIds.isEmpty {
|
||||
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
|
||||
|
||||
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
|
||||
var actualRawIds: [Data] = []
|
||||
for id in actualIds {
|
||||
if let data = id.stringRepresentation.data(using: .utf8) {
|
||||
actualRawIds.append(data)
|
||||
return ActionDisposable {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allSignal = .complete()
|
||||
}
|
||||
|
||||
let storySignal: Signal<Never, NoError>
|
||||
if storyTimeout != Int32.max {
|
||||
let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - storyTimeout
|
||||
storySignal = mediaBox.storageBox.all(peerId: peerId, onlyType: MediaResourceUserContentType.story.rawValue)
|
||||
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
|
||||
return Signal { subscriber in
|
||||
var isCancelled = false
|
||||
|
||||
processingQueue.justDispatch {
|
||||
var removeIds: [MediaResourceId] = []
|
||||
var localCounter = 0
|
||||
for resourceId in peerResourceIds {
|
||||
localCounter += 1
|
||||
if localCounter % 100 == 0 {
|
||||
if isCancelled {
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mediaBox.storageBox.remove(ids: actualRawIds)
|
||||
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
|
||||
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
|
||||
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
|
||||
removeIds.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
if !removeIds.isEmpty {
|
||||
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
|
||||
|
||||
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
|
||||
var actualRawIds: [Data] = []
|
||||
for id in actualIds {
|
||||
if let data = id.stringRepresentation.data(using: .utf8) {
|
||||
actualRawIds.append(data)
|
||||
}
|
||||
}
|
||||
|
||||
mediaBox.storageBox.remove(ids: actualRawIds)
|
||||
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} else {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} else {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
storySignal = .complete()
|
||||
}
|
||||
|
||||
return allSignal |> then(storySignal)
|
||||
|
|
|
|||
|
|
@ -2404,7 +2404,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
if additionalSideInsets.right > 0.0 {
|
||||
textFieldInsets.right += additionalSideInsets.right / 3.0
|
||||
}
|
||||
if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward || hasSlowmodeButton {
|
||||
if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward || hasSlowmodeButton || isEditingMedia {
|
||||
} else {
|
||||
if let customRightAction = self.customRightAction, case .empty = customRightAction {
|
||||
textFieldInsets.right = 8.0
|
||||
|
|
@ -3033,7 +3033,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
if self.extendedSearchLayout {
|
||||
nextButtonTopRight.x -= 46.0
|
||||
} else if hasSlowmodeButton {
|
||||
} else if inputHasText || hasMediaDraft || hasForward {
|
||||
} else if inputHasText || hasMediaDraft || hasForward || isEditingMedia {
|
||||
nextButtonTopRight.x -= sendActionButtonsSize.width
|
||||
}
|
||||
for (item, button) in self.accessoryItemButtons.reversed() {
|
||||
|
|
@ -3182,7 +3182,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
}
|
||||
|
||||
var mediaActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - mediaActionButtonsSize.height), size: mediaActionButtonsSize)
|
||||
if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil || hasSlowmodeButton {
|
||||
if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil || hasSlowmodeButton || isEditingMedia {
|
||||
mediaActionButtonsFrame.origin.x = width + 8.0
|
||||
}
|
||||
transition.updateFrame(node: self.mediaActionButtons, frame: mediaActionButtonsFrame)
|
||||
|
|
@ -3230,7 +3230,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
var sendActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX - sendActionButtonsSize.width, y: textInputContainerBackgroundFrame.maxY - sendActionButtonsSize.height), size: sendActionButtonsSize)
|
||||
|
||||
let sendActionsScale: CGFloat
|
||||
if inputHasText || hasMediaDraft || hasForward {
|
||||
if inputHasText || hasMediaDraft || hasForward || isEditingMedia {
|
||||
sendActionsScale = 1.0
|
||||
} else {
|
||||
sendActionsScale = 0.001
|
||||
|
|
@ -4369,6 +4369,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
if presentationInterfaceState.interfaceState.mediaDraftState != nil {
|
||||
keepSendButtonEnabled = true
|
||||
}
|
||||
if let editMessageState = presentationInterfaceState.editMessageState {
|
||||
if case let .media(value) = editMessageState.content, !value.isEmpty {
|
||||
keepSendButtonEnabled = true
|
||||
}
|
||||
}
|
||||
hasForward = presentationInterfaceState.interfaceState.forwardMessageIds != nil
|
||||
|
||||
hideMicButtonBackground = presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil
|
||||
|
|
|
|||
|
|
@ -1010,7 +1010,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
|
|||
|
||||
if (leftTitleInset == leftInset) != (rightTitleInset == rightInset) {
|
||||
if rightTitleInset == rightInset {
|
||||
rightTitleInset = leftTitleInset
|
||||
rightTitleInset = max(rightInset, 16.0)
|
||||
} else if leftTitleInset == leftInset {
|
||||
leftTitleInset = rightTitleInset
|
||||
}
|
||||
|
|
@ -1041,6 +1041,9 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
|
|||
let titleSize = titleView.updateLayout(availableSize: CGSize(width: size.width - leftTitleInset - rightTitleInset, height: nominalHeight), transition: titleViewTransition)
|
||||
|
||||
var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||
if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset {
|
||||
titleFrame.origin.x = size.width - rightTitleInset - titleFrame.width
|
||||
}
|
||||
if titleFrame.origin.x < leftTitleInset {
|
||||
titleFrame.origin.x = leftTitleInset + floorToScreenPixels((size.width - leftTitleInset - rightTitleInset - titleFrame.width) * 0.5)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,14 +69,18 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
private let groupsInCommonContext: GroupsInCommonContext
|
||||
|
||||
weak var parentController: ViewController?
|
||||
|
||||
|
||||
private let listBackgroundView: UIImageView
|
||||
private let listMaskView: UIImageView
|
||||
private let listNode: ListView
|
||||
private var state: GroupsInCommonState?
|
||||
private var currentEntries: [GroupsInCommonListEntry] = []
|
||||
private var enqueuedTransactions: [GroupsInCommonListTransaction] = []
|
||||
|
||||
private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
|
||||
|
||||
|
||||
private var ignoreListBackgroundUpdates: Bool = false
|
||||
|
||||
private let ready = Promise<Bool>()
|
||||
private var didSetReady: Bool = false
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
|
|
@ -114,11 +118,25 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||
}
|
||||
|
||||
|
||||
self.listBackgroundView = UIImageView()
|
||||
self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.listMaskView = UIImageView()
|
||||
self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 2.0 * 2.0 + 26.0 * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 2.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 2 + 26).withRenderingMode(.alwaysTemplate)
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
self.listNode.preloadPages = true
|
||||
self.view.addSubview(self.listBackgroundView)
|
||||
self.addSubnode(self.listNode)
|
||||
self.view.addSubview(self.listMaskView)
|
||||
|
||||
self.disposable = (groupsInCommonContext.state
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||
|
|
@ -139,6 +157,23 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
strongSelf.groupsInCommonContext.loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.visibleContentOffsetChanged = { [weak self] _, transition in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.ignoreListBackgroundUpdates {
|
||||
self.updateListBackground(transition: transition)
|
||||
}
|
||||
}
|
||||
self.listNode.displayedItemRangeChanged = { [weak self] _, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.ignoreListBackgroundUpdates {
|
||||
self.updateListBackground(transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -161,9 +196,10 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
let isFirstLayout = self.currentParams == nil
|
||||
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
||||
|
||||
self.ignoreListBackgroundUpdates = true
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
|
||||
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if isScrollingLockedAtTop {
|
||||
switch self.listNode.visibleContentOffset() {
|
||||
|
|
@ -174,10 +210,16 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset + 16.0, bottom: bottomInset, right: sideInset + 16.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||
|
||||
|
||||
self.ignoreListBackgroundUpdates = false
|
||||
self.updateListBackground(transition: transition)
|
||||
|
||||
self.listBackgroundView.tintColor = presentationData.theme.list.itemBlocksBackgroundColor
|
||||
self.listMaskView.tintColor = presentationData.theme.list.blocksBackgroundColor
|
||||
|
||||
if isFirstLayout, let state = self.state {
|
||||
self.updatePeers(state: state, presentationData: presentationData)
|
||||
}
|
||||
|
|
@ -221,6 +263,35 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
})
|
||||
}
|
||||
|
||||
private func updateListBackground(transition: ContainedViewLayoutTransition) {
|
||||
guard self.listNode.visibleSize.width != 0.0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var distanceToTop: CGFloat = -100.0
|
||||
var distanceToBottom: CGFloat = -100.0
|
||||
switch self.listNode.visibleContentOffset() {
|
||||
case let .known(topOffset):
|
||||
distanceToTop = -topOffset + self.listNode.insets.top
|
||||
default:
|
||||
break
|
||||
}
|
||||
switch self.listNode.visibleBottomContentOffset() {
|
||||
case let .known(bottomOffset):
|
||||
distanceToBottom = -bottomOffset + self.listNode.insets.bottom
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
distanceToTop = max(-100.0, distanceToTop)
|
||||
distanceToBottom = max(-100.0, distanceToBottom)
|
||||
|
||||
let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, self.listNode.visibleSize.width - 16.0 * 2.0), height: max(1.0, self.listNode.visibleSize.height - distanceToBottom - distanceToTop)))
|
||||
let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY - 2.0), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height + 2.0 * 2.0))
|
||||
transition.updateFrame(view: self.listBackgroundView, frame: listBackgroundFrame)
|
||||
transition.updateFrame(view: self.listMaskView, frame: listMaskFrame)
|
||||
}
|
||||
|
||||
func findLoadedMessage(id: MessageId) -> Message? {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,6 +286,8 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
|
||||
weak var parentController: ViewController?
|
||||
|
||||
private let listBackgroundView: UIImageView
|
||||
private let listMaskView: UIImageView
|
||||
private let listNode: ListView
|
||||
private var currentEntries: [PeerMembersListEntry] = []
|
||||
private var enclosingPeer: Peer?
|
||||
|
|
@ -296,6 +298,8 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool)?
|
||||
private let presentationDataPromise = Promise<PresentationData>()
|
||||
|
||||
private var ignoreListBackgroundUpdates: Bool = false
|
||||
|
||||
private let ready = Promise<Bool>()
|
||||
private var didSetReady: Bool = false
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
|
|
@ -325,10 +329,24 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||
}
|
||||
|
||||
self.listBackgroundView = UIImageView()
|
||||
self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.listMaskView = UIImageView()
|
||||
self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 26.0 * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 26).withRenderingMode(.alwaysTemplate)
|
||||
|
||||
super.init()
|
||||
|
||||
self.listNode.preloadPages = true
|
||||
self.view.addSubview(self.listBackgroundView)
|
||||
self.addSubnode(self.listNode)
|
||||
self.view.addSubview(self.listMaskView)
|
||||
|
||||
self.disposable = (combineLatest(queue: .mainQueue(),
|
||||
membersContext.state,
|
||||
|
|
@ -353,6 +371,23 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
strongSelf.membersContext.loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.visibleContentOffsetChanged = { [weak self] _, transition in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.ignoreListBackgroundUpdates {
|
||||
self.updateListBackground(transition: transition)
|
||||
}
|
||||
}
|
||||
self.listNode.displayedItemRangeChanged = { [weak self] _, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.ignoreListBackgroundUpdates {
|
||||
self.updateListBackground(transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -376,6 +411,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
self.currentParams = (size, isScrollingLockedAtTop)
|
||||
self.presentationDataPromise.set(.single(presentationData))
|
||||
|
||||
self.ignoreListBackgroundUpdates = true
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
|
||||
|
|
@ -388,10 +424,16 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: duration), directionHint: .Up)
|
||||
}
|
||||
}
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset + 16.0, bottom: bottomInset, right: sideInset + 16.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||
|
||||
self.ignoreListBackgroundUpdates = false
|
||||
self.updateListBackground(transition: transition)
|
||||
|
||||
self.listBackgroundView.tintColor = presentationData.theme.list.itemBlocksBackgroundColor
|
||||
self.listMaskView.tintColor = presentationData.theme.list.blocksBackgroundColor
|
||||
|
||||
if isFirstLayout, let enclosingPeer = self.enclosingPeer, let state = self.currentState {
|
||||
self.updateState(enclosingPeer: enclosingPeer, state: state, presentationData: presentationData)
|
||||
}
|
||||
|
|
@ -471,6 +513,35 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
})
|
||||
}
|
||||
|
||||
private func updateListBackground(transition: ContainedViewLayoutTransition) {
|
||||
guard self.listNode.visibleSize.width != 0.0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var distanceToTop: CGFloat = -100.0
|
||||
var distanceToBottom: CGFloat = -100.0
|
||||
switch self.listNode.visibleContentOffset() {
|
||||
case let .known(topOffset):
|
||||
distanceToTop = -topOffset + self.listNode.insets.top
|
||||
default:
|
||||
break
|
||||
}
|
||||
switch self.listNode.visibleBottomContentOffset() {
|
||||
case let .known(bottomOffset):
|
||||
distanceToBottom = -bottomOffset + self.listNode.insets.bottom
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
distanceToTop = max(-100.0, distanceToTop)
|
||||
distanceToBottom = max(-100.0, distanceToBottom)
|
||||
|
||||
let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, self.listNode.visibleSize.width - 16.0 * 2.0), height: max(1.0, self.listNode.visibleSize.height - distanceToBottom - distanceToTop)))
|
||||
let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height))
|
||||
transition.updateFrame(view: self.listBackgroundView, frame: listBackgroundFrame)
|
||||
transition.updateFrame(view: self.listMaskView, frame: listMaskFrame)
|
||||
}
|
||||
|
||||
func findLoadedMessage(id: MessageId) -> Message? {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -962,7 +962,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
|||
if self.currentPaneKey == .gifts {
|
||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||
} else {
|
||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor.mixedWith(presentationData.theme.list.plainBackgroundColor, alpha: expansionFraction)
|
||||
if self.currentPaneKey == .stories || self.currentPaneKey == .storyArchive {
|
||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor.mixedWith(presentationData.theme.list.plainBackgroundColor, alpha: expansionFraction)
|
||||
} else {
|
||||
backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
self.backgroundColor = backgroundColor
|
||||
|
|
|
|||
|
|
@ -682,6 +682,27 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
let itemsCount = items.count
|
||||
|
||||
/*if let cachedData = data.cachedData as? CachedUserData {
|
||||
var hasPaidFee = false
|
||||
if let paidMessageStars = cachedData.peerStatusSettings?.paidMessageStars, paidMessageStars != .zero {
|
||||
hasPaidFee = true
|
||||
}
|
||||
if !hasPaidFee {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Return Fee", icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self, let peer = self.data?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self.context.engine.peers.reinstateNoPaidMessagesException(scopePeerId: self.context.account.peerId, peerId: peer.id).startStandalone()
|
||||
})))
|
||||
}
|
||||
}*/
|
||||
|
||||
if canSetupAutoremoveTimeout {
|
||||
let strings = strongSelf.presentationData.strings
|
||||
|
|
|
|||
|
|
@ -187,8 +187,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
//self.addSubnode(self.backgroundNode)
|
||||
//self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.selectionPanel)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -680,6 +680,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
|||
var onTagTapImpl: (() -> Void)?
|
||||
var didScrollImpl: (() -> Void)?
|
||||
var coveringInsetOffsetUpdatedImpl: ((ContainedViewLayoutTransition) -> Void)?
|
||||
var scrollingOffsetUpdatedImpl: ((ContainedViewLayoutTransition) -> Void)?
|
||||
var onBeginFastScrollingImpl: (() -> Void)?
|
||||
var getShimmerColorsImpl: (() -> SparseItemGrid.ShimmerColors)?
|
||||
var updateShimmerLayersImpl: ((SparseItemGridDisplayItem) -> Void)?
|
||||
|
|
@ -1054,6 +1055,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
|
|||
}
|
||||
|
||||
func scrollingOffsetUpdated(transition: ContainedViewLayoutTransition) {
|
||||
self.scrollingOffsetUpdatedImpl?(transition)
|
||||
}
|
||||
|
||||
func onBeginFastScrolling() {
|
||||
|
|
@ -1132,6 +1134,8 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
private let contextGestureContainerNode: ContextControllerSourceNode
|
||||
private let itemGrid: SparseItemGrid
|
||||
private let itemGridBinding: SparseItemGridBindingImpl
|
||||
private let listBackgroundView: UIImageView
|
||||
private let listMaskView: UIImageView
|
||||
private let directMediaImageCache: DirectMediaImageCache
|
||||
private var items: SparseItemGrid.Items?
|
||||
private var didUpdateItemsOnce: Bool = false
|
||||
|
|
@ -1271,8 +1275,23 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
self.calendarSource = nil
|
||||
}
|
||||
|
||||
self.listBackgroundView = UIImageView()
|
||||
self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.listMaskView = UIImageView()
|
||||
self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 26.0 * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 26).withRenderingMode(.alwaysTemplate)
|
||||
self.listMaskView.image = nil
|
||||
self.listBackgroundView.isHidden = true
|
||||
self.listMaskView.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
if self.initialMessageIndex != nil {
|
||||
self.didSetReady = true
|
||||
self.ready.set(.single(true))
|
||||
|
|
@ -1342,6 +1361,13 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
strongSelf.tabBarOffsetUpdated?(transition)
|
||||
}
|
||||
|
||||
self.itemGridBinding.scrollingOffsetUpdatedImpl = { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateListBackground(transition: transition)
|
||||
}
|
||||
|
||||
var processedOnBeginFastScrolling = false
|
||||
self.itemGridBinding.onBeginFastScrollingImpl = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -1421,6 +1447,8 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
self.contextGestureContainerNode.isGestureEnabled = !useListItems
|
||||
self.contextGestureContainerNode.addSubnode(self.itemGrid)
|
||||
self.addSubnode(self.contextGestureContainerNode)
|
||||
self.view.insertSubview(self.listBackgroundView, at: 0)
|
||||
self.view.addSubview(self.listMaskView)
|
||||
|
||||
self.contextGestureContainerNode.shouldBegin = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -2239,7 +2267,8 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
fixedItemHeight = nil
|
||||
}
|
||||
|
||||
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: nil, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none)
|
||||
let listSideInset = isList ? sideInset + 16.0 : sideInset
|
||||
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: topInset, left: listSideInset, bottom: bottomInset, right: listSideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: nil, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none)
|
||||
if let initialMessageIndexValue = self.initialMessageIndex, items.items.contains(where: { item in
|
||||
if let _ = item as? VisualMediaItem {
|
||||
return true
|
||||
|
|
@ -2257,9 +2286,44 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.listBackgroundView.isHidden = !isList
|
||||
self.listMaskView.isHidden = !isList
|
||||
if isList {
|
||||
self.listBackgroundView.tintColor = presentationData.theme.list.itemBlocksBackgroundColor
|
||||
self.listMaskView.tintColor = presentationData.theme.list.blocksBackgroundColor
|
||||
self.updateListBackground(transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateListBackground(transition: ContainedViewLayoutTransition) {
|
||||
guard let (size, topInset, _, bottomInset, _, _, _, _, _, _) = self.currentParams else {
|
||||
return
|
||||
}
|
||||
let _ = bottomInset
|
||||
guard size.width != 0.0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var distanceToTop: CGFloat = -100.0
|
||||
var distanceToBottom: CGFloat = -100.0
|
||||
|
||||
let scrollingOffset = self.itemGrid.scrollingOffset
|
||||
distanceToTop = -scrollingOffset + topInset
|
||||
|
||||
let contentBottomOffset = self.itemGrid.contentBottomOffset
|
||||
distanceToBottom = -contentBottomOffset + size.height
|
||||
|
||||
distanceToTop = max(-100.0, distanceToTop)
|
||||
distanceToBottom = max(-100.0, distanceToBottom)
|
||||
|
||||
let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, size.width - 16.0 * 2.0), height: max(1.0, size.height - distanceToBottom - distanceToTop)))
|
||||
let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height))
|
||||
transition.updateFrame(view: self.listBackgroundView, frame: listBackgroundFrame)
|
||||
transition.updateFrame(view: self.listMaskView, frame: listMaskFrame)
|
||||
}
|
||||
|
||||
public func currentTopTimestamp() -> Int32? {
|
||||
var timestamp: Int32?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ swift_library(
|
|||
"//submodules/SegmentedControlNode",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||
self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor
|
||||
self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
|
||||
self.topPanelBackgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
self.topPanelMergedBackgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor
|
||||
self.topPanelMergedBackgroundView.backgroundColor = component.theme.list.blocksBackgroundColor
|
||||
}
|
||||
|
||||
let topPanelCoverHeight: CGFloat = 10.0
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import GalleryData
|
|||
import AnimatedTextComponent
|
||||
import BottomButtonPanelComponent
|
||||
import GlassBackgroundComponent
|
||||
import EdgeEffect
|
||||
|
||||
#if DEBUG
|
||||
import os.signpost
|
||||
|
|
@ -124,20 +125,17 @@ final class StorageUsageScreenComponent: Component {
|
|||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let overNavigationContainer: UIView
|
||||
let makeStorageUsageExceptionsScreen: (CacheStorageSettings.PeerStorageCategory) -> ViewController?
|
||||
let peer: EnginePeer?
|
||||
let ready: Promise<Bool>
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
overNavigationContainer: UIView,
|
||||
makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?,
|
||||
peer: EnginePeer?,
|
||||
ready: Promise<Bool>
|
||||
) {
|
||||
self.context = context
|
||||
self.overNavigationContainer = overNavigationContainer
|
||||
self.makeStorageUsageExceptionsScreen = makeStorageUsageExceptionsScreen
|
||||
self.peer = peer
|
||||
self.ready = ready
|
||||
|
|
@ -755,6 +753,8 @@ final class StorageUsageScreenComponent: Component {
|
|||
private let navigationEditButton = ComponentView<Empty>()
|
||||
private let navigationDoneButton = ComponentView<Empty>()
|
||||
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
|
||||
private let headerView = ComponentView<Empty>()
|
||||
private let headerOffsetContainer: UIView
|
||||
private let headerDescriptionView = ComponentView<Empty>()
|
||||
|
|
@ -768,7 +768,8 @@ final class StorageUsageScreenComponent: Component {
|
|||
private var doneStatusNode: RadialStatusNode?
|
||||
|
||||
private let scrollContainerView: UIView
|
||||
|
||||
private let topContentOverlayView: UIView
|
||||
|
||||
private let pieChartView = ComponentView<Empty>()
|
||||
private let chartTotalLabel = ComponentView<Empty>()
|
||||
private let categoriesView = ComponentView<Empty>()
|
||||
|
|
@ -811,7 +812,11 @@ final class StorageUsageScreenComponent: Component {
|
|||
self.headerOffsetContainer = SparseContainerView()
|
||||
|
||||
self.scrollContainerView = UIView()
|
||||
|
||||
|
||||
self.topContentOverlayView = UIView()
|
||||
self.topContentOverlayView.isUserInteractionEnabled = false
|
||||
self.topContentOverlayView.alpha = 0.0
|
||||
|
||||
self.scrollView = ScrollViewImpl()
|
||||
|
||||
self.keepDurationSectionContainerView = UIView()
|
||||
|
|
@ -823,6 +828,8 @@ final class StorageUsageScreenComponent: Component {
|
|||
|
||||
self.navigationRightButtonsBackground = GlassBackgroundView()
|
||||
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
|
|
@ -848,6 +855,8 @@ final class StorageUsageScreenComponent: Component {
|
|||
|
||||
self.scrollView.layer.addSublayer(self.headerProgressBackgroundLayer)
|
||||
self.scrollView.layer.addSublayer(self.headerProgressForegroundLayer)
|
||||
|
||||
self.addSubview(self.edgeEffectView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -961,6 +970,7 @@ final class StorageUsageScreenComponent: Component {
|
|||
if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View {
|
||||
panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition)
|
||||
}
|
||||
self.topContentOverlayView.alpha = 1.0 - expansionDistanceFactor
|
||||
|
||||
var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0
|
||||
offsetFraction = min(1.0, max(0.0, offsetFraction))
|
||||
|
|
@ -1105,10 +1115,10 @@ final class StorageUsageScreenComponent: Component {
|
|||
}
|
||||
|
||||
if self.headerOffsetContainer.superview == nil {
|
||||
component.overNavigationContainer.addSubview(self.headerOffsetContainer)
|
||||
self.addSubview(self.headerOffsetContainer)
|
||||
}
|
||||
if self.navigationRightButtonsBackground.superview == nil {
|
||||
component.overNavigationContainer.addSubview(self.navigationRightButtonsBackground)
|
||||
self.addSubview(self.navigationRightButtonsBackground)
|
||||
}
|
||||
|
||||
var wasLockedAtPanels = false
|
||||
|
|
@ -1222,6 +1232,11 @@ final class StorageUsageScreenComponent: Component {
|
|||
|
||||
self.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||
|
||||
let edgeEffectHeight: CGFloat = environment.navigationHeight + 24.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: min(64.0, edgeEffectHeight), transition: transition)
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
let topInset: CGFloat = 19.0
|
||||
|
|
@ -2219,10 +2234,16 @@ final class StorageUsageScreenComponent: Component {
|
|||
self.scrollContainerView.addSubview(panelContainerView)
|
||||
}
|
||||
transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize))
|
||||
if self.topContentOverlayView.superview == nil {
|
||||
self.scrollContainerView.insertSubview(self.topContentOverlayView, belowSubview: panelContainerView)
|
||||
}
|
||||
self.topContentOverlayView.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||
transition.setFrame(view: self.topContentOverlayView, frame: CGRect(origin: CGPoint(x: 0.0, y: panelContainerView.frame.minY - availableSize.height), size: availableSize))
|
||||
}
|
||||
contentHeight += panelContainerSize.height
|
||||
} else {
|
||||
self.panelContainer.view?.removeFromSuperview()
|
||||
self.topContentOverlayView.removeFromSuperview()
|
||||
}
|
||||
|
||||
self.ignoreScrolling = true
|
||||
|
|
@ -3315,8 +3336,6 @@ final class StorageUsageScreenComponent: Component {
|
|||
public final class StorageUsageScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
|
||||
private let overNavigationContainer: UIView
|
||||
|
||||
private let readyValue = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self.readyValue
|
||||
|
|
@ -3327,19 +3346,13 @@ public final class StorageUsageScreen: ViewControllerComponentContainer {
|
|||
public init(context: AccountContext, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer? = nil, focusOnItemTag: StorageUsageEntryTag? = nil) {
|
||||
self.context = context
|
||||
|
||||
self.overNavigationContainer = SparseContainerView()
|
||||
|
||||
let componentReady = Promise<Bool>()
|
||||
super.init(context: context, component: StorageUsageScreenComponent(context: context, overNavigationContainer: self.overNavigationContainer, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .default)
|
||||
super.init(context: context, component: StorageUsageScreenComponent(context: context, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .transparent)
|
||||
|
||||
if peer != nil {
|
||||
self.navigationPresentation = .modal
|
||||
}
|
||||
|
||||
if let navigationBar = self.navigationBar {
|
||||
navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0)
|
||||
}
|
||||
|
||||
self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -551,80 +551,94 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||
}
|
||||
}
|
||||
|
||||
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, voipVersions: PresentationCallManagerImpl.voipVersions(includeExperimental: true, includeReference: false).map { version, supportsVideo -> CallSessionManagerImplementationVersion in
|
||||
CallSessionManagerImplementationVersion(version: version, supportsVideo: supportsVideo)
|
||||
}, appData: self.regularDeviceToken.get()
|
||||
|> map { token in
|
||||
let tokenEnvironment: String
|
||||
#if DEBUG
|
||||
tokenEnvironment = "sandbox"
|
||||
#else
|
||||
tokenEnvironment = "production"
|
||||
#endif
|
||||
|
||||
let data = buildConfig.bundleData(withAppToken: token, tokenType: "apns", tokenEnvironment: tokenEnvironment, signatureDict: signatureDict)
|
||||
if let data = data, let _ = String(data: data, encoding: .utf8) {
|
||||
} else {
|
||||
Logger.shared.log("data", "can't deserialize")
|
||||
}
|
||||
return data
|
||||
}, externalRequestVerificationStream: self.firebaseRequestVerificationSecretStream.get(), externalRecaptchaRequestVerification: { method, siteKey in
|
||||
return Signal { subscriber in
|
||||
let recaptchaClient: Promise<RecaptchaClient>
|
||||
if let current = self.recaptchaClientsBySiteKey[siteKey] {
|
||||
recaptchaClient = current
|
||||
} else {
|
||||
recaptchaClient = Promise<RecaptchaClient>()
|
||||
self.recaptchaClientsBySiteKey[siteKey] = recaptchaClient
|
||||
|
||||
Recaptcha.fetchClient(withSiteKey: siteKey) { client, error in
|
||||
Queue.mainQueue().async {
|
||||
guard let client else {
|
||||
Logger.shared.log("App \(self.episodeId)", "RecaptchaClient creation error: \(String(describing: error)).")
|
||||
return
|
||||
}
|
||||
recaptchaClient.set(.single(client))
|
||||
}
|
||||
}
|
||||
}
|
||||
let networkArguments = NetworkInitializationArguments(
|
||||
apiId: apiId,
|
||||
apiHash: apiHash,
|
||||
languagesCategory: languagesCategory,
|
||||
appVersion: appVersion,
|
||||
voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer,
|
||||
voipVersions: PresentationCallManagerImpl.voipVersions(includeExperimental: true, includeReference: false).map { version, supportsVideo -> CallSessionManagerImplementationVersion in
|
||||
CallSessionManagerImplementationVersion(version: version, supportsVideo: supportsVideo)
|
||||
},
|
||||
appData: self.regularDeviceToken.get() |> map { token in
|
||||
let tokenEnvironment: String
|
||||
#if DEBUG
|
||||
tokenEnvironment = "sandbox"
|
||||
#else
|
||||
tokenEnvironment = "production"
|
||||
#endif
|
||||
|
||||
return (recaptchaClient.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { recaptchaClient -> Signal<String?, NoError> in
|
||||
return Signal { subscriber in
|
||||
var recaptchaAction: RecaptchaAction?
|
||||
switch method {
|
||||
case "signup":
|
||||
recaptchaAction = RecaptchaAction.signup
|
||||
default:
|
||||
break
|
||||
}
|
||||
let data = buildConfig.bundleData(withAppToken: token, tokenType: "apns", tokenEnvironment: tokenEnvironment, signatureDict: signatureDict)
|
||||
if let data = data, let _ = String(data: data, encoding: .utf8) {
|
||||
} else {
|
||||
Logger.shared.log("data", "can't deserialize")
|
||||
}
|
||||
return data
|
||||
},
|
||||
externalRequestVerificationStream: self.firebaseRequestVerificationSecretStream.get(),
|
||||
externalRecaptchaRequestVerification: { method, siteKey in
|
||||
return Signal<String?, NoError> { subscriber in
|
||||
let recaptchaClient: Promise<RecaptchaClient>
|
||||
if let current = self.recaptchaClientsBySiteKey[siteKey] {
|
||||
recaptchaClient = current
|
||||
} else {
|
||||
recaptchaClient = Promise<RecaptchaClient>()
|
||||
self.recaptchaClientsBySiteKey[siteKey] = recaptchaClient
|
||||
|
||||
guard let recaptchaAction else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
recaptchaClient.execute(withAction: recaptchaAction) { token, error in
|
||||
if let token {
|
||||
subscriber.putNext(token)
|
||||
Logger.shared.log("App \(self.episodeId)", "RecaptchaClient executed successfully")
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
Logger.shared.log("App \(self.episodeId)", "RecaptchaClient execute error: \(String(describing: error))")
|
||||
Recaptcha.fetchClient(withSiteKey: siteKey) { client, error in
|
||||
Queue.mainQueue().async {
|
||||
guard let client else {
|
||||
Logger.shared.log("App \(self.episodeId)", "RecaptchaClient creation error: \(String(describing: error)).")
|
||||
return
|
||||
}
|
||||
recaptchaClient.set(.single(client))
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}).startStandalone(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}, autolockDeadine: autolockDeadine, encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: buildConfig.isICloudEnabled)
|
||||
|
||||
return (recaptchaClient.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { recaptchaClient -> Signal<String?, NoError> in
|
||||
return Signal { subscriber in
|
||||
var recaptchaAction: RecaptchaAction?
|
||||
switch method {
|
||||
case "signup":
|
||||
recaptchaAction = RecaptchaAction.signup
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
guard let recaptchaAction else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
recaptchaClient.execute(withAction: recaptchaAction) { token, error in
|
||||
if let token {
|
||||
subscriber.putNext(token)
|
||||
Logger.shared.log("App \(self.episodeId)", "RecaptchaClient executed successfully")
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
Logger.shared.log("App \(self.episodeId)", "RecaptchaClient execute error: \(String(describing: error))")
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}).startStandalone(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
},
|
||||
autolockDeadine: autolockDeadine,
|
||||
encryptionProvider: OpenSSLEncryptionProvider(),
|
||||
deviceModelName: nil,
|
||||
useBetaFeatures: !buildConfig.isAppStoreBuild,
|
||||
isICloudEnabled: buildConfig.isICloudEnabled
|
||||
)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
self.mainWindow?.presentNative(UIAlertController(title: nil, message: "Error 2", preferredStyle: .alert))
|
||||
|
|
|
|||
|
|
@ -838,6 +838,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.wrappingNode.contentNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.contentNode.addSubnode(self.backgroundNode)
|
||||
self.contentContainerNode.contentNode.addSubnode(self.historyNodeContainer)
|
||||
|
||||
self.contentContainerNode.contentNode.addSubnode(self.messageTransitionNode)
|
||||
|
||||
self.contentContainerNode.contentNode.addSubnode(self.floatingTopicsPanelContainer)
|
||||
|
||||
if let navigationBar = self.navigationBar {
|
||||
|
|
@ -863,8 +866,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode)
|
||||
self.inputPanelContainerNode.addSubnode(self.inputPanelOverlayNode)
|
||||
self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode)
|
||||
|
||||
self.wrappingNode.contentNode.addSubnode(self.messageTransitionNode.overlayContainerNode)
|
||||
|
||||
self.wrappingNode.contentNode.addSubnode(self.messageTransitionNode)
|
||||
self.contentContainerNode.contentNode.addSubnode(self.navigateButtons)
|
||||
self.wrappingNode.contentNode.addSubnode(self.presentationContextMarker)
|
||||
self.contentContainerNode.contentNode.addSubnode(self.contentDimNode)
|
||||
|
|
@ -2028,7 +2032,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
messageTransitionNode.bounds = previousMessageTransitionNode.bounds
|
||||
messageTransitionNode.transform = previousMessageTransitionNode.transform
|
||||
|
||||
self.wrappingNode.contentNode.insertSubnode(self.messageTransitionNode, aboveSubnode: previousMessageTransitionNode)
|
||||
previousMessageTransitionNode.supernode?.insertSubnode(self.messageTransitionNode, aboveSubnode: previousMessageTransitionNode)
|
||||
|
||||
self.emptyType = nil
|
||||
self.isLoadingValue = false
|
||||
|
|
@ -2330,7 +2334,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
bottomBackgroundEdgeEffectNode = value
|
||||
self.bottomBackgroundEdgeEffectNode = value
|
||||
value.isUserInteractionEnabled = false
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.messageTransitionNode.view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2531,7 +2535,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
topBackgroundEdgeEffectNode = value
|
||||
self.topBackgroundEdgeEffectNode = value
|
||||
value.isUserInteractionEnabled = false
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.messageTransitionNode.view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,6 +433,12 @@ func chatHistoryEntriesForView(
|
|||
var i = 0
|
||||
let unreadEntry: ChatHistoryEntry = .UnreadEntry(maxReadIndex, presentationData)
|
||||
for entry in entries {
|
||||
if case let .MessageGroupEntry(_, messages, _) = entry {
|
||||
if !messages.isEmpty && maxReadIndex >= messages[0].0.index {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
if entry > unreadEntry {
|
||||
if i != 0 {
|
||||
entries.insert(unreadEntry, at: i)
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func chatHistoryViewForLocation(
|
|||
|
||||
let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData)
|
||||
|
||||
if view.isLoading {
|
||||
if view.isLoading || (view.entries.isEmpty && (view.holeEarlier || view.holeLater)) {
|
||||
return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType))
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ func chatHistoryViewForLocation(
|
|||
if preloaded {
|
||||
return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, flashIndicators: false, originalScrollPosition: nil, initialData: combinedInitialData, id: location.id)
|
||||
} else {
|
||||
if view.isLoading {
|
||||
if view.isLoading || (view.entries.isEmpty && (view.holeEarlier || view.holeLater)) {
|
||||
return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType))
|
||||
}
|
||||
var scrollPosition: ChatHistoryViewScrollPosition?
|
||||
|
|
@ -311,7 +311,7 @@ func chatHistoryViewForLocation(
|
|||
|
||||
let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData)
|
||||
|
||||
if view.isLoading {
|
||||
if view.isLoading || (view.entries.isEmpty && (view.holeEarlier || view.holeLater)) {
|
||||
return ChatHistoryViewUpdate.Loading(initialData: combinedInitialData, type: .Generic(type: updateType))
|
||||
}
|
||||
|
||||
|
|
@ -461,7 +461,7 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
|
|||
case .Loading:
|
||||
return nil
|
||||
case let .HistoryView(view, _, _, _, _, _, _):
|
||||
if view.isLoading {
|
||||
if view.isLoading || (view.entries.isEmpty && (view.holeEarlier || view.holeLater)) {
|
||||
return nil
|
||||
}
|
||||
return view.entries.isEmpty
|
||||
|
|
|
|||
|
|
@ -337,21 +337,25 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||
private let source: ChatMessageTransitionNodeImpl.Source
|
||||
private let getContentAreaInScreenSpace: () -> CGRect
|
||||
|
||||
private let portalSourceView: PortalSourceView
|
||||
private let scrollingContainer: ASDisplayNode
|
||||
private let containerNode: ASDisplayNode
|
||||
private let clippingNode: ASDisplayNode
|
||||
private var portalTargetView: PortalView?
|
||||
|
||||
weak var overlayController: OverlayTransitionContainerController?
|
||||
|
||||
var animationEnded: (() -> Void)?
|
||||
var updateAfterCompletion: Bool = false
|
||||
|
||||
init(itemNode: ChatMessageItemNodeProtocol, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||
init(itemNode: ChatMessageItemNodeProtocol, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, overlayContainerNode: ASDisplayNode, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||
self.portalSourceView = PortalSourceView()
|
||||
|
||||
self.itemNode = itemNode
|
||||
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
self.clippingNode.clipsToBounds = true
|
||||
self.clippingNode.clipsToBounds = false
|
||||
|
||||
self.scrollingContainer = ASDisplayNode()
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
|
@ -359,10 +363,18 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||
self.source = source
|
||||
|
||||
super.init()
|
||||
|
||||
self.view.addSubview(self.portalSourceView)
|
||||
|
||||
self.addSubnode(self.clippingNode)
|
||||
self.portalSourceView.addSubview(self.clippingNode.view)
|
||||
self.clippingNode.addSubnode(self.scrollingContainer)
|
||||
self.scrollingContainer.addSubnode(self.containerNode)
|
||||
|
||||
if let portalTargetView = PortalView(matchPosition: true) {
|
||||
self.portalTargetView = portalTargetView
|
||||
self.portalSourceView.addPortal(view: portalTargetView)
|
||||
overlayContainerNode.view.addSubview(portalTargetView.view)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -374,6 +386,13 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||
}
|
||||
|
||||
func beginAnimation() {
|
||||
if let portalTargetView = self.portalTargetView {
|
||||
portalTargetView.view.alpha = 0.0
|
||||
portalTargetView.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||
|
||||
self.portalSourceView.layer.animateAlpha(from: 0.01, to: 1.0, duration: 0.12)
|
||||
}
|
||||
|
||||
let verticalDuration: Double = ChatMessageTransitionNodeImpl.animationDuration
|
||||
let horizontalDuration: Double = verticalDuration
|
||||
let delay: Double = 0.0
|
||||
|
|
@ -970,6 +989,8 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||
private var decorationItemNodes: [DecorationItemNodeImpl] = []
|
||||
private var messageReactionContexts: [MessageReactionContext] = []
|
||||
private var customOffsetHandlers: [CustomOffsetHandlerImpl] = []
|
||||
|
||||
public let overlayContainerNode: ASDisplayNode
|
||||
|
||||
var hasScheduledTransitions: Bool {
|
||||
return !self.currentPendingItems.isEmpty
|
||||
|
|
@ -983,6 +1004,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||
self.listNode = listNode
|
||||
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||
self.onTransitionEvent = onTransitionEvent
|
||||
self.overlayContainerNode = ASDisplayNode()
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -1066,7 +1088,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
|||
}
|
||||
|
||||
if let contextSourceNode = contextSourceNode {
|
||||
let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: contextSourceNode, source: source, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
|
||||
let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: contextSourceNode, source: source, overlayContainerNode: self.overlayContainerNode, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
|
||||
animatingItemNode.updateLayout(size: self.bounds.size)
|
||||
|
||||
self.animatingItemNodes.append(animatingItemNode)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ private struct AccountTasks {
|
|||
}
|
||||
}
|
||||
|
||||
private let backgroundTaskSubmissionDelay: Double = 10.0
|
||||
|
||||
private struct PendingMediaUploadKey: Hashable {
|
||||
let accountId: AccountRecordId
|
||||
let messageId: MessageId
|
||||
|
|
@ -99,7 +101,8 @@ public final class SharedWakeupManager {
|
|||
private var backgroundProcessingTaskId: String?
|
||||
private var backgroundProcessingTaskLaunched: Bool = false
|
||||
private var backgroundProcessingTaskCancellationRequestedByApp: Bool = false
|
||||
|
||||
private var pendingBackgroundProcessingTaskTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var pendingStoryUploadsByKey: [PendingStoryUploadKey: Float] = [:]
|
||||
private var pendingStoryUploadStatusesByKey: [PendingStoryUploadKey: PendingStoryUploadStatus] = [:]
|
||||
private var backgroundStoryProcessingTaskProgressByKey: [PendingStoryUploadKey: Float] = [:]
|
||||
|
|
@ -107,7 +110,8 @@ public final class SharedWakeupManager {
|
|||
private var backgroundStoryProcessingTaskId: String?
|
||||
private var backgroundStoryProcessingTaskLaunched: Bool = false
|
||||
private var backgroundStoryProcessingTaskCancellationRequestedByApp: Bool = false
|
||||
|
||||
private var pendingBackgroundStoryProcessingTaskTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, acquireIdleExtension: @escaping () -> Disposable?, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>, presentationData: @escaping () -> PresentationData?) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
|
|
@ -155,6 +159,10 @@ public final class SharedWakeupManager {
|
|||
}
|
||||
strongSelf.allowBackgroundTimeExtensionDeadlineTimer?.invalidate()
|
||||
strongSelf.allowBackgroundTimeExtensionDeadlineTimer = nil
|
||||
strongSelf.pendingBackgroundProcessingTaskTimer?.invalidate()
|
||||
strongSelf.pendingBackgroundProcessingTaskTimer = nil
|
||||
strongSelf.pendingBackgroundStoryProcessingTaskTimer?.invalidate()
|
||||
strongSelf.pendingBackgroundStoryProcessingTaskTimer = nil
|
||||
}
|
||||
strongSelf.updateBackgroundProcessingTaskStateFromPendingMediaUploads()
|
||||
strongSelf.updateBackgroundProcessingTaskStateFromPendingStoryUploads()
|
||||
|
|
@ -356,6 +364,8 @@ public final class SharedWakeupManager {
|
|||
self.pendingStoryUploadsDisposable?.dispose()
|
||||
self.managedPausedInBackgroundPlayer?.dispose()
|
||||
self.keepIdleDisposable?.dispose()
|
||||
self.pendingBackgroundProcessingTaskTimer?.invalidate()
|
||||
self.pendingBackgroundStoryProcessingTaskTimer?.invalidate()
|
||||
if let (taskId, _, timer) = self.currentTask {
|
||||
timer.invalidate()
|
||||
self.endBackgroundTask(taskId)
|
||||
|
|
@ -369,19 +379,30 @@ public final class SharedWakeupManager {
|
|||
|
||||
let shouldHaveTask = !self.pendingMediaUploadsByKey.isEmpty && !self.inForeground
|
||||
let hadTask = self.backgroundProcessingTaskId != nil
|
||||
|
||||
|
||||
if shouldHaveTask {
|
||||
if !hadTask {
|
||||
self.startBackgroundProcessingTaskIfNeeded()
|
||||
if !hadTask && self.pendingBackgroundProcessingTaskTimer == nil {
|
||||
let timer = SwiftSignalKit.Timer(timeout: backgroundTaskSubmissionDelay, repeat: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.pendingBackgroundProcessingTaskTimer = nil
|
||||
self.startBackgroundProcessingTaskIfNeeded()
|
||||
}, queue: .mainQueue())
|
||||
self.pendingBackgroundProcessingTaskTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
} else {
|
||||
self.pendingBackgroundProcessingTaskTimer?.invalidate()
|
||||
self.pendingBackgroundProcessingTaskTimer = nil
|
||||
|
||||
if let backgroundProcessingTaskId = self.backgroundProcessingTaskId {
|
||||
if !self.backgroundProcessingTaskCancellationRequestedByApp {
|
||||
self.backgroundProcessingTaskCancellationRequestedByApp = true
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: backgroundProcessingTaskId)
|
||||
Logger.shared.log("Wakeup", "Requested BG task cancellation by app: \(backgroundProcessingTaskId)")
|
||||
}
|
||||
|
||||
|
||||
if !self.backgroundProcessingTaskLaunched {
|
||||
self.backgroundProcessingTaskId = nil
|
||||
self.backgroundProcessingTaskProgressByKey = [:]
|
||||
|
|
@ -399,19 +420,30 @@ public final class SharedWakeupManager {
|
|||
|
||||
let shouldHaveTask = !self.pendingStoryUploadStatusesByKey.isEmpty && !self.inForeground
|
||||
let hadTask = self.backgroundStoryProcessingTaskId != nil
|
||||
|
||||
|
||||
if shouldHaveTask {
|
||||
if !hadTask {
|
||||
self.startBackgroundStoryProcessingTaskIfNeeded()
|
||||
if !hadTask && self.pendingBackgroundStoryProcessingTaskTimer == nil {
|
||||
let timer = SwiftSignalKit.Timer(timeout: backgroundTaskSubmissionDelay, repeat: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.pendingBackgroundStoryProcessingTaskTimer = nil
|
||||
self.startBackgroundStoryProcessingTaskIfNeeded()
|
||||
}, queue: .mainQueue())
|
||||
self.pendingBackgroundStoryProcessingTaskTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
} else {
|
||||
self.pendingBackgroundStoryProcessingTaskTimer?.invalidate()
|
||||
self.pendingBackgroundStoryProcessingTaskTimer = nil
|
||||
|
||||
if let backgroundStoryProcessingTaskId = self.backgroundStoryProcessingTaskId {
|
||||
if !self.backgroundStoryProcessingTaskCancellationRequestedByApp {
|
||||
self.backgroundStoryProcessingTaskCancellationRequestedByApp = true
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: backgroundStoryProcessingTaskId)
|
||||
Logger.shared.log("Wakeup", "Requested story BG task cancellation by app: \(backgroundStoryProcessingTaskId)")
|
||||
}
|
||||
|
||||
|
||||
if !self.backgroundStoryProcessingTaskLaunched {
|
||||
self.backgroundStoryProcessingTaskId = nil
|
||||
self.backgroundStoryProcessingTaskProgressByKey = [:]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue