This commit is contained in:
Isaac 2025-12-06 01:28:06 +08:00
parent 1969b1b280
commit 1ee7268488
72 changed files with 1645 additions and 934 deletions

View file

@ -1,22 +0,0 @@
{
"permissions": {
"allow": [
"Bash(swift build)",
"Bash(.build/debug/makeproject:*)",
"Bash(mkdir:*)",
"Bash(find:*)",
"Bash(grep:*)",
"Skill(superpowers:brainstorming)",
"Bash(cat:*)",
"Bash(python3:*)",
"Skill(superpowers:writing-plans)",
"Skill(superpowers:subagent-driven-development)",
"Bash(test:*)",
"Bash(swift package:*)",
"Bash(.build/debug/MakeProject:*)",
"Bash(./build-system/MakeProject/.build/debug/MakeProject)",
"Bash(xcodebuild:*)"
],
"deny": []
}
}

3
.gitignore vendored
View file

@ -75,4 +75,5 @@ Telegram.LSP.json
**/.build/**
spm-files
xcode-files
.bsp/**
.bsp/**
/.claude/

View file

@ -46,6 +46,8 @@ class ProjectGenerator {
"CLANG_CXX_LANGUAGE_STANDARD": "c++17",
"CLANG_ENABLE_MODULES": "YES",
"CLANG_ENABLE_OBJC_ARC": "YES",
"CLANG_ENABLE_EXPLICIT_MODULES": "NO", // Disable explicit module builds for ObjC-Swift interop
"SWIFT_ENABLE_EXPLICIT_MODULES": "NO", // Disable explicit module builds for Swift
"ENABLE_STRICT_OBJC_MSGSEND": "YES",
"GCC_NO_COMMON_BLOCKS": "YES",
"IPHONEOS_DEPLOYMENT_TARGET": "13.0",

View file

@ -21,7 +21,9 @@ class SymlinkManager {
guard path.exists else { return }
do {
for item in try path.children() {
if item.lastComponent == ".build" { continue }
let name = item.lastComponent
// Skip build artifacts and xcodeproj bundles
if name == ".build" || name.hasSuffix(".xcodeproj") { continue }
previousFiles.insert(item)
if item.isDirectory && !item.isSymlink {
scanDirectory(item)

View file

@ -20,15 +20,48 @@ class TargetBuilder {
self.symlinkManager = symlinkManager
}
// Track which modules are header-only (have no linkable code)
private var headerOnlyModules: Set<String> = []
// Track which modules are static library collections (.a files)
private var staticLibraryModules: [String: [String]] = [:] // module name -> list of .a file paths
func isHeaderOnlyModule(_ name: String) -> Bool {
return headerOnlyModules.contains(name)
}
func isStaticLibraryModule(_ name: String) -> Bool {
return staticLibraryModules[name] != nil
}
func getStaticLibraries(for name: String) -> [String] {
return staticLibraryModules[name] ?? []
}
func buildTarget(for module: ModuleDefinition, allModules: [String: ModuleDefinition]) throws -> PBXNativeTarget? {
guard let moduleType = ModuleType(from: module) else {
print("Warning: Unknown module type \(module.type) for \(module.name)")
return nil
}
// Skip empty modules
let sourceFiles = module.sources.filter { !$0.hasSuffix(".a") }
if sourceFiles.isEmpty && moduleType != .xcframework {
// Check for static library modules (only .a files)
let staticLibs = module.sources.filter { $0.hasSuffix(".a") }
let nonStaticLibs = module.sources.filter { !$0.hasSuffix(".a") }
let isStaticLibOnly = !staticLibs.isEmpty && nonStaticLibs.allSatisfy { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") }
if isStaticLibOnly && moduleType != .xcframework {
// This is a static library module - track its .a files but don't create a framework
staticLibraryModules[module.name] = staticLibs
return nil // Don't create a target, just track the static libs
}
// Check if module is header-only (only header files, no real sources)
let sourceFiles = module.sources.filter { source in
!source.hasSuffix(".a") && !source.hasSuffix(".h") && !source.hasSuffix(".hpp")
}
let isHeaderOnly = sourceFiles.isEmpty && moduleType != .xcframework
// Skip modules with no sources at all
if module.sources.isEmpty && moduleType != .xcframework {
return nil
}
@ -36,7 +69,12 @@ class TargetBuilder {
case .xcframework:
return try buildXCFrameworkTarget(for: module)
case .swiftLibrary, .objcLibrary, .ccLibrary:
return try buildFrameworkTarget(for: module, moduleType: moduleType)
if isHeaderOnly {
headerOnlyModules.insert(module.name)
return try buildHeaderOnlyFrameworkTarget(for: module)
} else {
return try buildFrameworkTarget(for: module, moduleType: moduleType)
}
}
}
@ -61,7 +99,13 @@ class TargetBuilder {
// Create symlinks and file references
var sourceRefs: [PBXFileReference] = []
var headerRefs: [PBXFileReference] = []
var publicHeaderRefs: [PBXFileReference] = []
var seenPublicHeaderNames: Set<String> = [] // Track header filenames to avoid duplicates in headers build phase
// Determine public header detection:
// 1. If hdrs is provided, use those (explicit public headers)
// 2. Otherwise, headers in includes directories are public
let explicitPublicHeaders = Set(module.hdrs ?? [])
let allSourceFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? [])
@ -99,11 +143,33 @@ class TargetBuilder {
fileName: fileName
)
if source.hasSuffix(".h") || source.hasSuffix(".hpp") {
headerRefs.append(fileRef)
} else if !source.hasSuffix(".inc") {
let isHeader = source.hasSuffix(".h") || source.hasSuffix(".hpp")
// Determine if this is a public header:
// 1. Explicitly in hdrs array
// 2. Located in an includes directory
let isPublicHeader: Bool
if !isHeader {
isPublicHeader = false
} else if !explicitPublicHeaders.isEmpty {
// If hdrs is provided, only those are public
isPublicHeader = explicitPublicHeaders.contains(source)
} else {
// Headers in include directories are public (handles bazel-out paths)
isPublicHeader = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes)
}
if isPublicHeader {
// Skip duplicate header filenames to avoid "multiple commands produce" errors
if !seenPublicHeaderNames.contains(fileName) {
seenPublicHeaderNames.insert(fileName)
publicHeaderRefs.append(fileRef)
}
} else if !source.hasSuffix(".inc") && !isHeader {
// Source files (not headers)
sourceRefs.append(fileRef)
}
// Private headers are not added to any build phase
}
// Build phases
@ -120,17 +186,23 @@ class TargetBuilder {
pbxproj.add(object: sourcesBuildPhase)
buildPhases.append(sourcesBuildPhase)
// Headers build phase for ObjC/C++
// Generate modulemap for ObjC/C++ modules (SPM-style explicit headers)
var modulemapPath: Path? = nil
if moduleType == .objcLibrary || moduleType == .ccLibrary {
let headersBuildPhase = PBXHeadersBuildPhase(
files: headerRefs.map { ref in
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
pbxproj.add(object: buildFile)
return buildFile
}
)
pbxproj.add(object: headersBuildPhase)
buildPhases.append(headersBuildPhase)
modulemapPath = try generateModulemap(for: module)
// Headers build phase with public headers - needed for ObjC #import to work
if !publicHeaderRefs.isEmpty {
let headersBuildPhase = PBXHeadersBuildPhase(
files: publicHeaderRefs.map { ref in
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
pbxproj.add(object: buildFile)
return buildFile
}
)
pbxproj.add(object: headersBuildPhase)
buildPhases.append(headersBuildPhase)
}
}
// Frameworks build phase
@ -138,8 +210,8 @@ class TargetBuilder {
pbxproj.add(object: frameworksBuildPhase)
buildPhases.append(frameworksBuildPhase)
// Create target
let configList = createConfigurationList(for: module, isXCFramework: false)
// Create target with custom modulemap if generated
let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath)
let target = PBXNativeTarget(
name: module.name,
@ -155,6 +227,110 @@ class TargetBuilder {
return target
}
/// Build a framework target for header-only modules (just headers + modulemap, no sources)
private func buildHeaderOnlyFrameworkTarget(for module: ModuleDefinition) throws -> PBXNativeTarget {
// Create group for module
let moduleGroup = try getOrCreateGroup(for: module.path)
// Symlink header files and create file references
var publicHeaderRefs: [PBXFileReference] = []
var seenPublicHeaderNames: Set<String> = [] // Track header filenames to avoid duplicates
let allHeaders = module.sources.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") } + (module.hdrs ?? []) + (module.textualHdrs ?? [])
for source in allHeaders {
let sourcePath = Path(source)
let fileName = sourcePath.lastComponent
let relativeToModule = relativePathInModule(source: source, modulePath: module.path)
// Create parent group
let parentPath = (Path(module.path) + Path(relativeToModule).parent()).string
let parentGroup = try getOrCreateGroup(for: parentPath)
// Create symlink
let symlinkPath = outputDir + module.path + relativeToModule
try symlinkManager.createDirectory(symlinkPath.parent())
try symlinkManager.createSymlink(from: Path(source), to: symlinkPath)
// Create file reference
let fileType = lastKnownFileType(for: fileName)
let fileRef = PBXFileReference(
sourceTree: .group,
name: fileName,
lastKnownFileType: fileType,
path: fileName
)
pbxproj.add(object: fileRef)
parentGroup.children.append(fileRef)
// Check if it's a public header (in includes directories)
// Use helper that properly handles bazel-out paths
let isPublic = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes)
if isPublic {
// Skip duplicate header filenames to avoid "multiple commands produce" errors
if !seenPublicHeaderNames.contains(fileName) {
seenPublicHeaderNames.insert(fileName)
publicHeaderRefs.append(fileRef)
}
}
}
// Generate modulemap for this header-only module
let modulemapPath = try generateModulemap(for: module)
// Create build phases
var buildPhases: [PBXBuildPhase] = []
// Headers build phase with public headers - needed for ObjC #import to work
if !publicHeaderRefs.isEmpty {
let headersBuildPhase = PBXHeadersBuildPhase(
files: publicHeaderRefs.map { ref in
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
pbxproj.add(object: buildFile)
return buildFile
}
)
pbxproj.add(object: headersBuildPhase)
buildPhases.append(headersBuildPhase)
}
// Empty frameworks phase (needed for Xcode, but won't link anything)
let frameworksBuildPhase = PBXFrameworksBuildPhase(files: [])
pbxproj.add(object: frameworksBuildPhase)
buildPhases.append(frameworksBuildPhase)
// Create target with custom modulemap
let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath)
let target = PBXNativeTarget(
name: module.name,
buildConfigurationList: configList,
buildPhases: buildPhases,
productName: module.name,
productType: .framework
)
pbxproj.add(object: target)
project.targets.append(target)
targetsByName[module.name] = target
return target
}
/// Collect all transitive dependencies for a module
private func collectAllDependencies(for moduleName: String, modules: [String: ModuleDefinition], visited: inout Set<String>) -> Set<String> {
guard !visited.contains(moduleName) else { return [] }
visited.insert(moduleName)
guard let module = modules[moduleName], let deps = module.deps else {
return []
}
var allDeps = Set(deps)
for depName in deps {
allDeps.formUnion(collectAllDependencies(for: depName, modules: modules, visited: &visited))
}
return allDeps
}
func wireUpDependencies(modules: [String: ModuleDefinition]) throws {
for (name, module) in modules {
guard let target = targetsByName[name],
@ -165,13 +341,88 @@ class TargetBuilder {
continue
}
for depName in deps {
guard let depTarget = targetsByName[depName] else { continue }
// Track all frameworks we've added to avoid duplicates
var linkedFrameworks: Set<String> = []
// Add target dependency
let dependency = PBXTargetDependency(target: depTarget)
pbxproj.add(object: dependency)
target.dependencies.append(dependency)
// Add target dependency (only for direct deps)
for depName in deps {
if let depTarget = targetsByName[depName] {
let dependency = PBXTargetDependency(target: depTarget)
pbxproj.add(object: dependency)
target.dependencies.append(dependency)
}
}
// Collect all transitive dependencies to link
var visited = Set<String>()
let allDeps = collectAllDependencies(for: name, modules: modules, visited: &visited)
// Collect header paths from ALL dependencies (direct and transitive)
var depHeaderPaths: [String] = []
for depName in allDeps {
if let depModule = modules[depName] {
depHeaderPaths.append(contentsOf: exportedHeaderPaths(for: depModule))
}
}
// Link all dependencies (direct and transitive) that have targets
for depName in allDeps {
// Skip if already linked
guard !linkedFrameworks.contains(depName) else { continue }
// Check if this is a static library module - link .a files directly
if isStaticLibraryModule(depName) {
// Link static libraries directly
for libPath in getStaticLibraries(for: depName) {
let libName = Path(libPath).lastComponent
// Use absolute path to the static library in bazel-out
let absolutePath = "$(SRCROOT)/../\(libPath)"
let libRef = PBXFileReference(
sourceTree: .group,
name: libName,
lastKnownFileType: "archive.ar",
path: absolutePath
)
pbxproj.add(object: libRef)
let buildFile = PBXBuildFile(file: libRef)
pbxproj.add(object: buildFile)
frameworksPhase.files?.append(buildFile)
}
linkedFrameworks.insert(depName)
continue
}
// Skip header-only modules (no framework to link)
if isHeaderOnlyModule(depName) { continue }
// Regular framework dependency
guard targetsByName[depName] != nil else { continue }
linkedFrameworks.insert(depName)
let frameworkRef = PBXFileReference(
sourceTree: .buildProductsDir,
name: "\(depName).framework",
lastKnownFileType: "wrapper.framework",
path: "\(depName).framework"
)
pbxproj.add(object: frameworkRef)
let buildFile = PBXBuildFile(file: frameworkRef)
pbxproj.add(object: buildFile)
frameworksPhase.files?.append(buildFile)
}
// Update build configurations with dependency paths
if !depHeaderPaths.isEmpty || !deps.isEmpty {
for config in target.buildConfigurationList?.buildConfigurations ?? [] {
// Add header search paths
if !depHeaderPaths.isEmpty {
let existing = config.buildSettings["HEADER_SEARCH_PATHS"] as? String ?? "$(inherited)"
config.buildSettings["HEADER_SEARCH_PATHS"] = existing + " " + depHeaderPaths.joined(separator: " ")
}
// Ensure framework and module search paths include built products
config.buildSettings["FRAMEWORK_SEARCH_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)"
config.buildSettings["SWIFT_INCLUDE_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)"
}
}
// Add SDK frameworks
@ -192,13 +443,32 @@ class TargetBuilder {
}
}
/// Returns the header search paths that this module exports to its dependents
private func exportedHeaderPaths(for module: ModuleDefinition) -> [String] {
var paths: [String] = []
if let includes = module.includes, !includes.isEmpty {
for inc in includes {
if inc == "." {
paths.append("$(SRCROOT)/\(module.path)")
} else {
paths.append("$(SRCROOT)/\(module.path)/\(inc)")
}
}
} else {
// No includes specified, export module's own path
paths.append("$(SRCROOT)/\(module.path)")
}
return paths
}
func getTarget(named name: String) -> PBXNativeTarget? {
return targetsByName[name]
}
private func createConfigurationList(for module: ModuleDefinition, isXCFramework: Bool) -> XCConfigurationList {
let debugSettings = createBuildSettings(for: module, isDebug: true, isXCFramework: isXCFramework)
let releaseSettings = createBuildSettings(for: module, isDebug: false, isXCFramework: isXCFramework)
private func createConfigurationList(for module: ModuleDefinition, isXCFramework: Bool, modulemapPath: Path? = nil) -> XCConfigurationList {
let debugSettings = createBuildSettings(for: module, isDebug: true, isXCFramework: isXCFramework, modulemapPath: modulemapPath)
let releaseSettings = createBuildSettings(for: module, isDebug: false, isXCFramework: isXCFramework, modulemapPath: modulemapPath)
let debugConfig = XCBuildConfiguration(name: "Debug", buildSettings: debugSettings)
let releaseConfig = XCBuildConfiguration(name: "Release", buildSettings: releaseSettings)
@ -215,7 +485,7 @@ class TargetBuilder {
return configList
}
private func createBuildSettings(for module: ModuleDefinition, isDebug: Bool, isXCFramework: Bool) -> BuildSettings {
private func createBuildSettings(for module: ModuleDefinition, isDebug: Bool, isXCFramework: Bool, modulemapPath: Path? = nil) -> BuildSettings {
var settings: BuildSettings = [
"PRODUCT_NAME": "$(TARGET_NAME)",
"PRODUCT_BUNDLE_IDENTIFIER": "org.telegram.\(module.name)",
@ -229,6 +499,7 @@ class TargetBuilder {
// Swift settings
if moduleType == .swiftLibrary {
settings["SWIFT_VERSION"] = "5.0"
settings["DEFINES_MODULE"] = "YES"
if let copts = module.copts, !copts.isEmpty {
let filtered = copts.filter { !$0.hasPrefix("-warnings") }
@ -244,12 +515,14 @@ class TargetBuilder {
// C/ObjC settings
if moduleType == .objcLibrary || moduleType == .ccLibrary {
// Always suppress deprecated warnings (e.g., OSSpinLock) and don't treat warnings as errors
var cflags = ["-Wno-deprecated-declarations"]
if let copts = module.copts, !copts.isEmpty {
let filtered = copts.filter { !$0.hasPrefix("-warnings") && !$0.hasPrefix("-W") }
if !filtered.isEmpty {
settings["OTHER_CFLAGS"] = "$(inherited) " + filtered.joined(separator: " ")
}
cflags.append(contentsOf: filtered)
}
settings["OTHER_CFLAGS"] = "$(inherited) " + cflags.joined(separator: " ")
settings["GCC_TREAT_WARNINGS_AS_ERRORS"] = "NO"
if let cxxopts = module.cxxopts, !cxxopts.isEmpty {
let filtered = cxxopts.filter { !$0.hasPrefix("-std=") }
@ -262,15 +535,22 @@ class TargetBuilder {
settings["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) " + defines.joined(separator: " ")
}
if let includes = module.includes, !includes.isEmpty {
let paths = includes.map { inc -> String in
if inc == "." {
return "$(SRCROOT)/\(module.path)"
}
return "$(SRCROOT)/\(module.path)/\(inc)"
// Always include module's own path for header search
var headerPaths = ["$(SRCROOT)/\(module.path)"]
if let includes = module.includes {
for inc in includes where inc != "." {
headerPaths.append("$(SRCROOT)/\(module.path)/\(inc)")
}
settings["HEADER_SEARCH_PATHS"] = "$(inherited) " + paths.joined(separator: " ")
}
settings["HEADER_SEARCH_PATHS"] = "$(inherited) " + headerPaths.joined(separator: " ")
// Use custom modulemap if provided (SPM-style)
if let modmap = modulemapPath {
// Get relative path from SRCROOT
let relativePath = modmap.string.replacingOccurrences(of: outputDir.string + "/", with: "")
settings["MODULEMAP_FILE"] = "$(SRCROOT)/\(relativePath)"
}
settings["DEFINES_MODULE"] = "YES"
}
return settings
@ -352,4 +632,110 @@ class TargetBuilder {
default: return "text"
}
}
/// Extract the relative path within a module from a source path
/// Handles both regular paths (submodules/Foo/...) and bazel-out paths (bazel-out/.../bin/submodules/Foo/...)
private func relativePathInModule(source: String, modulePath: String) -> String {
if source.hasPrefix(modulePath + "/") {
return String(source.dropFirst(modulePath.count + 1))
} else if source.hasPrefix("bazel-out/") {
// Generated file - extract path after the module path portion
if let range = source.range(of: modulePath + "/") {
return String(source[range.upperBound...])
} else {
return Path(source).lastComponent
}
} else {
return Path(source).lastComponent
}
}
/// Check if a source file is in one of the includes directories
private func isInIncludesDirectory(source: String, modulePath: String, includes: [String]?) -> Bool {
guard let includes = includes, !includes.isEmpty else {
return false
}
let relative = relativePathInModule(source: source, modulePath: modulePath)
return includes.contains { inc in
if inc == "." {
return true // All files in module are public
} else {
return relative.hasPrefix(inc + "/") || relative == inc
}
}
}
/// Generates an explicit module.modulemap for ObjC/C++ modules (SPM-style)
/// Returns the path to the modulemap, or nil if no public headers
func generateModulemap(for module: ModuleDefinition) throws -> Path? {
let moduleType = ModuleType(from: module)
guard moduleType == .objcLibrary || moduleType == .ccLibrary else {
return nil
}
// Determine public header prefix from includes
let publicHeaderPrefix: String
if let includes = module.includes, !includes.isEmpty {
let firstInclude = includes[0]
publicHeaderPrefix = firstInclude == "." ? "" : firstInclude
} else {
publicHeaderPrefix = ""
}
// Determine public headers
let explicitPublicHeaders = Set(module.hdrs ?? [])
let allFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? [])
let allHeaders = allFiles.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") }
// Determine which headers are public
let publicHeaders: [String]
if !explicitPublicHeaders.isEmpty {
publicHeaders = allHeaders.filter { explicitPublicHeaders.contains($0) }
} else if module.includes != nil && !module.includes!.isEmpty {
// Use helper that properly handles bazel-out paths
publicHeaders = allHeaders.filter { header in
isInIncludesDirectory(source: header, modulePath: module.path, includes: module.includes)
}
} else {
publicHeaders = []
}
guard !publicHeaders.isEmpty else {
return nil
}
// Generate explicit modulemap content (SPM-style)
var content = "// module.modulemap for \(module.name)\n"
content += "// Auto-generated - do not edit\n\n"
content += "module \(module.name) {\n"
for header in publicHeaders {
// Calculate the symlinked path - matches the symlink creation in buildFrameworkTarget
// The symlink is at: outputDir + module.path + relativeToModule
let relativeToModule = relativePathInModule(source: header, modulePath: module.path)
let symlinkPath = outputDir + module.path + relativeToModule
content += " header \"\(symlinkPath.string)\"\n"
}
content += " export *\n"
content += "}\n"
// Write modulemap to the public headers directory
let modulemapDir: Path
if !publicHeaderPrefix.isEmpty {
modulemapDir = outputDir + module.path + publicHeaderPrefix
} else {
modulemapDir = outputDir + module.path
}
let modulemapPath = modulemapDir + "module.modulemap"
try modulemapDir.mkpath()
try modulemapPath.write(content)
// Track this file so it doesn't get cleaned up
symlinkManager.markFile(modulemapPath)
return modulemapPath
}
}

View file

@ -429,12 +429,6 @@ public final class BrowserBookmarksScreen: ViewController {
searchContentNode.updateListVisibleContentOffset(offset)
}
}
//
// self.node.historyNode.didEndScrolling = { [weak self] _ in
// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
// let _ = fixNavigationSearchableListNodeScrolling(strongSelf.node.historyNode, searchNode: searchContentNode)
// }
// }
self.displayNodeDidLoad()
}

View file

@ -102,7 +102,7 @@ public final class CallListController: TelegramBaseController {
self.segmentedTitleView = ItemListControllerSegmentedTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], selectedIndex: 0)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .none, locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), mediaAccessoryPanelVisibility: .none, locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
self.tabBarItemContextActionType = .always
@ -205,7 +205,7 @@ public final class CallListController: TelegramBaseController {
}
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate)
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate)
if self.isNodeLoaded {
self.controllerNode.updateThemeAndStrings(presentationData: self.presentationData)

View file

@ -125,6 +125,7 @@ swift_library(
"//submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent",
"//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent",
"//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode",
"//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent",
],
visibility = [
"//visibility:public",

View file

@ -453,7 +453,7 @@ final class ChatListContainerItemNode: ASDisplayNode {
let edgeEffectHeight: CGFloat = insets.bottom
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition))
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectFrame.height, 40.0), transition: ComponentTransition(transition))
transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0)
}

View file

@ -224,7 +224,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private var fullScreenEffectView: RippleEffectView?
private let globalControlPanelsContext: GlobalControlPanelsContext
let globalControlPanelsContext: GlobalControlPanelsContext
private(set) var globalControlPanelsContextState: GlobalControlPanelsContext.State?
private var globalControlPanelsContextStateDisposable: Disposable?
@ -249,9 +249,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.animationRenderer = context.animationRenderer
let groupCallPanelSource: GroupCallPanelSource
var chatListNotices = false
switch self.location {
case .chatList:
case let .chatList(groupId):
groupCallPanelSource = .all
if case .root = groupId {
chatListNotices = true
}
case let .forum(peerId):
groupCallPanelSource = .peer(peerId)
case .savedMessagesChats:
@ -266,7 +270,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
context: context,
mediaPlayback: true,
liveLocationMode: .all,
groupCalls: groupCallPanelSource
groupCalls: groupCallPanelSource,
chatListNotices: chatListNotices
)
super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .none, locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
@ -4809,6 +4814,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
#if DEBUG
if "".isEmpty {
(self.navigationController as? NavigationController)?.pushViewController(oldChannelsController(context: self.context, intent: .join, completed: { value in
}))
return
}
#endif
guard let navigationController = self.navigationController as? NavigationController else {
return
}

View file

@ -28,6 +28,7 @@ import ChatListTabsComponent
import PremiumUI
import MediaPlaybackHeaderPanelComponent
import LiveLocationHeaderPanelComponent
import ChatListHeaderNoticeComponent
public enum ChatListContainerNodeFilter: Equatable {
case all
@ -1361,10 +1362,70 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
let headerContent = self.controller?.updateHeaderContent()
var panels: [HeaderPanelContainerComponent.Panel] = []
if let chatListNotice = self.controller?.globalControlPanelsContextState?.chatListNotice {
panels.append(HeaderPanelContainerComponent.Panel(
key: "chatListNotice",
orderIndex: 0,
component: AnyComponent(ChatListHeaderNoticeComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
data: chatListNotice,
activateAction: { [weak self] notice in
guard let self else {
return
}
switch notice {
case .clearStorage:
self.effectiveContainerNode.currentItemNode.interaction?.openStorageManagement()
case .setupPassword:
self.effectiveContainerNode.currentItemNode.interaction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumIntro()
case .xmasPremiumGift:
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift([], nil)
case .premiumGrace:
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumManagement()
case .setupBirthday:
self.effectiveContainerNode.currentItemNode.interaction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers, birthdays):
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
self.effectiveContainerNode.currentItemNode.interaction?.openStarsTopup(amount.value)
case .setupPhoto:
self.effectiveContainerNode.currentItemNode.interaction?.openPhotoSetup()
case .accountFreeze:
self.effectiveContainerNode.currentItemNode.interaction?.openAccountFreezeInfo()
case let .link(_, url, _, _):
self.effectiveContainerNode.currentItemNode.interaction?.openUrl(url)
}
},
dismissAction: { [weak self] notice in
guard let self, let controller = self.controller else {
return
}
controller.globalControlPanelsContext.dismissChatListNotice(parentController: controller, notice: notice)
},
selectAction: { [weak self] notice, isPositive in
guard let self else {
return
}
switch notice {
case let .reviewLogin(newSessionReview, _):
self.effectiveContainerNode.currentItemNode.interaction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
}
)))
)
}
if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback {
panels.append(HeaderPanelContainerComponent.Panel(
key: "media",
orderIndex: 0,
orderIndex: 1,
component: AnyComponent(MediaPlaybackHeaderPanelComponent(
context: self.context,
theme: self.presentationData.theme,
@ -1379,7 +1440,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
if let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation {
panels.append(HeaderPanelContainerComponent.Panel(
key: "liveLocation",
orderIndex: 1,
orderIndex: 2,
component: AnyComponent(LiveLocationHeaderPanelComponent(
context: self.context,
theme: self.presentationData.theme,

View file

@ -37,6 +37,9 @@ import PremiumUI
import AvatarNode
import StoryContainerScreen
import ChatListSearchFiltersContainerNode
import EdgeEffect
import ComponentFlow
import ComponentDisplayAdapters
private enum ChatListTokenId: Int32 {
case archive
@ -108,6 +111,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var dismissSearch: (() -> Void)?
var openAdInfo: ((ASDisplayNode, AdPeer) -> Void)?
private let edgeEffectView: EdgeEffectView
private let filterContainerNode: ChatListSearchFiltersContainerNode
private let paneContainerNode: ChatListSearchPaneContainerNode
private var selectionPanelNode: ChatListSearchMessageSelectionPanelNode?
@ -182,6 +187,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.present = present
self.presentInGlobalOverlay = presentInGlobalOverlay
self.edgeEffectView = EdgeEffectView()
self.filterContainerNode = ChatListSearchFiltersContainerNode()
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, requestPeerType: self.requestPeerType, location: location, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController, parentController: parentController())
self.paneContainerNode.clipsToBounds = true
@ -321,6 +328,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
parentController()?.view.endEditing(true)
}
self.view.addSubview(self.edgeEffectView)
self.addSubnode(self.filterContainerNode)
self.filterContainerNode.filterPressed = { [weak self] filter in
guard let strongSelf = self else {
@ -921,7 +930,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers)
}
self.selectionPanelNode = selectionPanelNode
self.addSubnode(selectionPanelNode)
self.insertSubnode(selectionPanelNode, aboveSubnode: self.filterContainerNode)
}
selectionPanelNode.selectedMessages = selectedMessageIds
@ -942,7 +951,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
}
transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset, width: layout.size.width, height: layout.size.height - topInset))
transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height))
var bottomInset = layout.intrinsicInsets.bottom
if let inputHeight = layout.inputHeight {
@ -964,8 +973,16 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} else {
availablePanes = isForum ? [.topics] : [.chats]
}
bottomInset += 44.0
let edgeEffectHeight: CGFloat = bottomInset
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: ComponentTransition(transition))
transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0)
self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: bottomInset, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition)
self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height), sideInset: layout.safeInsets.left, topInset: topInset, bottomInset: bottomInset, visibleHeight: layout.size.height, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition)
}
private var currentMessages: ([EnginePeer.Id: EnginePeer], [EngineMessage.Id: EngineMessage]) {

View file

@ -1696,7 +1696,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private var emptyRecentAnimationNode: AnimatedStickerNode?
private var emptyRecentAnimationSize = CGSize()
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
private let ready = Promise<Bool>()
private var didSetReady: Bool = false
@ -3495,7 +3495,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.interaction.openStories?(id, sourceNode.avatarNode)
}
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@ -4640,8 +4639,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.playlistStateAndType = nil
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
}
}
self.playlistLocation = playlistStateAndType?.1.playlistLocation
@ -4758,10 +4757,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
let hadValidLayout = self.currentParams != nil
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.topInset != topInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData)
var topPanelHeight: CGFloat = 0.0
if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType {
@ -5035,9 +5034,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight)))
let topInset: CGFloat = topPanelHeight
let topInset: CGFloat = topInset + topPanelHeight
let overflowInset: CGFloat = 20.0
let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
let insets = UIEdgeInsets(top: topInset + topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height))
self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, key: !(self.searchQueryValue?.isEmpty ?? true) && self.key == .media ? .chats : self.key, hasSelection: self.selectedMessages != nil, transition: transition)
@ -5480,8 +5479,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
strongSelf.emptyResultsButtonSubtitleText = nil
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
}
if strongSelf.key == .downloads {
@ -5783,7 +5782,6 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {

View file

@ -15,7 +15,7 @@ protocol ChatListSearchPaneNode: ASDisplayNode {
var isReady: Signal<Bool, NoError> { get }
var isCurrent: Bool { get set }
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
func scrollToTop() -> Bool
func cancelPreviewGestures()
func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
@ -32,21 +32,21 @@ final class ChatListSearchPaneWrapper {
let key: ChatListSearchPaneKey
let node: ChatListSearchPaneNode
var isAnimatingOut: Bool = false
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, PresentationData)?
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, CGFloat, PresentationData)?
init(key: ChatListSearchPaneKey, node: ChatListSearchPaneNode) {
self.key = key
self.node = node
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
if let (currentSize, currentSideInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
if let (currentSize, currentSideInset, currentTopInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
if currentSize == size && currentSideInset == sideInset && currentTopInset == topInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
return
}
}
self.appliedParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
self.node.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
self.appliedParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData)
self.node.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
}
}
@ -190,7 +190,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
var isAdjacentLoadingEnabled = false
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])?
private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])?
private(set) var currentPaneKey: ChatListSearchPaneKey?
var pendingSwitchToPaneKey: ChatListSearchPaneKey?
@ -251,8 +251,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
if self.currentPanes[key] != nil {
self.currentPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
}
if case .apps = key {
@ -261,8 +261,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
} else if self.pendingSwitchToPaneKey != key {
self.pendingSwitchToPaneKey = key
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
}
if case .apps = key {
@ -275,7 +275,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
super.didLoad()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let strongSelf = self, let (_, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
guard let strongSelf = self, let (_, _, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
return []
}
if index == 0 {
@ -321,7 +321,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
cancelContextGestures(view: self.view)
case .changed:
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
self.isAdjacentLoadingEnabled = true
let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / size.width
@ -332,10 +332,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
transitionFraction = max(0.0, transitionFraction)
}
self.transitionFraction = transitionFraction
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate)
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate)
}
case .cancelled, .ended:
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
var directionIsToRight: Bool?
@ -364,7 +364,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
}
}
self.transitionFraction = 0.0
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring))
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring))
}
default:
break
@ -396,7 +396,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
}
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) {
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) {
let previousAvailablePanes = self.currentAvailablePanes ?? []
self.currentAvailablePanes = availablePanes
@ -430,7 +430,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
currentIndex = nil
}
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes)
self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes)
switch self.location {
case .forum, .savedMessagesChats:
@ -489,12 +489,12 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
guard let strongSelf = self else {
return
}
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams {
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams {
var transition: ContainedViewLayoutTransition = .immediate
if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil {
transition = .animated(duration: 0.4, curve: .spring)
}
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition)
strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition)
}
}
if leftScope {
@ -504,14 +504,14 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
)
self.pendingPanes[key] = pane
pane.pane.node.frame = paneFrame
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
leftScope = true
}
}
for (key, pane) in self.pendingPanes {
pane.pane.node.frame = paneFrame
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
if pane.isReady {
self.pendingPanes.removeValue(forKey: key)
@ -587,7 +587,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
paneCompletion()
})
}
pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
pane.node.isCurrent = key == self.currentPaneKey
if paneWasAdded && key == self.currentPaneKey {
pane.node.didBecomeFocused()
@ -598,7 +598,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
for (_, pane) in self.pendingPanes {
let paneTransition: ContainedViewLayoutTransition = .immediate
paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
}
if !self.didSetIsReady {
if let currentPaneKey = self.currentPaneKey, let currentPane = self.currentPanes[currentPaneKey] {

View file

@ -157,7 +157,6 @@ public final class ChatListShimmerNode: ASDisplayNode {
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {

View file

@ -23,6 +23,7 @@ import ChatListHeaderComponent
import UndoUI
import NewSessionInfoScreen
import PresentationDataUtils
import GlobalControlPanelsContext
public enum ChatListNodeMode {
case chatList(appendContacts: Bool)
@ -110,7 +111,6 @@ public final class ChatListNodeInteraction {
let hideChatFolderUpdates: () -> Void
let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
let openStarsTopup: (Int64?) -> Void
let dismissNotice: (ChatListNotice) -> Void
let editPeer: (ChatListItem) -> Void
let openWebApp: (TelegramUser) -> Void
let openPhotoSetup: () -> Void
@ -171,7 +171,6 @@ public final class ChatListNodeInteraction {
hideChatFolderUpdates: @escaping () -> Void,
openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void,
openStarsTopup: @escaping (Int64?) -> Void,
dismissNotice: @escaping (ChatListNotice) -> Void,
editPeer: @escaping (ChatListItem) -> Void,
openWebApp: @escaping (TelegramUser) -> Void,
openPhotoSetup: @escaping () -> Void,
@ -219,7 +218,6 @@ public final class ChatListNodeInteraction {
self.hideChatFolderUpdates = hideChatFolderUpdates
self.openStories = openStories
self.openStarsTopup = openStarsTopup
self.dismissNotice = dismissNotice
self.editPeer = editPeer
self.openWebApp = openWebApp
self.openPhotoSetup = openPhotoSetup
@ -751,47 +749,6 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
case .activate:
switch notice {
case .clearStorage:
nodeInteraction?.openStorageManagement()
case .setupPassword:
nodeInteraction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
nodeInteraction?.openPremiumIntro()
case .xmasPremiumGift:
nodeInteraction?.openPremiumGift([], nil)
case .premiumGrace:
nodeInteraction?.openPremiumManagement()
case .setupBirthday:
nodeInteraction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers, birthdays):
nodeInteraction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
case let .link(_, url, _, _):
nodeInteraction?.openUrl(url)
}
case .hide:
nodeInteraction?.dismissNotice(notice)
case let .buttonChoice(isPositive):
switch notice {
case let .reviewLogin(newSessionReview, _):
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
}
}), directionHint: entry.directionHint)
}
}
}
@ -1101,47 +1058,6 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
case .activate:
switch notice {
case .clearStorage:
nodeInteraction?.openStorageManagement()
case .setupPassword:
nodeInteraction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
nodeInteraction?.openPremiumIntro()
case .xmasPremiumGift:
nodeInteraction?.openPremiumGift([], nil)
case .premiumGrace:
nodeInteraction?.openPremiumManagement()
case .setupBirthday:
nodeInteraction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers, birthdays):
nodeInteraction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
case let .link(_, url, _, _):
nodeInteraction?.openUrl(url)
}
case .hide:
nodeInteraction?.dismissNotice(notice)
case let .buttonChoice(isPositive):
switch notice {
case let .reviewLogin(newSessionReview, _):
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
default:
break
}
}
}), directionHint: entry.directionHint)
case .HeaderEntry:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
@ -1282,7 +1198,7 @@ public final class ChatListNode: ListView {
return []
}
}
private var interaction: ChatListNodeInteraction?
public private(set) var interaction: ChatListNodeInteraction?
private var dequeuedInitialTransitionOnLayout = false
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
@ -1383,7 +1299,6 @@ public final class ChatListNode: ListView {
private let autoSetReady: Bool
public let isMainTab = ValuePromise<Bool>(false, ignoreRepeated: true)
private let suggestedChatListNotice = Promise<ChatListNotice?>(nil)
public var synchronousDrawingWhenNotAnimated: Bool = false
@ -1868,38 +1783,6 @@ public final class ChatListNode: ListView {
return
}
self.openStarsTopup?(amount)
}, dismissNotice: { [weak self] notice in
guard let self else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
switch notice {
case .xmasPremiumGift:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone()
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}))
case .setupBirthday:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone()
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}))
case .birthdayPremiumGift:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone()
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}))
case .premiumGrace:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone()
case .setupPhoto:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone()
case .starsSubscriptionLowBalance:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone()
case let .link(id, _, _, _):
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone()
default:
break
}
}, editPeer: { _ in
}, openWebApp: { [weak self] user in
guard let self else {
@ -1992,172 +1875,12 @@ public final class ChatListNode: ListView {
} else {
displayArchiveIntro = .single(false)
}
let starsSubscriptionsContextPromise = Promise<StarsSubscriptionsContext?>(nil)
self.updateIsMainTabDisposable = (self.isMainTab.get()
|> deliverOnMainQueue).startStrict(next: { [weak self] isMainTab in
guard let self else {
return
|> deliverOnMainQueue).startStrict(next: { isMainTab in
if isMainTab {
let _ = context.engine.privacy.cleanupSessionReviews().startStandalone()
}
guard case .chatList(groupId: .root) = location, isMainTab else {
self.suggestedChatListNotice.set(.single(nil))
return
}
let _ = context.engine.privacy.cleanupSessionReviews().startStandalone()
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> distinctUntilChanged
|> map { appConfiguration -> AccountFreezeConfiguration in
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
})
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
context.engine.notices.getServerProvidedSuggestions(),
context.engine.notices.getServerDismissedSuggestions(),
twoStepData,
newSessionReviews(postbox: context.account.postbox),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
),
context.account.stateManager.contactBirthdays,
starsSubscriptionsContextPromise.get(),
accountFreezeConfiguration
)
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first {
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
}
if suggestions.contains(.setupPassword), let configuration {
var notSet = false
switch configuration {
case let .notSet(pendingEmail):
if pendingEmail == nil {
notSet = true
}
case .set:
break
}
if notSet {
return .single(.setupPassword)
}
}
let today = Calendar(identifier: .gregorian).component(.day, from: Date())
var todayBirthdayPeerIds: [EnginePeer.Id] = []
for (peerId, birthday) in birthdays {
if birthday.day == today {
todayBirthdayPeerIds.append(peerId)
}
}
todayBirthdayPeerIds.sort { lhs, rhs in
return lhs < rhs
}
if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) {
todayBirthdayPeerIds = []
}
if let _ = accountFreezeConfiguration.freezeUntilDate {
return .single(.accountFreeze)
} else if suggestions.contains(.starsSubscriptionLowBalance) {
if let starsSubscriptionsContext {
return starsSubscriptionsContext.state
|> map { state in
if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty {
return .starsSubscriptionLowBalance(
amount: state.balance,
peers: state.subscriptions.map { $0.peer }
)
} else {
return nil
}
}
} else {
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
return .single(nil)
}
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
return .single(.setupPhoto(accountPeer))
} else if suggestions.contains(.gracePremium) {
return .single(.premiumGrace)
} else if suggestions.contains(.xmasPremiumGift) {
return .single(.xmasPremiumGift)
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
return inAppPurchaseManager.availableProducts
|> map { products -> ChatListNotice? in
if products.count > 1 {
let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
}
for product in products {
if product.id.hasSuffix(".annual") {
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0)
let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0)
if discount > 0 {
if suggestions.contains(.restorePremium) {
return .premiumRestore(discount: discount)
} else if suggestions.contains(.annualPremium) {
return .premiumAnnualDiscount(discount: discount)
} else if suggestions.contains(.upgradePremium) {
return .premiumUpgrade(discount: discount)
}
}
break
}
}
return nil
} else {
if !GlobalExperimentalSettings.isAppStoreBuild {
if suggestions.contains(.restorePremium) {
return .premiumRestore(discount: 0)
} else if suggestions.contains(.annualPremium) {
return .premiumAnnualDiscount(discount: 0)
} else if suggestions.contains(.upgradePremium) {
return .premiumUpgrade(discount: 0)
}
}
return nil
}
}
} else if !todayBirthdayPeerIds.isEmpty {
return context.engine.data.get(
EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> map { result -> ChatListNotice? in
var todayBirthdayPeers: [EnginePeer] = []
for (peerId, _) in birthdays {
if let maybePeer = result[peerId], let peer = maybePeer {
todayBirthdayPeers.append(peer)
}
}
return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays)
}
} else if suggestions.contains(.setupBirthday) && birthday == nil {
return .single(.setupBirthday)
} else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) {
return .single(.link(id: id, url: url, title: title, subtitle: subtitle))
} else {
return .single(nil)
}
}
|> distinctUntilChanged
self.suggestedChatListNotice.set(suggestedChatListNoticeSignal)
}).strict()
let storageInfo: Signal<Double?, NoError>
@ -2346,7 +2069,6 @@ public final class ChatListNode: ListView {
hideArchivedFolderByDefault,
displayArchiveIntro,
storageInfo,
suggestedChatListNotice.get(),
savedMessagesPeer,
chatListViewUpdate,
self.statePromise.get(),
@ -2354,23 +2076,14 @@ public final class ChatListNode: ListView {
chatListFilters,
accountIsPremium
)
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
let notice: ChatListNotice?
if let suggestedChatListNotice {
notice = suggestedChatListNotice
} else if let storageInfo {
notice = .clearStorage(sizeFraction: storageInfo)
} else {
notice = nil
}
let innerIsMainTab = location == .chatList(groupId: .root) && chatListFilter == nil
let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab)
let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab)
var isEmpty = true
var entries = rawEntries.filter { entry in
switch entry {
@ -2697,7 +2410,6 @@ public final class ChatListNode: ListView {
var didIncludeRemovingPeerId = false
var didIncludeHiddenByDefaultArchive = false
var didIncludeHiddenThread = false
var didIncludeNotice = false
if let previous = previousView {
for entry in previous.filteredEntries {
if case let .PeerEntry(peerEntry) = entry {
@ -2724,15 +2436,12 @@ public final class ChatListNode: ListView {
}
} else if case let .GroupReferenceEntry(groupReferenceEntry) = entry {
didIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault
} else if case .Notice = entry {
didIncludeNotice = true
}
}
}
var doesIncludeRemovingPeerId = false
var doesIncludeArchive = false
var doesIncludeHiddenByDefaultArchive = false
var doesIncludeNotice = false
var doesIncludeHiddenThread = false
for entry in processedView.filteredEntries {
@ -2761,8 +2470,6 @@ public final class ChatListNode: ListView {
} else if case let .GroupReferenceEntry(groupReferenceEntry) = entry {
doesIncludeArchive = true
doesIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault
} else if case .Notice = entry {
doesIncludeNotice = true
}
}
if previousPinnedChats != updatedPinnedChats || previousPinnedThreads != updatedPinnedThreads {
@ -2789,9 +2496,6 @@ public final class ChatListNode: ListView {
if didIncludeHiddenThread != doesIncludeHiddenThread {
disableAnimations = false
}
if didIncludeNotice != doesIncludeNotice {
disableAnimations = false
}
}
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
@ -3658,7 +3362,7 @@ public final class ChatListNode: ListView {
} else {
break loop
}
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory:
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .HeaderEntry, .AdditionalCategory:
break
}
}

View file

@ -79,23 +79,6 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
case psa(type: String, message: String?)
}
public enum ChatListNotice: Equatable {
case clearStorage(sizeFraction: Double)
case setupPassword
case premiumUpgrade(discount: Int32)
case premiumAnnualDiscount(discount: Int32)
case premiumRestore(discount: Int32)
case xmasPremiumGift
case setupBirthday
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
case setupPhoto(EnginePeer)
case accountFreeze
case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text)
}
enum ChatListNodeEntry: Comparable, Identifiable {
struct PeerEntryData: Equatable {
var index: EngineChatList.Item.Index
@ -409,7 +392,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
case ArchiveIntro(presentationData: ChatListPresentationData)
case EmptyIntro(presentationData: ChatListPresentationData)
case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool)
case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice)
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
var sortIndex: ChatListNodeEntrySortIndex {
@ -430,8 +412,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .SectionHeader:
return .sectionHeader
case .Notice:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
case let .AdditionalCategory(index, _, _, _, _, _, _):
return .additionalCategory(index)
}
@ -460,8 +440,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .EmptyIntro
case .SectionHeader:
return .SectionHeader
case .Notice:
return .Notice
case let .AdditionalCategory(_, id, _, _, _, _, _):
return .additionalCategory(id)
}
@ -534,18 +512,6 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
case let .Notice(lhsPresentationData, lhsInfo):
if case let .Notice(rhsPresentationData, rhsInfo) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsInfo != rhsInfo {
return false
}
return true
} else {
return false
}
case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData):
if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs {
if lhsIndex != rhsIndex {
@ -595,7 +561,7 @@ struct ChatListContactPeer {
}
}
func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) {
func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) {
var groupItems = view.groupItems
if isMainTab && state.archiveStoryState != nil && groupItems.isEmpty {
groupItems.append(EngineChatList.GroupItem(
@ -927,10 +893,6 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
result.append(.EmptyIntro(presentationData: state.presentationData))
}
if let notice {
result.append(.Notice(presentationData: state.presentationData, notice: notice))
}
result.append(.HeaderEntry)
}

View file

@ -410,7 +410,7 @@ public final class CombinedComponentContext<ComponentType: Component> {
public let component: ComponentType
public let availableSize: CGSize
public let transition: ComponentTransition
private let addImpl: (_ updatedComponent: _UpdatedChildComponent) -> Void
private let addImpl: (_ updatedComponent: _UpdatedChildComponent, _ container: UIView?) -> Void
public var environment: Environment<ComponentType.EnvironmentType> {
return self.context.environment
@ -425,7 +425,7 @@ public final class CombinedComponentContext<ComponentType: Component> {
component: ComponentType,
availableSize: CGSize,
transition: ComponentTransition,
add: @escaping (_ updatedComponent: _UpdatedChildComponent) -> Void
add: @escaping (_ updatedComponent: _UpdatedChildComponent, _ container: UIView?) -> Void
) {
self.context = context
self.view = view
@ -436,7 +436,11 @@ public final class CombinedComponentContext<ComponentType: Component> {
}
public func add(_ updatedComponent: _UpdatedChildComponent) {
self.addImpl(updatedComponent)
self.addImpl(updatedComponent, nil)
}
public func addWithExternalContainer(_ updatedComponent: _UpdatedChildComponent, container: UIView) {
self.addImpl(updatedComponent, container)
}
}
@ -671,7 +675,7 @@ public extension CombinedComponent {
component: self,
availableSize: availableSize,
transition: transition,
add: { updatedChild in
add: { updatedChild, optionalContainer in
if !addedChildIds.insert(updatedChild.id).inserted {
preconditionFailure("Child component can only be added once")
}
@ -692,7 +696,11 @@ public extension CombinedComponent {
context.childViewIndices.remove(at: previousView.index)
context.childViewIndices.insert(updatedChild.id, at: index)
previousView.index = index
view.insertSubview(previousView.view, at: index)
if let optionalContainer {
optionalContainer.addSubview(previousView.view)
} else {
view.insertSubview(previousView.view, at: index)
}
}
previousView.updateGestures(updatedChild.gestures)
@ -715,7 +723,11 @@ public extension CombinedComponent {
childView.transition = updatedChild.transitionDisappear
childView.transitionWithGuide = updatedChild.transitionDisappearWithGuide
view.insertSubview(updatedChild.view, at: index)
if let optionalContainer {
optionalContainer.addSubview(updatedChild.view)
} else {
view.insertSubview(updatedChild.view, at: index)
}
updatedChild.view.layer.anchorPoint = updatedChild._anchorPoint ?? CGPoint(x: 0.5, y: 0.5)

View file

@ -280,9 +280,9 @@ open class ViewControllerComponentContainer: ViewController {
case .none:
navigationBarPresentationData = nil
case .transparent:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true, style: .glass)
case .default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, style: .glass)
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
@ -308,9 +308,9 @@ open class ViewControllerComponentContainer: ViewController {
case .none:
navigationBarPresentationData = nil
case .transparent:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true, style: .glass)
case .default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, style: .glass)
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
@ -356,9 +356,9 @@ open class ViewControllerComponentContainer: ViewController {
case .none:
navigationBarPresentationData = nil
case .transparent:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true, style: .glass)
case .default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, style: .glass)
}
if let navigationBarPresentationData {
strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate)

View file

@ -485,7 +485,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.isSearchDisplayControllerActive = true
self.storiesUnlocked = false
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ContactsSearchContainerNode(context: self.context, glass: true, externalSearchBar: true, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in
if let requestAddContact = self?.requestAddContact {
requestAddContact(phoneNumber)
}

View file

@ -232,6 +232,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
private let context: AccountContext
private let glass: Bool
private let externalSearchBar: Bool
private let isPeerEnabled: (ContactListPeer) -> Bool
private let addContact: ((String) -> Void)?
private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void
@ -265,6 +266,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
public init(
context: AccountContext,
glass: Bool = false,
externalSearchBar: Bool = false,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
onlyWriteable: Bool,
categories: ContactsSearchCategories,
@ -278,6 +280,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
) {
self.context = context
self.glass = glass
self.externalSearchBar = externalSearchBar
self.isPeerEnabled = isPeerEnabled
self.addContact = addContact
self.openPeer = openPeer
@ -686,7 +689,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
self.containerViewLayout = (layout, navigationBarHeight)
let topInset = navigationBarHeight
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
self.backgroundNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight))
@ -714,7 +717,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize)
if self.glass {
if self.glass && !self.externalSearchBar {
let searchInputSize = self.searchInput.update(
transition: .immediate,
component: AnyComponent(

View file

@ -144,6 +144,7 @@ public protocol NavigationBar: ASDisplayNode {
var stripeNode: ASDisplayNode { get }
var clippingNode: SparseNode { get }
var backgroundView: UIView { get }
var contentNode: NavigationBarContentNode? { get }
var secondaryContentNode: ASDisplayNode? { get }
var secondaryContentNodeDisplayFraction: CGFloat { get set }

View file

@ -256,9 +256,9 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
if self._presentedInModal && self._hasGlassStyle {
defaultNavigationBarHeight = 66.0
} else if self._presentedInModal && layout.orientation == .portrait {
defaultNavigationBarHeight = 56.0
defaultNavigationBarHeight = 60.0
} else {
defaultNavigationBarHeight = 44.0 + 12.0
defaultNavigationBarHeight = 60.0
}
let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight(defaultHeight: defaultNavigationBarHeight) ?? defaultNavigationBarHeight)

View file

@ -327,13 +327,23 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
Queue.mainQueue().async {
if let strongSelf = self {
let previousState = previousControllerState.swap(controllerState)
let isFirstTime = previousState == nil
if previousState?.title != controllerState.title {
var previousHadContentNode = false
switch previousState?.title {
case .textWithTabs:
previousHadContentNode = true
default:
break
}
switch controllerState.title {
case let .text(text):
strongSelf.title = text
strongSelf.navigationItem.titleView = nil
strongSelf.segmentedTitleView = nil
strongSelf.navigationBar?.setContentNode(nil, animated: false)
if previousHadContentNode {
strongSelf.navigationBar?.setContentNode(nil, animated: false)
}
if strongSelf.isNodeLoaded {
strongSelf.controllerNode.panRecognizer?.isEnabled = false
}
@ -341,7 +351,9 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
strongSelf.title = ""
strongSelf.navigationItem.titleView = ItemListTextWithSubtitleTitleView(theme: controllerState.presentationData.theme, title: title, subtitle: subtitle)
strongSelf.segmentedTitleView = nil
strongSelf.navigationBar?.setContentNode(nil, animated: false)
if previousHadContentNode {
strongSelf.navigationBar?.setContentNode(nil, animated: false)
}
if strongSelf.isNodeLoaded {
strongSelf.controllerNode.panRecognizer?.isEnabled = false
}
@ -359,7 +371,9 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
}
}
}
strongSelf.navigationBar?.setContentNode(nil, animated: false)
if previousHadContentNode {
strongSelf.navigationBar?.setContentNode(nil, animated: false)
}
if strongSelf.isNodeLoaded {
strongSelf.controllerNode.panRecognizer?.isEnabled = false
}
@ -516,7 +530,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
}
}
if strongSelf.presentationData != controllerState.presentationData {
if strongSelf.presentationData != controllerState.presentationData || isFirstTime {
strongSelf.presentationData = controllerState.presentationData
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: strongSelf.hideNavigationBarBackground, hideSeparator: strongSelf.hideNavigationBarBackground, edgeEffectColor: state.0.style == .blocks ? strongSelf.presentationData.theme.list.blocksBackgroundColor : strongSelf.presentationData.theme.list.plainBackgroundColor, style: .glass), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)), transition: .immediate)

View file

@ -910,11 +910,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
if let validLayout = self.validLayout {
updatedNode.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
}
if updatedNode.addedUnderNavigationBar {
self.insertSubnode(updatedNode, belowSubnode: self.navigationBar)
} else {
self.addSubnode(updatedNode)
}
self.insertSubnode(updatedNode, belowSubnode: self.navigationBar)
updatedNode.activate()
}
} else {

View file

@ -80,6 +80,9 @@ swift_library(
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
"//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",

View file

@ -135,7 +135,7 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo
super.init()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.dimNode.backgroundColor = .clear
self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.listNode.isHidden = true

View file

@ -9,6 +9,11 @@ import ItemListUI
import PresentationDataUtils
import AccountContext
import SearchBarNode
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ActivityIndicator
final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch {
let context: AccountContext
@ -84,8 +89,8 @@ private final class ChannelDiscussionGroupSetupSearchItemNode: ItemListControlle
}
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -100,17 +105,40 @@ private final class ChannelDiscussionGroupSetupSearchItemNode: ItemListControlle
private let searchBarFont = Font.regular(17.0)
private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode {
private struct Params: Equatable {
let size: CGSize
let leftInset: CGFloat
let rightInset: CGFloat
init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.size = size
self.leftInset = leftInset
self.rightInset = rightInset
}
}
private var theme: PresentationTheme
private let strings: PresentationStrings
private let cancel: () -> Void
private let backgroundContainer: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let iconView: UIImageView
private var activityIndicator: ActivityIndicator?
private let searchBar: SearchBarNode
private let close: (background: GlassBackgroundView, icon: UIImageView)
private var params: Params?
private var queryUpdated: ((String) -> Void)?
var activity: Bool = false {
didSet {
searchBar.activity = activity
if self.activity != oldValue {
if let params = self.params {
self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
}
}
}
init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) {
@ -119,11 +147,41 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC
self.cancel = cancel
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern)
self.backgroundContainer = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.backgroundContainer.contentView.addSubview(self.backgroundView)
self.iconView = UIImageView()
self.backgroundView.contentView.addSubview(self.iconView)
self.close = (GlassBackgroundView(), UIImageView())
self.close.background.contentView.addSubview(self.close.icon)
self.searchBar = SearchBarNode(
theme: SearchBarNodeTheme(
background: .clear,
separator: .clear,
inputFill: .clear,
primaryText: theme.chat.inputPanel.panelControlColor,
placeholder: theme.chat.inputPanel.inputPlaceholderColor,
inputIcon: theme.chat.inputPanel.inputControlColor,
inputClear: theme.chat.inputPanel.inputControlColor,
accent: theme.chat.inputPanel.panelControlAccentColor,
keyboard: theme.rootController.keyboardColor
),
strings: strings,
fieldStyle: .inlineNavigation,
forceSeparator: false,
displayBackground: false,
cancelText: nil
)
super.init()
self.addSubnode(self.searchBar)
self.view.addSubview(self.backgroundContainer)
self.backgroundView.contentView.addSubview(self.searchBar.view)
self.backgroundContainer.contentView.addSubview(self.close.background)
self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:))))
self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false)
@ -141,13 +199,21 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC
self.updatePlaceholder()
}
@objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.searchBar.cancel?()
}
}
func setQueryUpdated(_ f: @escaping (String) -> Void) {
self.queryUpdated = f
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings)
if let params = self.params {
self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
self.updatePlaceholder()
}
@ -158,13 +224,85 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC
}
override var nominalHeight: CGFloat {
return 54.0
return 60.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset)
let transition = ComponentTransition(transition)
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0))
let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0))
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
if self.iconView.image == nil {
self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate)
}
transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor)
if let image = self.iconView.image {
let imageSize: CGSize
let iconFrame: CGRect
let iconFraction: CGFloat = 0.8
imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction)
iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize)
transition.setPosition(view: self.iconView, position: iconFrame.center)
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
}
if self.activity {
let activityIndicator: ActivityIndicator
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false))
self.activityIndicator = activityIndicator
self.backgroundView.contentView.addSubview(activityIndicator.view)
}
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0))
let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize)
transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center)
transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size))
} else if let activityIndicator = self.activityIndicator {
self.activityIndicator = nil
activityIndicator.view.removeFromSuperview()
}
self.iconView.isHidden = self.activity
let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0))
transition.setFrame(view: self.searchBar.view, frame: searchBarFrame)
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition)
if self.close.icon.image == nil {
self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(UIColor.white.cgColor)
context.beginPath()
context.move(to: CGPoint(x: 12.0, y: 12.0))
context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0))
context.move(to: CGPoint(x: size.width - 12.0, y: 12.0))
context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
}
if let image = close.icon.image {
self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size))
}
self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor
transition.setFrame(view: self.close.background, frame: closeFrame)
self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
}
func activate() {

View file

@ -1203,7 +1203,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
strongSelf.enqueueEmptyQueryTransition(transition, firstTime: firstTime)
if entries == nil {
strongSelf.emptyQueryListNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
strongSelf.emptyQueryListNode.backgroundColor = .clear
} else {
strongSelf.emptyQueryListNode.backgroundColor = presentationData.theme.chatList.backgroundColor
}

View file

@ -110,8 +110,8 @@ private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode {
}
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

View file

@ -8,24 +8,53 @@ import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import SearchBarNode
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ActivityIndicator
private let searchBarFont = Font.regular(17.0)
final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode {
private struct Params: Equatable {
let size: CGSize
let leftInset: CGFloat
let rightInset: CGFloat
init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.size = size
self.leftInset = leftInset
self.rightInset = rightInset
}
}
private var theme: PresentationTheme
private let strings: PresentationStrings
private let searchMode: ChannelMembersSearchMode
private let cancel: () -> Void
private let backgroundContainer: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let iconView: UIImageView
private var activityIndicator: ActivityIndicator?
private let searchBar: SearchBarNode
private let close: (background: GlassBackgroundView, icon: UIImageView)
private var params: Params?
private var queryUpdated: ((String) -> Void)?
var activity: Bool = false {
didSet {
searchBar.activity = activity
if self.activity != oldValue {
if let params = self.params {
self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
}
}
}
init(theme: PresentationTheme, strings: PresentationStrings, mode: ChannelMembersSearchMode, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) {
self.theme = theme
self.strings = strings
@ -33,11 +62,41 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item
self.cancel = cancel
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false)
self.backgroundContainer = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.backgroundContainer.contentView.addSubview(self.backgroundView)
self.iconView = UIImageView()
self.backgroundView.contentView.addSubview(self.iconView)
self.close = (GlassBackgroundView(), UIImageView())
self.close.background.contentView.addSubview(self.close.icon)
self.searchBar = SearchBarNode(
theme: SearchBarNodeTheme(
background: .clear,
separator: .clear,
inputFill: .clear,
primaryText: theme.chat.inputPanel.panelControlColor,
placeholder: theme.chat.inputPanel.inputPlaceholderColor,
inputIcon: theme.chat.inputPanel.inputControlColor,
inputClear: theme.chat.inputPanel.inputControlColor,
accent: theme.chat.inputPanel.panelControlAccentColor,
keyboard: theme.rootController.keyboardColor
),
strings: strings,
fieldStyle: .inlineNavigation,
forceSeparator: false,
displayBackground: false,
cancelText: nil
)
super.init()
self.addSubnode(self.searchBar)
self.view.addSubview(self.backgroundContainer)
self.backgroundView.contentView.addSubview(self.searchBar.view)
self.backgroundContainer.contentView.addSubview(self.close.background)
self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:))))
self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false)
@ -55,13 +114,21 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item
self.updatePlaceholder()
}
@objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.searchBar.cancel?()
}
}
func setQueryUpdated(_ f: @escaping (String) -> Void) {
self.queryUpdated = f
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings)
if let params = self.params {
self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
self.updatePlaceholder()
}
@ -77,13 +144,85 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item
}
override var nominalHeight: CGFloat {
return 54.0
return 60.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset)
let transition = ComponentTransition(transition)
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0))
let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0))
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
if self.iconView.image == nil {
self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate)
}
transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor)
if let image = self.iconView.image {
let imageSize: CGSize
let iconFrame: CGRect
let iconFraction: CGFloat = 0.8
imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction)
iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize)
transition.setPosition(view: self.iconView, position: iconFrame.center)
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
}
if self.activity {
let activityIndicator: ActivityIndicator
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false))
self.activityIndicator = activityIndicator
self.backgroundView.contentView.addSubview(activityIndicator.view)
}
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0))
let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize)
transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center)
transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size))
} else if let activityIndicator = self.activityIndicator {
self.activityIndicator = nil
activityIndicator.view.removeFromSuperview()
}
self.iconView.isHidden = self.activity
let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0))
transition.setFrame(view: self.searchBar.view, frame: searchBarFrame)
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition)
if self.close.icon.image == nil {
self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(UIColor.white.cgColor)
context.beginPath()
context.move(to: CGPoint(x: 12.0, y: 12.0))
context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0))
context.move(to: CGPoint(x: size.width - 12.0, y: 12.0))
context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
}
if let image = close.icon.image {
self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size))
}
self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor
transition.setFrame(view: self.close.background, frame: closeFrame)
self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
}
func activate() {
@ -94,4 +233,3 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item
self.searchBar.deactivate(clear: false)
}
}

View file

@ -2960,6 +2960,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
private final class PremiumIntroScreenComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let overNavigationContainer: UIView
let screenContext: PremiumIntroScreen.ScreenContext
let mode: PremiumIntroScreen.Mode
let source: PremiumSource
@ -2972,7 +2973,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let copyLink: (String) -> Void
let shareLink: (String) -> Void
init(screenContext: PremiumIntroScreen.ScreenContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void, copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void) {
init(overNavigationContainer: UIView, screenContext: PremiumIntroScreen.ScreenContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void, copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void) {
self.overNavigationContainer = overNavigationContainer
self.screenContext = screenContext
self.mode = mode
self.source = source
@ -3415,8 +3417,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let star = Child(PremiumStarComponent.self)
let emoji = Child(EmojiHeaderComponent.self)
let coin = Child(PremiumCoinComponent.self)
let topPanel = Child(BlurredBackgroundComponent.self)
let topSeparator = Child(Rectangle.self)
let title = Child(MultilineTextComponent.self)
let secondaryTitle = Child(MultilineTextWithEntitiesComponent.self)
let bottomEdgeEffect = Child(EdgeEffectComponent.self)
@ -3504,22 +3504,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
)
}
let topPanel = topPanel.update(
component: BlurredBackgroundComponent(
color: environment.theme.rootController.navigationBar.blurredBackgroundColor
),
availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight),
transition: context.transition
)
let topSeparator = topSeparator.update(
component: Rectangle(
color: environment.theme.rootController.navigationBar.separatorColor
),
availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel),
transition: context.transition
)
let titleString: String
if case .premiumGift = context.component.source {
titleString = environment.strings.Premium_PremiumGift_Title
@ -3743,14 +3727,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
let topPanelAlpha: CGFloat
let titleOffset: CGFloat
let titleScale: CGFloat
let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)
let titleAlpha: CGFloat
if let topContentOffset = state.topContentOffset {
topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
let topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0
titleOffset = topContentOffset
let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta))
@ -3762,36 +3744,29 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
titleAlpha = 1.0
}
} else {
topPanelAlpha = 0.0
titleScale = 1.0
titleOffset = 0.0
titleAlpha = state.otherPeerName != nil ? 0.0 : 1.0
}
context.add(header
context.addWithExternalContainer(header
.position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale))
.scale(titleScale)
.scale(titleScale),
container: context.component.overNavigationContainer
)
context.add(topPanel
.position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0))
.opacity(topPanelAlpha)
)
context.add(topSeparator
.position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height))
.opacity(topPanelAlpha)
)
context.add(title
context.addWithExternalContainer(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
.scale(titleScale)
.opacity(titleAlpha)
.opacity(titleAlpha),
container: context.component.overNavigationContainer
)
context.add(secondaryTitle
context.addWithExternalContainer(secondaryTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
.scale(titleScale)
.opacity(max(0.0, 1.0 - titleAlpha * 1.8))
.opacity(max(0.0, 1.0 - titleAlpha * 1.8)),
container: context.component.overNavigationContainer
)
var isUnusedGift = false
@ -3989,6 +3964,8 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
public weak var containerView: UIView?
public var animationColor: UIColor?
private let overNavigationContainer: UIView
public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) {
self.init(screenContext: .accountContext(context), mode: mode, source: source, modal: modal, forceDark: forceDark, forceHasPremium: forceHasPremium)
}
@ -4005,7 +3982,11 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
var completionImpl: (() -> Void)?
var copyLinkImpl: ((String) -> Void)?
var shareLinkImpl: ((String) -> Void)?
self.overNavigationContainer = UIView()
super.init(component: PremiumIntroScreenComponent(
overNavigationContainer: self.overNavigationContainer,
screenContext: screenContext,
mode: mode,
source: source,
@ -4107,12 +4088,20 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
context.account.viewTracker.keepQuickRepliesApproximatelyUpdated()
context.account.viewTracker.keepBusinessLinksApproximatelyUpdated()
}
if let navigationBar = self.navigationBar {
navigationBar.view.insertSubview(self.overNavigationContainer, aboveSubview: navigationBar.backgroundView)
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.dismissAllTooltips()
@ -4145,10 +4134,10 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
super.containerLayoutUpdated(layout, transition: transition)
if !self.didSetReady {
if let view = self.node.hostView.findTaggedView(tag: PremiumCoinComponent.View.Tag()) as? PremiumCoinComponent.View {
if let view = findTaggedComponentViewImpl(view: self.view, tag: PremiumCoinComponent.View.Tag()) as? PremiumCoinComponent.View {
self.didSetReady = true
self._ready.set(view.ready)
} else if let view = self.node.hostView.findTaggedView(tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View {
} else if let view = findTaggedComponentViewImpl(view: self.view, tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View {
self.didSetReady = true
self._ready.set(view.ready)
@ -4161,7 +4150,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
self.containerView = nil
self.animationColor = nil
}
} else if let view = self.node.hostView.findTaggedView(tag: EmojiHeaderComponent.View.Tag()) as? EmojiHeaderComponent.View {
} else if let view = findTaggedComponentViewImpl(view: self.view, tag: EmojiHeaderComponent.View.Tag()) as? EmojiHeaderComponent.View {
self.didSetReady = true
self._ready.set(view.ready)

View file

@ -10,12 +10,17 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/SearchBarNode:SearchBarNode",
"//submodules/ChatListSearchItemNode:ChatListSearchItemNode",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramPresentationData",
"//submodules/SearchBarNode",
"//submodules/ChatListSearchItemNode",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/AppBundle",
"//submodules/ActivityIndicator",
],
visibility = [
"//visibility:public",

View file

@ -47,22 +47,3 @@ public func fixNavigationSearchableListNodeScrolling(_ listNode: ListView, searc
}
return false
}
func fixNavigationSearchableGridNodeScrolling(_ gridNode: GridNode, searchNode: NavigationBarSearchContentNode) -> Bool {
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
let scrollToItem: GridNodeScrollToItem
let targetProgress: CGFloat
if searchNode.expansionProgress < 0.6 {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), transition: .animated(duration: 0.3, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true)
targetProgress = 0.0
} else {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.3, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true)
targetProgress = 1.0
}
searchNode.updateExpansionProgress(targetProgress, animated: true)
gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in })
return true
}
return false
}

View file

@ -4,11 +4,28 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import SearchBarNode
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ActivityIndicator
private let searchBarFont = Font.regular(17.0)
public let navigationBarSearchContentHeight: CGFloat = 54.0
public class NavigationBarSearchContentNode: NavigationBarContentNode {
private struct Params: Equatable {
let size: CGSize
let leftInset: CGFloat
let rightInset: CGFloat
init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.size = size
self.leftInset = leftInset
self.rightInset = rightInset
}
}
public var theme: PresentationTheme?
public var placeholder: String
public var compactPlaceholder: String
@ -19,8 +36,6 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
private var disabledOverlay: ASDisplayNode?
public var expansionProgress: CGFloat = 1.0
public var additionalHeight: CGFloat = 0.0
private var validLayout: (CGSize, CGFloat, CGFloat)?
@ -158,7 +173,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
}
override public var nominalHeight: CGFloat {
return navigationBarSearchContentHeight + self.additionalHeight
return 60.0
}
override public var mode: NavigationBarContentMode {

View file

@ -59,113 +59,6 @@ extension SettingsSearchableItemIcon {
}
}
final class SettingsSearchItem: ItemListControllerSearch {
let context: AccountContext
let theme: PresentationTheme
let placeholder: String
let activated: Bool
let updateActivated: (Bool) -> Void
let presentController: (ViewController, Any?) -> Void
let pushController: (ViewController) -> Void
let getNavigationController: (() -> NavigationController?)?
let resolvedFaqUrl: Signal<ResolvedUrl?, NoError>
let exceptionsList: Signal<NotificationExceptionsList?, NoError>
let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>
let privacySettings: Signal<AccountPrivacySettings?, NoError>
let hasTwoStepAuth: Signal<Bool?, NoError>
let twoStepAuthData: Signal<TwoStepVerificationAccessConfiguration?, NoError>
let activeSessionsContext: Signal<ActiveSessionsContext?, NoError>
let webSessionsContext: Signal<WebSessionsContext?, NoError>
private var updateActivity: ((Bool) -> Void)?
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
private let activityDisposable = MetaDisposable()
init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal<ResolvedUrl?, NoError>, exceptionsList: Signal<NotificationExceptionsList?, NoError>, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal<AccountPrivacySettings?, NoError>, hasTwoStepAuth: Signal<Bool?, NoError>, twoStepAuthData: Signal<TwoStepVerificationAccessConfiguration?, NoError>, activeSessionsContext: Signal<ActiveSessionsContext?, NoError>, webSessionsContext: Signal<WebSessionsContext?, NoError>) {
self.context = context
self.theme = theme
self.placeholder = placeholder
self.activated = activated
self.updateActivated = updateActivated
self.presentController = presentController
self.pushController = pushController
self.getNavigationController = getNavigationController
self.resolvedFaqUrl = resolvedFaqUrl
self.exceptionsList = exceptionsList
self.archivedStickerPacks = archivedStickerPacks
self.privacySettings = privacySettings
self.hasTwoStepAuth = hasTwoStepAuth
self.twoStepAuthData = twoStepAuthData
self.activeSessionsContext = activeSessionsContext
self.webSessionsContext = webSessionsContext
self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal<Bool, NoError> in
if value {
return .single(value) |> delay(0.2, queue: Queue.mainQueue())
} else {
return .single(value)
}
}).start(next: { [weak self] value in
self?.updateActivity?(value)
}))
}
deinit {
self.activityDisposable.dispose()
}
func isEqual(to: ItemListControllerSearch) -> Bool {
if let to = to as? SettingsSearchItem {
if self.context !== to.context || self.theme !== to.theme || self.placeholder != to.placeholder || self.activated != to.activated {
return false
}
return true
} else {
return false
}
}
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
let updateActivated: (Bool) -> Void = self.updateActivated
if let current = current as? NavigationBarSearchContentNode {
current.updateThemeAndPlaceholder(theme: self.theme, placeholder: self.placeholder)
return current
} else {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
return NavigationBarSearchContentNode(theme: presentationData.theme, placeholder: presentationData.strings.Settings_Search, activate: {
updateActivated(true)
})
}
}
func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode {
let updateActivated: (Bool) -> Void = self.updateActivated
let presentController: (ViewController, Any?) -> Void = self.presentController
let pushController: (ViewController) -> Void = self.pushController
if let current = current as? SettingsSearchItemNode, let titleContentNode = titleContentNode as? NavigationBarSearchContentNode {
current.updatePresentationData(self.context.sharedContext.currentPresentationData.with { $0 })
if current.isSearching != self.activated {
if self.activated {
current.activateSearch(placeholderNode: titleContentNode.placeholderNode)
} else {
current.deactivateSearch(placeholderNode: titleContentNode.placeholderNode)
}
}
return current
} else {
return SettingsSearchItemNode(context: self.context, cancel: {
updateActivated(false)
}, updateActivity: { [weak self] value in
self?.activity.set(value)
}, pushController: { c in
pushController(c)
}, presentController: { c, a in
presentController(c, a)
}, getNavigationController: self.getNavigationController, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasTwoStepAuth: self.hasTwoStepAuth, twoStepAuthData: self.twoStepAuthData, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext)
}
}
}
final class SettingsSearchInteraction {
let openItem: (SettingsSearchableItem) -> Void
let deleteRecentItem: (SettingsSearchableItemId) -> Void

View file

@ -228,7 +228,6 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {

View file

@ -377,7 +377,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {

View file

@ -25,6 +25,7 @@ swift_library(
"//submodules/Emoji",
"//submodules/TelegramCore/FlatBuffers",
"//submodules/TelegramCore/FlatSerialization",
"//submodules/MurMurHash32"
],
visibility = [
"//visibility:public",

View file

@ -53,11 +53,15 @@ public extension NavigationBarTheme {
let disabledButtonColor: UIColor
let badgeBackgroundColor: UIColor
let badgeTextColor: UIColor
var edgeEffectColor = edgeEffectColor
if case .glass = style {
buttonColor = rootControllerTheme.chat.inputPanel.panelControlColor
disabledButtonColor = buttonColor.withMultipliedAlpha(0.5)
badgeBackgroundColor = rootControllerTheme.chat.inputPanel.panelControlColor
badgeTextColor = rootControllerTheme.list.itemCheckColors.foregroundColor
badgeTextColor = rootControllerTheme.overallDarkAppearance ? .black : rootControllerTheme.list.itemCheckColors.foregroundColor
if edgeEffectColor == nil {
edgeEffectColor = rootControllerTheme.list.plainBackgroundColor
}
} else {
buttonColor = theme.buttonColor
disabledButtonColor = theme.disabledButtonColor

View file

@ -459,10 +459,10 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre
let updatedTheme = presentationData.theme.withModalBlocksBackground()
presentationData = presentationData.withUpdated(theme: updatedTheme)
let barButtonSize = CGSize(width: 40.0, height: 40.0)
let barButtonSize = CGSize(width: 44.0, height: 44.0)
let closeButton = GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: nil,
isDark: presentationData.theme.overallDarkAppearance,
state: .generic,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
@ -489,7 +489,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre
let searchButton = GlassBarButtonComponent(
size: barButtonSize,
backgroundColor: presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: nil,
isDark: presentationData.theme.overallDarkAppearance,
state: .generic,
component: AnyComponentWithIdentity(id: "search", component: AnyComponent(

View file

@ -901,8 +901,6 @@ public final class ChatInlineSearchResultsListComponent: Component {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { _ in
},
openWebApp: { _ in

View file

@ -151,7 +151,7 @@ public final class ChatSearchNavigationContentNode: NavigationBarContentNode {
let transition = ComponentTransition(transition)
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: floor((size.height - 44.0) * 0.5)), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0))
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0))
let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0))
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))

View file

@ -0,0 +1,33 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatListHeaderNoticeComponent",
module_name = "ChatListHeaderNoticeComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AppBundle",
"//submodules/ItemListUI",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TextFormat",
"//submodules/AvatarNode",
"//submodules/TelegramUI/Components/GlobalControlPanelsContext",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,139 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import TelegramPresentationData
import AccountContext
import GlobalControlPanelsContext
import ComponentFlow
import ComponentDisplayAdapters
public final class ChatListHeaderNoticeComponent: Component {
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let data: GlobalControlPanelsContext.ChatListNotice
public let activateAction: (GlobalControlPanelsContext.ChatListNotice) -> Void
public let dismissAction: (GlobalControlPanelsContext.ChatListNotice) -> Void
public let selectAction: (GlobalControlPanelsContext.ChatListNotice, Bool) -> Void
public init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
data: GlobalControlPanelsContext.ChatListNotice,
activateAction: @escaping (GlobalControlPanelsContext.ChatListNotice) -> Void,
dismissAction: @escaping (GlobalControlPanelsContext.ChatListNotice) -> Void,
selectAction: @escaping (GlobalControlPanelsContext.ChatListNotice, Bool) -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.data = data
self.activateAction = activateAction
self.dismissAction = dismissAction
self.selectAction = selectAction
}
public static func ==(lhs: ChatListHeaderNoticeComponent, rhs: ChatListHeaderNoticeComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.data != rhs.data {
return false
}
return true
}
public final class View: UIView {
private var panel: ChatListNoticeItemNode?
private var component: ChatListHeaderNoticeComponent?
private weak var state: EmptyComponentState?
public override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
component.activateAction(component.data)
}
}
func update(component: ChatListHeaderNoticeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let itemNode: ChatListNoticeItemNode
if let current = self.panel {
itemNode = current
} else {
itemNode = ChatListNoticeItemNode()
self.panel = itemNode
self.addSubview(itemNode.view)
}
let item = ChatListNoticeItem(
context: component.context,
theme: component.theme,
strings: component.strings,
notice: component.data,
action: { [weak self] action in
guard let self, let component = self.component else {
return
}
switch action {
case .activate:
component.activateAction(component.data)
case .hide:
component.dismissAction(component.data)
case let .buttonChoice(isPositive):
component.selectAction(component.data, isPositive)
}
}
)
let (nodeLayout, apply) = itemNode.asyncLayout()(item, ListViewItemLayoutParams(
width: availableSize.width,
leftInset: 0.0,
rightInset: 0.0,
availableHeight: 10000.0,
isStandalone: true
), false)
let size = CGSize(width: availableSize.width, height: nodeLayout.contentSize.height)
let panelFrame = CGRect(origin: CGPoint(), size: size)
transition.setFrame(view: itemNode.view, frame: panelFrame)
apply()
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -5,7 +5,6 @@ import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import ListSectionHeaderNode
import AppBundle
import ItemListUI
import Markdown
@ -14,6 +13,7 @@ import MergedAvatarsNode
import TextNodeWithEntities
import TextFormat
import AvatarNode
import GlobalControlPanelsContext
class ChatListNoticeItem: ListViewItem {
enum Action {
@ -25,12 +25,12 @@ class ChatListNoticeItem: ListViewItem {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let notice: ChatListNotice
let notice: GlobalControlPanelsContext.ChatListNotice
let action: (Action) -> Void
let selectable: Bool = true
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: ChatListNotice, action: @escaping (Action) -> Void) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: GlobalControlPanelsContext.ChatListNotice, action: @escaping (Action) -> Void) {
self.context = context
self.theme = theme
self.strings = strings
@ -140,9 +140,6 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
self.contentContainer.addSubnode(self.arrowNode)
self.addSubnode(self.contentContainer)
self.addSubnode(self.separatorNode)
self.zPosition = 1.0
}
@objc private func closePressed() {
@ -329,8 +326,6 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
strongSelf.item = item
if themeUpdated {
strongSelf.contentContainer.backgroundColor = item.theme.chatList.pinnedItemBackgroundColor
strongSelf.separatorNode.backgroundColor = item.theme.chatList.itemSeparatorColor
strongSelf.arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.theme)
}
@ -502,7 +497,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
//strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
switch item.notice {
default:

View file

@ -544,8 +544,8 @@ public class GlassBackgroundView: UIView {
nativeParamsView.lumaMin = 0.0
nativeParamsView.lumaMax = 0.15
} else {
nativeParamsView.lumaMin = 0.6
nativeParamsView.lumaMax = 0.61
nativeParamsView.lumaMin = 0.7
nativeParamsView.lumaMax = 0.71
}
}
}
@ -638,8 +638,8 @@ public final class GlassBackgroundContainerView: UIView {
nativeParamsView.lumaMin = 0.0
nativeParamsView.lumaMax = 0.15
} else {
nativeParamsView.lumaMin = 0.6
nativeParamsView.lumaMax = 0.61
nativeParamsView.lumaMin = 0.7
nativeParamsView.lumaMax = 0.71
}
transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size))

View file

@ -13,7 +13,7 @@ public final class GlassBarButtonComponent: Component {
}
public let size: CGSize?
public let backgroundColor: UIColor
public let backgroundColor: UIColor?
public let isDark: Bool
public let state: DisplayState?
public let isEnabled: Bool
@ -22,7 +22,7 @@ public final class GlassBarButtonComponent: Component {
public init(
size: CGSize?,
backgroundColor: UIColor,
backgroundColor: UIColor?,
isDark: Bool,
state: DisplayState? = nil,
isEnabled: Bool = true,
@ -181,8 +181,9 @@ public final class GlassBarButtonComponent: Component {
}
let cornerRadius = containerSize.height * 0.5
self.genericBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: component.backgroundColor), transition: transition)
if let backgroundColor = component.backgroundColor {
self.genericBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: backgroundColor), transition: transition)
}
let bounds = CGRect(origin: .zero, size: containerSize)
transition.setFrame(view: self.containerView, frame: bounds)
@ -196,7 +197,7 @@ public final class GlassBarButtonComponent: Component {
transition.setFrame(view: self.genericBackgroundView, frame: bounds)
if glassAlpha == 1.0 {
if glassAlpha == 1.0, let backgroundColor = component.backgroundColor {
let glassBackgroundView: GlassBackgroundView
var glassBackgroundTransition = transition
if let current = self.glassBackgroundView {
@ -210,7 +211,7 @@ public final class GlassBarButtonComponent: Component {
transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0)
}
glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: component.backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: glassBackgroundTransition)
glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: glassBackgroundTransition)
glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds)
} else if let glassBackgroundView = self.glassBackgroundView {
self.glassBackgroundView = nil
@ -310,7 +311,7 @@ public final class BarComponentHostNode: ASDisplayNode {
transition.animateScale(view: view, from: 0.01, to: 1.0)
}
}
view.frame = CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: self.size)
view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.size)
}
}
}

View file

@ -15,6 +15,8 @@ swift_library(
"//submodules/AccountContext",
"//submodules/TelegramUIPreferences",
"//submodules/TelegramCallsUI",
"//submodules/Display",
"//submodules/UndoUI",
],
visibility = [
"//visibility:public",

View file

@ -1,9 +1,12 @@
import Foundation
import UIKit
import SwiftSignalKit
import TelegramCore
import AccountContext
import TelegramUIPreferences
import TelegramCallsUI
import Display
import UndoUI
public final class GlobalControlPanelsContext {
public final class MediaPlayback: Equatable {
@ -62,17 +65,37 @@ public final class GlobalControlPanelsContext {
return true
}
}
public enum ChatListNotice: Equatable {
case clearStorage(sizeFraction: Double)
case setupPassword
case premiumUpgrade(discount: Int32)
case premiumAnnualDiscount(discount: Int32)
case premiumRestore(discount: Int32)
case xmasPremiumGift
case setupBirthday
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
case setupPhoto(EnginePeer)
case accountFreeze
case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text)
}
public final class State {
public let mediaPlayback: MediaPlayback?
public let liveLocation: LiveLocation?
public let chatListNotice: ChatListNotice?
public init(
mediaPlayback: MediaPlayback?,
liveLocation: LiveLocation?
liveLocation: LiveLocation?,
chatListNotice: ChatListNotice?
) {
self.mediaPlayback = mediaPlayback
self.liveLocation = liveLocation
self.chatListNotice = chatListNotice
}
}
@ -94,12 +117,15 @@ public final class GlobalControlPanelsContext {
var liveLocationState: (mode: LiveLocationMode, peers: [EnginePeer], messages: [EngineMessage.Id: EngineMessage], canClose: Bool, version: Int)?
var liveLocationDisposable: Disposable?
var chatListNotice: ChatListNotice?
var suggestedChatListNoticeDisposable: Disposable?
init(queue: Queue, context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?) {
init(queue: Queue, context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?, chatListNotices: Bool) {
self.queue = queue
self.context = context
self.stateValue = State(mediaPlayback: nil, liveLocation: nil)
self.stateValue = State(mediaPlayback: nil, liveLocation: nil, chatListNotice: nil)
if mediaPlayback {
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
@ -227,11 +253,176 @@ public final class GlobalControlPanelsContext {
}
})
}
if chatListNotices {
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> distinctUntilChanged
|> map { appConfiguration -> AccountFreezeConfiguration in
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
})
let starsSubscriptionsContextPromise = Promise<StarsSubscriptionsContext?>(nil)
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
context.engine.notices.getServerProvidedSuggestions(),
context.engine.notices.getServerDismissedSuggestions(),
twoStepData,
newSessionReviews(postbox: context.account.postbox),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
),
context.account.stateManager.contactBirthdays,
starsSubscriptionsContextPromise.get(),
accountFreezeConfiguration
)
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first {
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
}
if suggestions.contains(.setupPassword), let configuration {
var notSet = false
switch configuration {
case let .notSet(pendingEmail):
if pendingEmail == nil {
notSet = true
}
case .set:
break
}
if notSet {
return .single(.setupPassword)
}
}
let today = Calendar(identifier: .gregorian).component(.day, from: Date())
var todayBirthdayPeerIds: [EnginePeer.Id] = []
for (peerId, birthday) in birthdays {
if birthday.day == today {
todayBirthdayPeerIds.append(peerId)
}
}
todayBirthdayPeerIds.sort { lhs, rhs in
return lhs < rhs
}
if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) {
todayBirthdayPeerIds = []
}
if let _ = accountFreezeConfiguration.freezeUntilDate {
return .single(.accountFreeze)
} else if suggestions.contains(.starsSubscriptionLowBalance) {
if let starsSubscriptionsContext {
return starsSubscriptionsContext.state
|> map { state in
if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty {
return .starsSubscriptionLowBalance(
amount: state.balance,
peers: state.subscriptions.map { $0.peer }
)
} else {
return nil
}
}
} else {
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
return .single(nil)
}
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
return .single(.setupPhoto(accountPeer))
} else if suggestions.contains(.gracePremium) {
return .single(.premiumGrace)
} else if suggestions.contains(.xmasPremiumGift) {
return .single(.xmasPremiumGift)
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
return inAppPurchaseManager.availableProducts
|> map { products -> ChatListNotice? in
if products.count > 1 {
let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
}
for product in products {
if product.id.hasSuffix(".annual") {
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0)
let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0)
if discount > 0 {
if suggestions.contains(.restorePremium) {
return .premiumRestore(discount: discount)
} else if suggestions.contains(.annualPremium) {
return .premiumAnnualDiscount(discount: discount)
} else if suggestions.contains(.upgradePremium) {
return .premiumUpgrade(discount: discount)
}
}
break
}
}
return nil
} else {
if !GlobalExperimentalSettings.isAppStoreBuild {
if suggestions.contains(.restorePremium) {
return .premiumRestore(discount: 0)
} else if suggestions.contains(.annualPremium) {
return .premiumAnnualDiscount(discount: 0)
} else if suggestions.contains(.upgradePremium) {
return .premiumUpgrade(discount: 0)
}
}
return nil
}
}
} else if !todayBirthdayPeerIds.isEmpty {
return context.engine.data.get(
EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> map { result -> ChatListNotice? in
var todayBirthdayPeers: [EnginePeer] = []
for (peerId, _) in birthdays {
if let maybePeer = result[peerId], let peer = maybePeer {
todayBirthdayPeers.append(peer)
}
}
return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays)
}
} else if suggestions.contains(.setupBirthday) && birthday == nil {
return .single(.setupBirthday)
} else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) {
return .single(.link(id: id, url: url, title: title, subtitle: subtitle))
} else {
return .single(nil)
}
}
|> distinctUntilChanged
self.suggestedChatListNoticeDisposable = (suggestedChatListNoticeSignal
|> deliverOn(self.queue)).startStrict(next: { [weak self] chatListNotice in
guard let self else {
return
}
if self.chatListNotice != chatListNotice {
self.chatListNotice = chatListNotice
self.notifyStateUpdated()
}
})
}
}
deinit {
self.mediaStatusDisposable?.dispose()
self.liveLocationDisposable?.dispose()
self.suggestedChatListNoticeDisposable?.dispose()
}
private func notifyStateUpdated() {
@ -256,10 +447,42 @@ public final class GlobalControlPanelsContext {
canClose: liveLocationState.canClose,
version: liveLocationState.version
)
}
},
chatListNotice: self.chatListNotice
)
self.statePipe.putNext(self.stateValue)
}
func dismissChatListNotice(parentController: ViewController, notice: ChatListNotice) {
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
switch notice {
case .xmasPremiumGift:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone()
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}), in: .current)
case .setupBirthday:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone()
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}), in: .current)
case .birthdayPremiumGift:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone()
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
return true
}), in: .current)
case .premiumGrace:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone()
case .setupPhoto:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone()
case .starsSubscriptionLowBalance:
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone()
case let .link(id, _, _, _):
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone()
default:
break
}
}
}
private let impl: QueueLocalObject<Impl>
@ -270,9 +493,15 @@ public final class GlobalControlPanelsContext {
}
}
public init(context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?) {
public init(context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?, chatListNotices: Bool) {
self.impl = QueueLocalObject(queue: .mainQueue(), generate: {
return Impl(queue: .mainQueue(), context: context, mediaPlayback: mediaPlayback, liveLocationMode: liveLocationMode, groupCalls: groupCalls)
return Impl(queue: .mainQueue(), context: context, mediaPlayback: mediaPlayback, liveLocationMode: liveLocationMode, groupCalls: groupCalls, chatListNotices: chatListNotices)
})
}
public func dismissChatListNotice(parentController: ViewController, notice: ChatListNotice) {
self.impl.with { impl in
impl.dismissChatListNotice(parentController: parentController, notice: notice)
}
}
}

View file

@ -24,6 +24,11 @@ swift_library(
"//submodules/SearchUI:SearchUI",
"//submodules/MergeLists:MergeLists",
"//submodules/UndoUI:UndoUI",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/AppBundle",
"//submodules/ActivityIndicator",
],
visibility = [
"//visibility:public",

View file

@ -105,8 +105,8 @@ private final class GroupStickerSearchItemNode: ItemListControllerSearchNode {
}
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

View file

@ -8,34 +8,93 @@ import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import SearchBarNode
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ActivityIndicator
private let searchBarFont = Font.regular(17.0)
final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode {
private struct Params: Equatable {
let size: CGSize
let leftInset: CGFloat
let rightInset: CGFloat
init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.size = size
self.leftInset = leftInset
self.rightInset = rightInset
}
}
private var theme: PresentationTheme
private let strings: PresentationStrings
private let cancel: () -> Void
private let backgroundContainer: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let iconView: UIImageView
private var activityIndicator: ActivityIndicator?
private let searchBar: SearchBarNode
private let close: (background: GlassBackgroundView, icon: UIImageView)
private var params: Params?
private var queryUpdated: ((String) -> Void)?
var activity: Bool = false {
didSet {
self.searchBar.activity = activity
if self.activity != oldValue {
if let params = self.params {
self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate)
}
}
}
}
init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) {
self.theme = theme
self.strings = strings
self.cancel = cancel
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false)
self.backgroundContainer = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.backgroundContainer.contentView.addSubview(self.backgroundView)
self.iconView = UIImageView()
self.backgroundView.contentView.addSubview(self.iconView)
self.close = (GlassBackgroundView(), UIImageView())
self.close.background.contentView.addSubview(self.close.icon)
self.searchBar = SearchBarNode(
theme: SearchBarNodeTheme(
background: .clear,
separator: .clear,
inputFill: .clear,
primaryText: theme.chat.inputPanel.panelControlColor,
placeholder: theme.chat.inputPanel.inputPlaceholderColor,
inputIcon: theme.chat.inputPanel.inputControlColor,
inputClear: theme.chat.inputPanel.inputControlColor,
accent: theme.chat.inputPanel.panelControlAccentColor,
keyboard: theme.rootController.keyboardColor
),
strings: strings,
fieldStyle: .inlineNavigation,
forceSeparator: false,
displayBackground: false,
cancelText: nil
)
super.init()
self.addSubnode(self.searchBar)
self.view.addSubview(self.backgroundContainer)
self.backgroundView.contentView.addSubview(self.searchBar.view)
self.backgroundContainer.contentView.addSubview(self.close.background)
self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:))))
self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false)
@ -47,12 +106,21 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I
}
updateActivity({ [weak self] value in
self?.activity = value
guard let self else {
return
}
self.activity = value
})
self.updatePlaceholder()
}
@objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.searchBar.cancel?()
}
}
func setQueryUpdated(_ f: @escaping (String) -> Void) {
self.queryUpdated = f
}
@ -68,13 +136,85 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I
}
override var nominalHeight: CGFloat {
return 54.0
return 60.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset)
let transition = ComponentTransition(transition)
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0))
let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0))
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
if self.iconView.image == nil {
self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate)
}
transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor)
if let image = self.iconView.image {
let imageSize: CGSize
let iconFrame: CGRect
let iconFraction: CGFloat = 0.8
imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction)
iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize)
transition.setPosition(view: self.iconView, position: iconFrame.center)
transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
}
if self.activity {
let activityIndicator: ActivityIndicator
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false))
self.activityIndicator = activityIndicator
self.backgroundView.contentView.addSubview(activityIndicator.view)
}
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0))
let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize)
transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center)
transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size))
} else if let activityIndicator = self.activityIndicator {
self.activityIndicator = nil
activityIndicator.view.removeFromSuperview()
}
self.iconView.isHidden = self.activity
let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0))
transition.setFrame(view: self.searchBar.view, frame: searchBarFrame)
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition)
if self.close.icon.image == nil {
self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(UIColor.white.cgColor)
context.beginPath()
context.move(to: CGPoint(x: 12.0, y: 12.0))
context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0))
context.move(to: CGPoint(x: size.width - 12.0, y: 12.0))
context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
}
if let image = close.icon.image {
self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size))
}
self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor
transition.setFrame(view: self.close.background, frame: closeFrame)
self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
}
func activate() {
@ -85,4 +225,3 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I
self.searchBar.deactivate(clear: false)
}
}

View file

@ -52,7 +52,7 @@ public final class MoreHeaderButton: HighlightableButtonNode {
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: 44.0))
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0))
self.referenceNode.frame = self.containerNode.bounds
//self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color)
@ -174,7 +174,7 @@ public final class MoreHeaderButton: HighlightableButtonNode {
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 22.0, height: 44.0)
return CGSize(width: 44.0, height: 44.0)
}
public func onLayout() {

View file

@ -540,6 +540,14 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
private var edgeEffectView: EdgeEffectView?
private var backgroundContainer: GlassBackgroundContainerView?
public var backgroundView: UIView {
if let edgeEffectView = self.edgeEffectView {
return edgeEffectView
} else {
return self.backgroundNode.view
}
}
public init(presentationData: NavigationBarPresentationData) {
self.presentationData = presentationData
self.stripeNode = ASDisplayNode()
@ -734,7 +742,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
var contentVerticalOrigin = additionalTopHeight
if case .glass = self.presentationData.theme.style {
contentVerticalOrigin += 4.0
contentVerticalOrigin += 2.0
}
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight))
@ -744,8 +752,10 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
}
if let backgroundContainer = self.backgroundContainer {
transition.updateFrame(view: backgroundContainer, frame: backgroundFrame)
backgroundContainer.update(size: backgroundFrame.size, isDark: self.presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition))
var backgroundContainerFrame = backgroundFrame
backgroundContainerFrame.size.height += 44.0
transition.updateFrame(view: backgroundContainer, frame: backgroundContainerFrame)
backgroundContainer.update(size: backgroundContainerFrame.size, isDark: self.presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition))
}
if let edgeEffectView = self.edgeEffectView {
@ -755,7 +765,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
edgeEffectView.isHidden = false
let edgeEffectFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight + 20.0))
transition.updateFrame(view: edgeEffectView, frame: edgeEffectFrame)
edgeEffectView.update(content: self.presentationData.theme.edgeEffectColor ?? .white, blur: true, rect: CGRect(origin: CGPoint(), size: edgeEffectFrame.size), edge: .top, edgeSize: 40.0, transition: ComponentTransition(transition))
edgeEffectView.update(content: self.presentationData.theme.edgeEffectColor ?? .white, blur: true, rect: CGRect(origin: CGPoint(), size: edgeEffectFrame.size), edge: .top, edgeSize: 50.0, transition: ComponentTransition(transition))
}
}
@ -795,7 +805,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
transition.updateFrame(node: self.stripeNode, frame: CGRect(x: (additionalCutout?.width ?? 0.0), y: size.height + additionalBackgroundHeight, width: size.width - (additionalCutout?.width ?? 0.0), height: UIScreenPixel))
let nominalHeight: CGFloat = defaultHeight
let nominalHeight: CGFloat = 56.0
var leftTitleInset: CGFloat = leftInset + 1.0
var rightTitleInset: CGFloat = rightInset + 1.0
@ -910,15 +920,15 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
let rightButtonsBackgroundFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - rightButtonsWidth, y: contentVerticalOrigin + floor((nominalHeight - 44.0) * 0.5)), size: CGSize(width: rightButtonsWidth, height: 44.0))
var rightButtonsBackgroundTransition = ComponentTransition(transition)
if rightButtonsBackgroundView.alpha == 0.0 {
if rightButtonsBackgroundView.isHidden {
rightButtonsBackgroundTransition = .immediate
}
rightButtonsBackgroundTransition.setFrame(view: rightButtonsBackgroundView, frame: rightButtonsBackgroundFrame)
transition.updateAlpha(layer: rightButtonsBackgroundView.layer, alpha: 1.0)
rightButtonsBackgroundView.isHidden = false
rightButtonsBackgroundView.update(size: rightButtonsBackgroundFrame.size, cornerRadius: rightButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: rightButtonsBackgroundTransition)
} else {
transition.updateAlpha(layer: rightButtonsBackgroundView.layer, alpha: 0.0)
rightButtonsBackgroundView.isHidden = true
}
}

View file

@ -749,7 +749,7 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
}
}
if self.bounds.contains(point) {
return self.nodes[0].view
return self.nodes[0].view.hitTest(self.view.convert(point, to: self.nodes[0].view), with: event)
} else {
return nil
}

View file

@ -183,7 +183,6 @@ public final class LoadingOverlayNode: ASDisplayNode {
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
@ -541,8 +540,6 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { _ in
},
openWebApp: { _ in

View file

@ -104,7 +104,6 @@ import AttachmentUI
import BoostLevelIconComponent
import PeerInfoChatPaneNode
import PeerInfoChatListPaneNode
import GroupStickerPackSetupController
import PeerNameColorItem
import PeerSelectionScreen
import UIKitRuntimeUtils
@ -593,7 +592,6 @@ private final class PeerInfoInteraction {
let editingOpenPreHistorySetup: () -> Void
let editingOpenAutoremoveMesages: () -> Void
let openPermissions: () -> Void
let editingOpenStickerPackSetup: () -> Void
let openLocation: () -> Void
let editingOpenSetupLocation: () -> Void
let openPeerInfo: (Peer, Bool) -> Void
@ -670,7 +668,6 @@ private final class PeerInfoInteraction {
editingOpenPreHistorySetup: @escaping () -> Void,
editingOpenAutoremoveMesages: @escaping () -> Void,
openPermissions: @escaping () -> Void,
editingOpenStickerPackSetup: @escaping () -> Void,
openLocation: @escaping () -> Void,
editingOpenSetupLocation: @escaping () -> Void,
openPeerInfo: @escaping (Peer, Bool) -> Void,
@ -746,7 +743,6 @@ private final class PeerInfoInteraction {
self.editingOpenPreHistorySetup = editingOpenPreHistorySetup
self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages
self.openPermissions = openPermissions
self.editingOpenStickerPackSetup = editingOpenStickerPackSetup
self.openLocation = openLocation
self.editingOpenSetupLocation = editingOpenSetupLocation
self.openPeerInfo = openPeerInfo
@ -3273,9 +3269,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
openPermissions: { [weak self] in
self?.openPermissions()
},
editingOpenStickerPackSetup: { [weak self] in
self?.editingOpenStickerPackSetup()
},
openLocation: { [weak self] in
self?.openLocation()
},
@ -9809,13 +9802,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.controller?.push(channelPermissionsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id))
}
private func editingOpenStickerPackSetup() {
guard let data = self.data, let peer = data.peer, let cachedData = data.cachedData as? CachedChannelData else {
return
}
self.controller?.push(groupStickerPackSetupController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, currentPackInfo: cachedData.stickerPack))
}
private func openLocation() {
guard let data = self.data, let peer = data.peer else {
return

View file

@ -205,8 +205,6 @@ final class GreetingMessageListItemComponent: Component {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { _ in
},
openWebApp: { _ in

View file

@ -219,8 +219,6 @@ final class QuickReplySetupScreenComponent: Component {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { [weak listNode] _ in
guard let listNode, let parentView = listNode.parentView else {
return

View file

@ -871,7 +871,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {

View file

@ -45,8 +45,6 @@ public final class ThemeGridController: ViewController {
private let presentationDataPromise = Promise<PresentationData>()
private var presentationDataDisposable: Disposable?
private var searchContentNode: NavigationBarSearchContentNode?
private var isEmpty: Bool?
private var editingMode: Bool = false
@ -81,9 +79,6 @@ public final class ThemeGridController: ViewController {
self.scrollToTop = { [weak self] in
if let strongSelf = self {
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateExpansionProgress(1.0, animated: true)
}
strongSelf.controllerNode.scrollToTop()
}
}
@ -182,7 +177,6 @@ public final class ThemeGridController: ViewController {
} else {
uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in
if let strongSelf = self {
strongSelf.deactivateSearch(animated: false)
strongSelf.controllerNode.scrollToTop(animated: false)
}
if let controller = controller {
@ -392,9 +386,6 @@ public final class ThemeGridController: ViewController {
}
})
self.controllerNode.navigationBar = self.navigationBar
self.controllerNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch(animated: true)
}
self.controllerNode.requestWallpaperRemoval = { [weak self] in
if let self {
self.completion(.remove)
@ -403,10 +394,6 @@ public final class ThemeGridController: ViewController {
}
self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in
if let strongSelf = self {
if let searchContentNode = strongSelf.searchContentNode {
searchContentNode.updateGridVisibleContentOffset(offset)
}
var previousContentOffsetValue: CGFloat?
if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset {
previousContentOffsetValue = value
@ -427,12 +414,6 @@ public final class ThemeGridController: ViewController {
strongSelf.previousContentOffset = offset
}
}
self.controllerNode.gridNode.scrollingCompleted = { [weak self] in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
let _ = strongSelf.controllerNode.fixNavigationSearchableGridNodeScrolling(searchNode: searchContentNode)
}
}
self._ready.set(self.controllerNode.ready.get())
@ -492,34 +473,6 @@ public final class ThemeGridController: ViewController {
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, transition: transition)
}
func activateSearch() {
if self.displayNavigationBar {
let _ = (self.controllerNode.ready.get()
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
guard let strongSelf = self else {
return
}
if let scrollToTop = strongSelf.scrollToTop {
scrollToTop()
}
if let searchContentNode = strongSelf.searchContentNode {
strongSelf.controllerNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
}
strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
})
}
}
func deactivateSearch(animated: Bool) {
if !self.displayNavigationBar {
self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
if let searchContentNode = self.searchContentNode {
self.controllerNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
}
}
}
@objc func editPressed() {
self.editingMode = true
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))

View file

@ -162,7 +162,6 @@ final class ThemeGridControllerNode: ASDisplayNode {
private let emptyStateUpdated: (Bool) -> Void
private let resetWallpapers: () -> Void
var requestDeactivateSearch: (() -> Void)?
var requestWallpaperRemoval: (() -> Void)?
let ready = ValuePromise<Bool>()
@ -958,75 +957,6 @@ final class ThemeGridControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
guard let (containerLayout, navigationBarHeight) = self.validLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ThemeGridSearchContentNode(context: context, openResult: { [weak self] result in
if let strongSelf = self {
strongSelf.presentPreviewController(.contextResult(result))
}
}), cancel: { [weak self] in
self?.requestDeactivateSearch?()
})
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
if isSearchBar {
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
} else {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
}
}
}, placeholder: placeholderNode)
}
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
self.searchDisplayController = nil
}
}
func fixNavigationSearchableGridNodeScrolling(searchNode: NavigationBarSearchContentNode) -> Bool {
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
let scrollToItem: GridNodeScrollToItem
let targetProgress: CGFloat
let duration: Double = 0.3
let curve = ContainedViewLayoutTransitionCurve.slide
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: curve)
let timingFunction = curve.timingFunction
let mediaTimingFunction = curve.mediaTimingFunction
if searchNode.expansionProgress < 0.6 {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(navigationBarSearchContentHeight), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
targetProgress = 0.0
} else {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
targetProgress = 1.0
}
let previousOffset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top)
searchNode.updateExpansionProgress(targetProgress, animated: true)
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in })
let offset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top) - previousOffset
self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.backgroundNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.separatorNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.colorItemNode.layer.animatePosition(from: self.colorItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.colorItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.galleryItemNode.layer.animatePosition(from: self.galleryItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.galleryItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
self.descriptionItemNode.layer.animatePosition(from: self.descriptionItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.descriptionItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
return true
}
return false
}
func scrollToTop(animated: Bool = true) {
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.contentNode.scrollToTop()

View file

@ -102,10 +102,6 @@ final class StarsTransactionsScreenComponent: Component {
private let scrollView: ScrollViewImpl
private var currentSelectedPanelId: AnyHashable?
private let navigationBackgroundView: BlurredBackgroundView
private let navigationSeparatorLayer: SimpleLayer
private let navigationSeparatorLayerContainer: SimpleLayer
private let scrollContainerView: UIView
@ -160,14 +156,6 @@ final class StarsTransactionsScreenComponent: Component {
private var cachedChevronImage: (UIImage, PresentationTheme)?
override init(frame: CGRect) {
self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
self.navigationBackgroundView.alpha = 0.0
self.navigationSeparatorLayer = SimpleLayer()
self.navigationSeparatorLayer.opacity = 0.0
self.navigationSeparatorLayerContainer = SimpleLayer()
self.navigationSeparatorLayerContainer.opacity = 0.0
self.scrollContainerView = UIView()
self.scrollView = ScrollViewImpl()
@ -191,11 +179,6 @@ final class StarsTransactionsScreenComponent: Component {
self.addSubview(self.scrollView)
self.scrollView.addSubview(self.scrollContainerView)
self.addSubview(self.navigationBackgroundView)
self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer)
self.layer.addSublayer(self.navigationSeparatorLayerContainer)
}
required init?(coder: NSCoder) {
@ -296,7 +279,6 @@ final class StarsTransactionsScreenComponent: Component {
var topContentOffset = self.scrollView.contentOffset.y
let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0
titleOffset = topContentOffset
let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta))
@ -317,15 +299,10 @@ final class StarsTransactionsScreenComponent: Component {
headerTransition.setScale(view: titleView, scale: titleScale)
}
let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut))
animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
let expansionDistance: CGFloat = 32.0
var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance
expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor))
transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor)
if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View {
panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition)
}
@ -435,18 +412,6 @@ final class StarsTransactionsScreenComponent: Component {
self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight)
self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight))
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame)
let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel))
transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame)
transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size))
self.backgroundColor = environment.theme.list.blocksBackgroundColor
var contentHeight: CGFloat = 0.0
@ -519,7 +484,7 @@ final class StarsTransactionsScreenComponent: Component {
UIColor(rgb: 0xfdd219)
],
particleColor: UIColor(rgb: 0xf9b004),
backgroundColor: environment.theme.list.blocksBackgroundColor
backgroundColor: nil
))
}
@ -532,7 +497,13 @@ final class StarsTransactionsScreenComponent: Component {
let starFrame = CGRect(origin: .zero, size: starSize)
if let starView = self.starView.view {
if starView.superview == nil {
self.insertSubview(starView, aboveSubview: self.scrollView)
if let navigationBar = environment.controller()?.navigationBar {
if let titleView = self.titleView.view, titleView.superview != nil {
navigationBar.view.insertSubview(starView, belowSubview: titleView)
} else {
navigationBar.view.insertSubview(starView, aboveSubview: navigationBar.backgroundView)
}
}
}
starTransition.setBounds(view: starView, bounds: starFrame)
}
@ -562,7 +533,9 @@ final class StarsTransactionsScreenComponent: Component {
)
if let titleView = self.titleView.view {
if titleView.superview == nil {
self.addSubview(titleView)
if let navigationBar = environment.controller()?.navigationBar {
navigationBar.view.insertSubview(titleView, aboveSubview: navigationBar.backgroundView)
}
}
starTransition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleSize))
}
@ -617,7 +590,13 @@ final class StarsTransactionsScreenComponent: Component {
if let topBalanceTitleView = self.topBalanceTitleView.view {
if topBalanceTitleView.superview == nil {
topBalanceTitleView.alpha = 0.0
self.addSubview(topBalanceTitleView)
if let navigationBar = environment.controller()?.navigationBar {
if let titleView = self.titleView.view, titleView.superview != nil {
navigationBar.view.insertSubview(topBalanceTitleView, aboveSubview: titleView)
} else {
navigationBar.view.insertSubview(topBalanceTitleView, aboveSubview: navigationBar.backgroundView)
}
}
}
starTransition.setFrame(view: topBalanceTitleView, frame: topBalanceTitleFrame)
}
@ -626,7 +605,13 @@ final class StarsTransactionsScreenComponent: Component {
if let topBalanceValueView = self.topBalanceValueView.view {
if topBalanceValueView.superview == nil {
topBalanceValueView.alpha = 0.0
self.addSubview(topBalanceValueView)
if let navigationBar = environment.controller()?.navigationBar {
if let titleView = self.titleView.view, titleView.superview != nil {
navigationBar.view.insertSubview(topBalanceValueView, aboveSubview: titleView)
} else {
navigationBar.view.insertSubview(topBalanceValueView, aboveSubview: navigationBar.backgroundView)
}
}
}
starTransition.setFrame(view: topBalanceValueView, frame: topBalanceValueFrame)
}
@ -638,7 +623,13 @@ final class StarsTransactionsScreenComponent: Component {
if let topBalanceIconView = self.topBalanceIconView.view {
if topBalanceIconView.superview == nil {
topBalanceIconView.alpha = 0.0
self.addSubview(topBalanceIconView)
if let navigationBar = environment.controller()?.navigationBar {
if let titleView = self.titleView.view, titleView.superview != nil {
navigationBar.view.insertSubview(topBalanceIconView, aboveSubview: titleView)
} else {
navigationBar.view.insertSubview(topBalanceIconView, aboveSubview: navigationBar.backgroundView)
}
}
}
starTransition.setFrame(view: topBalanceIconView, frame: topBalanceIconFrame)
}

View file

@ -259,7 +259,7 @@ public final class NavigationSearchView: UIView {
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:))))
close.background.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:))))
close.background.contentView.addSubview(close.icon)
self.insertSubview(close.background, at: 0)

View file

@ -4630,7 +4630,8 @@ extension ChatControllerImpl {
context: self.context,
mediaPlayback: mediaPlayback,
liveLocationMode: liveLocationMode,
groupCalls: groupCallPanelSource
groupCalls: groupCallPanelSource,
chatListNotices: false
)
self.globalControlPanelsContext = globalControlPanelsContext
self.globalControlPanelsContextStateDisposable = (globalControlPanelsContext.state

View file

@ -429,6 +429,7 @@ extension ChatControllerImpl {
return
}
if messageIds.count == 1, let message = messages.values.compactMap({ $0 }).first, let repeatAttribute = message.attributes.first(where: { $0 is ScheduledRepeatAttribute }) as? ScheduledRepeatAttribute {
let commit = { [weak self] in
guard let self else {

View file

@ -1533,7 +1533,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
)
headerPanelsSize = headerPanelsSizeValue
floatingTopicsPanelInsets.top += 8.0 + headerPanelsSizeValue.height
floatingTopicsPanelInsets.top += headerPanelsSizeValue.height
} else if let headerPanelsView = self.headerPanelsView {
self.headerPanelsView = nil
if let headerPanelsComponentView = headerPanelsView.view {
@ -2387,7 +2387,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
topBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .top, size: 40.0),
blur: true,
blur: false,
containerSize: wallpaperBounds.size,
transition: transition
)
@ -2449,7 +2449,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true)
if let headerPanelsComponentView = self.headerPanelsView?.view, let headerPanelsSize {
let headerPanelsFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset + 8.0), size: headerPanelsSize)
let headerPanelsFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: headerPanelsSize)
var headerPanelsTransition = ComponentTransition(transition)
if headerPanelsComponentView.superview == nil {
headerPanelsTransition.animateAlpha(view: headerPanelsComponentView, from: 0.0, to: 1.0)
@ -2457,7 +2457,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.floatingTopicsPanelContainer.view.addSubview(headerPanelsComponentView)
}
headerPanelsTransition.setFrame(view: headerPanelsComponentView, frame: headerPanelsFrame)
sidePanelTopInset += 8.0 + headerPanelsSize.height
sidePanelTopInset += headerPanelsSize.height
}
let floatingTopicsPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height))

View file

@ -291,7 +291,6 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
}, dismissNotice: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {

View file

@ -573,7 +573,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
rightControlsBackgroundFrame.origin.x -= rightControlsBackgroundFrame.width
transition.setFrame(view: self.rightControlsBackgroundView, frame: rightControlsBackgroundFrame)
self.rightControlsBackgroundView.update(size: rightControlsBackgroundFrame.size, cornerRadius: rightControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition)
transition.setAlpha(view: self.rightControlsBackgroundView, alpha: rightControlsRect.isEmpty ? 0.0 : 1.0)
self.rightControlsBackgroundView.isHidden = rightControlsRect.isEmpty
return height
}

View file

@ -175,8 +175,6 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
},
openStarsTopup: { _ in
},
dismissNotice: { _ in
},
editPeer: { _ in
},
openWebApp: { _ in

View file

@ -3510,7 +3510,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
private func updateNavigationButtons() {
if case .attachMenu = self.source {
let barButtonSize = CGSize(width: 40.0, height: 40.0)
let barButtonSize = CGSize(width: 44.0, height: 44.0)
let closeComponent: AnyComponentWithIdentity<Empty> = AnyComponentWithIdentity(
id: "close",
component: AnyComponent(GlassBarButtonComponent(