diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index e6863b84f5..0000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -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": [] - } -} diff --git a/.gitignore b/.gitignore index bf06cade52..3b9c2f5f79 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ Telegram.LSP.json **/.build/** spm-files xcode-files -.bsp/** \ No newline at end of file +.bsp/** +/.claude/ diff --git a/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift b/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift index 93d01a4845..7b968b1835 100644 --- a/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift +++ b/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift @@ -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", diff --git a/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift b/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift index 119e894d17..1ee9da3d62 100644 --- a/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift +++ b/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift @@ -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) diff --git a/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift b/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift index fc47ecaa1f..30d0bb3c3e 100644 --- a/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift +++ b/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift @@ -20,15 +20,48 @@ class TargetBuilder { self.symlinkManager = symlinkManager } + // Track which modules are header-only (have no linkable code) + private var headerOnlyModules: Set = [] + // 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 = [] // 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 = [] // 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) -> Set { + 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 = [] - // 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() + 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 + } } diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index b1ef15533d..a6069636be 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -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() } diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index c759b95527..d2a2bc2b4c 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -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) diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index bbf3402994..80da4dd360 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -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", diff --git a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift index 9b6974a7ba..0a7e58172a 100644 --- a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift +++ b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift @@ -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) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 67b635c904..f433190a79 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -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 } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 522dc16688..5c973e677d 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -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, diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 52c98a6c3c..ef01564b92 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -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]) { diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c3854ee848..dae74fd531 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -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() 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: { diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index 7d52bddb6a..054acc955c 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -15,7 +15,7 @@ protocol ChatListSearchPaneNode: ASDisplayNode { var isReady: Signal { 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] { diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 2cf134e227..6ca1a33b87 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -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: { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index ad864f6be2..89e700e648 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -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(false, ignoreRepeated: true) - private let suggestedChatListNotice = Promise(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(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 = .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 = 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 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 @@ -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 in + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal 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 } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index dfc3361882..fe3c08d1f0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -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) } diff --git a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift index 5f019bc5ff..3c8188796b 100644 --- a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift +++ b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift @@ -410,7 +410,7 @@ public final class CombinedComponentContext { 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 { return self.context.environment @@ -425,7 +425,7 @@ public final class CombinedComponentContext { 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 { } 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) diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index 805506728a..b7e025bfcf 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -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) diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 901b768ce2..da5c6206f7 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -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) } diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 67f534a4cf..2f81e431cc 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -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)? = 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( diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 99c8035ac8..c094bef0b0 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -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 } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 5c08c086af..cef4c0d501 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -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) diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 10ac002792..f889d9aa30 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -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) diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 46657a20bb..1babb42202 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -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 { diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index 7c57b565c1..756a3d95f0 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -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", diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift index 52ff8fb04f..c661eab9f0 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift @@ -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 diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift index d0e057565e..302e762c5c 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift @@ -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() { diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 0116746e4a..1aec3ba690 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -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 } diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift index 6f63653114..46469ec4f3 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift @@ -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? { diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift index 16c072a390..ec265447eb 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift @@ -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) } } - diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 71f3a6e4e6..3d9a74e164 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -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) diff --git a/submodules/SearchUI/BUILD b/submodules/SearchUI/BUILD index 7210ffaa38..809937bf8d 100644 --- a/submodules/SearchUI/BUILD +++ b/submodules/SearchUI/BUILD @@ -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", diff --git a/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift b/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift index 9d202507c4..d5cfcfc57b 100644 --- a/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift +++ b/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift @@ -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 -} diff --git a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift index 4997880732..58814af64a 100644 --- a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift +++ b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift @@ -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 { diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 595c4adbeb..213ea50681 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -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 - let exceptionsList: Signal - let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError> - let privacySettings: Signal - let hasTwoStepAuth: Signal - let twoStepAuthData: Signal - let activeSessionsContext: Signal - let webSessionsContext: Signal - - private var updateActivity: ((Bool) -> Void)? - private var activity: ValuePromise = 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, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasTwoStepAuth: Signal, twoStepAuthData: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { - 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 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 diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 44ab48af9e..b9b032765c 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -228,7 +228,6 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 9232eaf461..a0afa08c37 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -377,7 +377,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/TelegramCore/BUILD b/submodules/TelegramCore/BUILD index fe9bc15dbf..53c9a721c6 100644 --- a/submodules/TelegramCore/BUILD +++ b/submodules/TelegramCore/BUILD @@ -25,6 +25,7 @@ swift_library( "//submodules/Emoji", "//submodules/TelegramCore/FlatBuffers", "//submodules/TelegramCore/FlatSerialization", + "//submodules/MurMurHash32" ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift index 5cc4393c78..15a5670fe2 100644 --- a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift +++ b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift @@ -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 diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index 59b5b88acf..465295f40e 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -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( diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index ba8e6418c3..c84ec35a84 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -901,8 +901,6 @@ public final class ChatInlineSearchResultsListComponent: Component { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift index 4108976266..4637f89e3a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift @@ -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)) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/BUILD b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/BUILD new file mode 100644 index 0000000000..cd53921f16 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/BUILD @@ -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", + ], +) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListHeaderNoticeComponent.swift b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListHeaderNoticeComponent.swift new file mode 100644 index 0000000000..f35967279e --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListHeaderNoticeComponent.swift @@ -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, 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, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift similarity index 98% rename from submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift rename to submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift index eae620ae40..420975850f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift @@ -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: diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index c4e968e8ce..8fd0a8fe13 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -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)) diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift index 6b012d1097..78bea812ea 100644 --- a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift @@ -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) } } } diff --git a/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD b/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD index 19eb7cab34..a32c2d380b 100644 --- a/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD +++ b/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD @@ -15,6 +15,8 @@ swift_library( "//submodules/AccountContext", "//submodules/TelegramUIPreferences", "//submodules/TelegramCallsUI", + "//submodules/Display", + "//submodules/UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift index b6ac0ac9b0..176bccca06 100644 --- a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift +++ b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift @@ -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 = .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(nil) + + let suggestedChatListNoticeSignal: Signal = 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 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 @@ -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) + } + } } diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD index 8b9993c933..442a3f6aaf 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD @@ -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", diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift index 7f275d2601..4b215b5eef 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift @@ -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? { diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift index 408019f90b..60f4873920 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift @@ -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) } } - diff --git a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift index c175e8dede..6879454e18 100644 --- a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift +++ b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift @@ -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() { diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift index 7d84c646a4..b36e1d1a80 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift index a991e7453f..2e46351ae0 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index 815b7bfaa2..80f58c4fec 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index d530e48bfa..95d0bc1d9e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 74bcf76ef4..6e6048012d 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -205,8 +205,6 @@ final class GreetingMessageListItemComponent: Component { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 3b987bb777..2059693a5e 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 9f7668774b..b4b9057333 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -871,7 +871,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift index 6921f99619..83c0c30381 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift @@ -45,8 +45,6 @@ public final class ThemeGridController: ViewController { private let presentationDataPromise = Promise() 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)) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index 039b41bb87..5d59dd666e 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -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() @@ -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() diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 07bed879a4..9b16a84d72 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -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) } diff --git a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift index 4621d859b5..1454098642 100644 --- a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift +++ b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index b531ec14e3..2bc4a62c3b 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index bbd1aa1113..e289e8722a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 2caadea76c..a8d6967025 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -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)) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 26b8afe92c..b0148af511 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -291,7 +291,6 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe }, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index 952ecd5d74..84622b6005 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 457fd8a889..144f600855 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -175,8 +175,6 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index ca9010bd6b..d2fc494df5 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -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 = AnyComponentWithIdentity( id: "close", component: AnyComponent(GlassBarButtonComponent(