mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Glass
This commit is contained in:
parent
1969b1b280
commit
1ee7268488
72 changed files with 1645 additions and 934 deletions
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(swift build)",
|
||||
"Bash(.build/debug/makeproject:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(grep:*)",
|
||||
"Skill(superpowers:brainstorming)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(python3:*)",
|
||||
"Skill(superpowers:writing-plans)",
|
||||
"Skill(superpowers:subagent-driven-development)",
|
||||
"Bash(test:*)",
|
||||
"Bash(swift package:*)",
|
||||
"Bash(.build/debug/MakeProject:*)",
|
||||
"Bash(./build-system/MakeProject/.build/debug/MakeProject)",
|
||||
"Bash(xcodebuild:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -75,4 +75,5 @@ Telegram.LSP.json
|
|||
**/.build/**
|
||||
spm-files
|
||||
xcode-files
|
||||
.bsp/**
|
||||
.bsp/**
|
||||
/.claude/
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -20,15 +20,48 @@ class TargetBuilder {
|
|||
self.symlinkManager = symlinkManager
|
||||
}
|
||||
|
||||
// Track which modules are header-only (have no linkable code)
|
||||
private var headerOnlyModules: Set<String> = []
|
||||
// Track which modules are static library collections (.a files)
|
||||
private var staticLibraryModules: [String: [String]] = [:] // module name -> list of .a file paths
|
||||
|
||||
func isHeaderOnlyModule(_ name: String) -> Bool {
|
||||
return headerOnlyModules.contains(name)
|
||||
}
|
||||
|
||||
func isStaticLibraryModule(_ name: String) -> Bool {
|
||||
return staticLibraryModules[name] != nil
|
||||
}
|
||||
|
||||
func getStaticLibraries(for name: String) -> [String] {
|
||||
return staticLibraryModules[name] ?? []
|
||||
}
|
||||
|
||||
func buildTarget(for module: ModuleDefinition, allModules: [String: ModuleDefinition]) throws -> PBXNativeTarget? {
|
||||
guard let moduleType = ModuleType(from: module) else {
|
||||
print("Warning: Unknown module type \(module.type) for \(module.name)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip empty modules
|
||||
let sourceFiles = module.sources.filter { !$0.hasSuffix(".a") }
|
||||
if sourceFiles.isEmpty && moduleType != .xcframework {
|
||||
// Check for static library modules (only .a files)
|
||||
let staticLibs = module.sources.filter { $0.hasSuffix(".a") }
|
||||
let nonStaticLibs = module.sources.filter { !$0.hasSuffix(".a") }
|
||||
let isStaticLibOnly = !staticLibs.isEmpty && nonStaticLibs.allSatisfy { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") }
|
||||
|
||||
if isStaticLibOnly && moduleType != .xcframework {
|
||||
// This is a static library module - track its .a files but don't create a framework
|
||||
staticLibraryModules[module.name] = staticLibs
|
||||
return nil // Don't create a target, just track the static libs
|
||||
}
|
||||
|
||||
// Check if module is header-only (only header files, no real sources)
|
||||
let sourceFiles = module.sources.filter { source in
|
||||
!source.hasSuffix(".a") && !source.hasSuffix(".h") && !source.hasSuffix(".hpp")
|
||||
}
|
||||
let isHeaderOnly = sourceFiles.isEmpty && moduleType != .xcframework
|
||||
|
||||
// Skip modules with no sources at all
|
||||
if module.sources.isEmpty && moduleType != .xcframework {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +69,12 @@ class TargetBuilder {
|
|||
case .xcframework:
|
||||
return try buildXCFrameworkTarget(for: module)
|
||||
case .swiftLibrary, .objcLibrary, .ccLibrary:
|
||||
return try buildFrameworkTarget(for: module, moduleType: moduleType)
|
||||
if isHeaderOnly {
|
||||
headerOnlyModules.insert(module.name)
|
||||
return try buildHeaderOnlyFrameworkTarget(for: module)
|
||||
} else {
|
||||
return try buildFrameworkTarget(for: module, moduleType: moduleType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +99,13 @@ class TargetBuilder {
|
|||
|
||||
// Create symlinks and file references
|
||||
var sourceRefs: [PBXFileReference] = []
|
||||
var headerRefs: [PBXFileReference] = []
|
||||
var publicHeaderRefs: [PBXFileReference] = []
|
||||
var seenPublicHeaderNames: Set<String> = [] // Track header filenames to avoid duplicates in headers build phase
|
||||
|
||||
// Determine public header detection:
|
||||
// 1. If hdrs is provided, use those (explicit public headers)
|
||||
// 2. Otherwise, headers in includes directories are public
|
||||
let explicitPublicHeaders = Set(module.hdrs ?? [])
|
||||
|
||||
let allSourceFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? [])
|
||||
|
||||
|
|
@ -99,11 +143,33 @@ class TargetBuilder {
|
|||
fileName: fileName
|
||||
)
|
||||
|
||||
if source.hasSuffix(".h") || source.hasSuffix(".hpp") {
|
||||
headerRefs.append(fileRef)
|
||||
} else if !source.hasSuffix(".inc") {
|
||||
let isHeader = source.hasSuffix(".h") || source.hasSuffix(".hpp")
|
||||
|
||||
// Determine if this is a public header:
|
||||
// 1. Explicitly in hdrs array
|
||||
// 2. Located in an includes directory
|
||||
let isPublicHeader: Bool
|
||||
if !isHeader {
|
||||
isPublicHeader = false
|
||||
} else if !explicitPublicHeaders.isEmpty {
|
||||
// If hdrs is provided, only those are public
|
||||
isPublicHeader = explicitPublicHeaders.contains(source)
|
||||
} else {
|
||||
// Headers in include directories are public (handles bazel-out paths)
|
||||
isPublicHeader = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes)
|
||||
}
|
||||
|
||||
if isPublicHeader {
|
||||
// Skip duplicate header filenames to avoid "multiple commands produce" errors
|
||||
if !seenPublicHeaderNames.contains(fileName) {
|
||||
seenPublicHeaderNames.insert(fileName)
|
||||
publicHeaderRefs.append(fileRef)
|
||||
}
|
||||
} else if !source.hasSuffix(".inc") && !isHeader {
|
||||
// Source files (not headers)
|
||||
sourceRefs.append(fileRef)
|
||||
}
|
||||
// Private headers are not added to any build phase
|
||||
}
|
||||
|
||||
// Build phases
|
||||
|
|
@ -120,17 +186,23 @@ class TargetBuilder {
|
|||
pbxproj.add(object: sourcesBuildPhase)
|
||||
buildPhases.append(sourcesBuildPhase)
|
||||
|
||||
// Headers build phase for ObjC/C++
|
||||
// Generate modulemap for ObjC/C++ modules (SPM-style explicit headers)
|
||||
var modulemapPath: Path? = nil
|
||||
if moduleType == .objcLibrary || moduleType == .ccLibrary {
|
||||
let headersBuildPhase = PBXHeadersBuildPhase(
|
||||
files: headerRefs.map { ref in
|
||||
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
|
||||
pbxproj.add(object: buildFile)
|
||||
return buildFile
|
||||
}
|
||||
)
|
||||
pbxproj.add(object: headersBuildPhase)
|
||||
buildPhases.append(headersBuildPhase)
|
||||
modulemapPath = try generateModulemap(for: module)
|
||||
|
||||
// Headers build phase with public headers - needed for ObjC #import to work
|
||||
if !publicHeaderRefs.isEmpty {
|
||||
let headersBuildPhase = PBXHeadersBuildPhase(
|
||||
files: publicHeaderRefs.map { ref in
|
||||
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
|
||||
pbxproj.add(object: buildFile)
|
||||
return buildFile
|
||||
}
|
||||
)
|
||||
pbxproj.add(object: headersBuildPhase)
|
||||
buildPhases.append(headersBuildPhase)
|
||||
}
|
||||
}
|
||||
|
||||
// Frameworks build phase
|
||||
|
|
@ -138,8 +210,8 @@ class TargetBuilder {
|
|||
pbxproj.add(object: frameworksBuildPhase)
|
||||
buildPhases.append(frameworksBuildPhase)
|
||||
|
||||
// Create target
|
||||
let configList = createConfigurationList(for: module, isXCFramework: false)
|
||||
// Create target with custom modulemap if generated
|
||||
let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath)
|
||||
|
||||
let target = PBXNativeTarget(
|
||||
name: module.name,
|
||||
|
|
@ -155,6 +227,110 @@ class TargetBuilder {
|
|||
return target
|
||||
}
|
||||
|
||||
/// Build a framework target for header-only modules (just headers + modulemap, no sources)
|
||||
private func buildHeaderOnlyFrameworkTarget(for module: ModuleDefinition) throws -> PBXNativeTarget {
|
||||
// Create group for module
|
||||
let moduleGroup = try getOrCreateGroup(for: module.path)
|
||||
|
||||
// Symlink header files and create file references
|
||||
var publicHeaderRefs: [PBXFileReference] = []
|
||||
var seenPublicHeaderNames: Set<String> = [] // Track header filenames to avoid duplicates
|
||||
let allHeaders = module.sources.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") } + (module.hdrs ?? []) + (module.textualHdrs ?? [])
|
||||
|
||||
for source in allHeaders {
|
||||
let sourcePath = Path(source)
|
||||
let fileName = sourcePath.lastComponent
|
||||
let relativeToModule = relativePathInModule(source: source, modulePath: module.path)
|
||||
|
||||
// Create parent group
|
||||
let parentPath = (Path(module.path) + Path(relativeToModule).parent()).string
|
||||
let parentGroup = try getOrCreateGroup(for: parentPath)
|
||||
|
||||
// Create symlink
|
||||
let symlinkPath = outputDir + module.path + relativeToModule
|
||||
try symlinkManager.createDirectory(symlinkPath.parent())
|
||||
try symlinkManager.createSymlink(from: Path(source), to: symlinkPath)
|
||||
|
||||
// Create file reference
|
||||
let fileType = lastKnownFileType(for: fileName)
|
||||
let fileRef = PBXFileReference(
|
||||
sourceTree: .group,
|
||||
name: fileName,
|
||||
lastKnownFileType: fileType,
|
||||
path: fileName
|
||||
)
|
||||
pbxproj.add(object: fileRef)
|
||||
parentGroup.children.append(fileRef)
|
||||
|
||||
// Check if it's a public header (in includes directories)
|
||||
// Use helper that properly handles bazel-out paths
|
||||
let isPublic = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes)
|
||||
if isPublic {
|
||||
// Skip duplicate header filenames to avoid "multiple commands produce" errors
|
||||
if !seenPublicHeaderNames.contains(fileName) {
|
||||
seenPublicHeaderNames.insert(fileName)
|
||||
publicHeaderRefs.append(fileRef)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate modulemap for this header-only module
|
||||
let modulemapPath = try generateModulemap(for: module)
|
||||
|
||||
// Create build phases
|
||||
var buildPhases: [PBXBuildPhase] = []
|
||||
|
||||
// Headers build phase with public headers - needed for ObjC #import to work
|
||||
if !publicHeaderRefs.isEmpty {
|
||||
let headersBuildPhase = PBXHeadersBuildPhase(
|
||||
files: publicHeaderRefs.map { ref in
|
||||
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
|
||||
pbxproj.add(object: buildFile)
|
||||
return buildFile
|
||||
}
|
||||
)
|
||||
pbxproj.add(object: headersBuildPhase)
|
||||
buildPhases.append(headersBuildPhase)
|
||||
}
|
||||
|
||||
// Empty frameworks phase (needed for Xcode, but won't link anything)
|
||||
let frameworksBuildPhase = PBXFrameworksBuildPhase(files: [])
|
||||
pbxproj.add(object: frameworksBuildPhase)
|
||||
buildPhases.append(frameworksBuildPhase)
|
||||
|
||||
// Create target with custom modulemap
|
||||
let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath)
|
||||
|
||||
let target = PBXNativeTarget(
|
||||
name: module.name,
|
||||
buildConfigurationList: configList,
|
||||
buildPhases: buildPhases,
|
||||
productName: module.name,
|
||||
productType: .framework
|
||||
)
|
||||
pbxproj.add(object: target)
|
||||
project.targets.append(target)
|
||||
targetsByName[module.name] = target
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
/// Collect all transitive dependencies for a module
|
||||
private func collectAllDependencies(for moduleName: String, modules: [String: ModuleDefinition], visited: inout Set<String>) -> Set<String> {
|
||||
guard !visited.contains(moduleName) else { return [] }
|
||||
visited.insert(moduleName)
|
||||
|
||||
guard let module = modules[moduleName], let deps = module.deps else {
|
||||
return []
|
||||
}
|
||||
|
||||
var allDeps = Set(deps)
|
||||
for depName in deps {
|
||||
allDeps.formUnion(collectAllDependencies(for: depName, modules: modules, visited: &visited))
|
||||
}
|
||||
return allDeps
|
||||
}
|
||||
|
||||
func wireUpDependencies(modules: [String: ModuleDefinition]) throws {
|
||||
for (name, module) in modules {
|
||||
guard let target = targetsByName[name],
|
||||
|
|
@ -165,13 +341,88 @@ class TargetBuilder {
|
|||
continue
|
||||
}
|
||||
|
||||
for depName in deps {
|
||||
guard let depTarget = targetsByName[depName] else { continue }
|
||||
// Track all frameworks we've added to avoid duplicates
|
||||
var linkedFrameworks: Set<String> = []
|
||||
|
||||
// Add target dependency
|
||||
let dependency = PBXTargetDependency(target: depTarget)
|
||||
pbxproj.add(object: dependency)
|
||||
target.dependencies.append(dependency)
|
||||
// Add target dependency (only for direct deps)
|
||||
for depName in deps {
|
||||
if let depTarget = targetsByName[depName] {
|
||||
let dependency = PBXTargetDependency(target: depTarget)
|
||||
pbxproj.add(object: dependency)
|
||||
target.dependencies.append(dependency)
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all transitive dependencies to link
|
||||
var visited = Set<String>()
|
||||
let allDeps = collectAllDependencies(for: name, modules: modules, visited: &visited)
|
||||
|
||||
// Collect header paths from ALL dependencies (direct and transitive)
|
||||
var depHeaderPaths: [String] = []
|
||||
for depName in allDeps {
|
||||
if let depModule = modules[depName] {
|
||||
depHeaderPaths.append(contentsOf: exportedHeaderPaths(for: depModule))
|
||||
}
|
||||
}
|
||||
|
||||
// Link all dependencies (direct and transitive) that have targets
|
||||
for depName in allDeps {
|
||||
// Skip if already linked
|
||||
guard !linkedFrameworks.contains(depName) else { continue }
|
||||
|
||||
// Check if this is a static library module - link .a files directly
|
||||
if isStaticLibraryModule(depName) {
|
||||
// Link static libraries directly
|
||||
for libPath in getStaticLibraries(for: depName) {
|
||||
let libName = Path(libPath).lastComponent
|
||||
// Use absolute path to the static library in bazel-out
|
||||
let absolutePath = "$(SRCROOT)/../\(libPath)"
|
||||
let libRef = PBXFileReference(
|
||||
sourceTree: .group,
|
||||
name: libName,
|
||||
lastKnownFileType: "archive.ar",
|
||||
path: absolutePath
|
||||
)
|
||||
pbxproj.add(object: libRef)
|
||||
let buildFile = PBXBuildFile(file: libRef)
|
||||
pbxproj.add(object: buildFile)
|
||||
frameworksPhase.files?.append(buildFile)
|
||||
}
|
||||
linkedFrameworks.insert(depName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip header-only modules (no framework to link)
|
||||
if isHeaderOnlyModule(depName) { continue }
|
||||
|
||||
// Regular framework dependency
|
||||
guard targetsByName[depName] != nil else { continue }
|
||||
|
||||
linkedFrameworks.insert(depName)
|
||||
let frameworkRef = PBXFileReference(
|
||||
sourceTree: .buildProductsDir,
|
||||
name: "\(depName).framework",
|
||||
lastKnownFileType: "wrapper.framework",
|
||||
path: "\(depName).framework"
|
||||
)
|
||||
pbxproj.add(object: frameworkRef)
|
||||
let buildFile = PBXBuildFile(file: frameworkRef)
|
||||
pbxproj.add(object: buildFile)
|
||||
frameworksPhase.files?.append(buildFile)
|
||||
}
|
||||
|
||||
// Update build configurations with dependency paths
|
||||
if !depHeaderPaths.isEmpty || !deps.isEmpty {
|
||||
for config in target.buildConfigurationList?.buildConfigurations ?? [] {
|
||||
// Add header search paths
|
||||
if !depHeaderPaths.isEmpty {
|
||||
let existing = config.buildSettings["HEADER_SEARCH_PATHS"] as? String ?? "$(inherited)"
|
||||
config.buildSettings["HEADER_SEARCH_PATHS"] = existing + " " + depHeaderPaths.joined(separator: " ")
|
||||
}
|
||||
// Ensure framework and module search paths include built products
|
||||
config.buildSettings["FRAMEWORK_SEARCH_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)"
|
||||
config.buildSettings["SWIFT_INCLUDE_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)"
|
||||
}
|
||||
}
|
||||
|
||||
// Add SDK frameworks
|
||||
|
|
@ -192,13 +443,32 @@ class TargetBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the header search paths that this module exports to its dependents
|
||||
private func exportedHeaderPaths(for module: ModuleDefinition) -> [String] {
|
||||
var paths: [String] = []
|
||||
if let includes = module.includes, !includes.isEmpty {
|
||||
for inc in includes {
|
||||
if inc == "." {
|
||||
paths.append("$(SRCROOT)/\(module.path)")
|
||||
} else {
|
||||
paths.append("$(SRCROOT)/\(module.path)/\(inc)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No includes specified, export module's own path
|
||||
paths.append("$(SRCROOT)/\(module.path)")
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func getTarget(named name: String) -> PBXNativeTarget? {
|
||||
return targetsByName[name]
|
||||
}
|
||||
|
||||
private func createConfigurationList(for module: ModuleDefinition, isXCFramework: Bool) -> XCConfigurationList {
|
||||
let debugSettings = createBuildSettings(for: module, isDebug: true, isXCFramework: isXCFramework)
|
||||
let releaseSettings = createBuildSettings(for: module, isDebug: false, isXCFramework: isXCFramework)
|
||||
|
||||
private func createConfigurationList(for module: ModuleDefinition, isXCFramework: Bool, modulemapPath: Path? = nil) -> XCConfigurationList {
|
||||
let debugSettings = createBuildSettings(for: module, isDebug: true, isXCFramework: isXCFramework, modulemapPath: modulemapPath)
|
||||
let releaseSettings = createBuildSettings(for: module, isDebug: false, isXCFramework: isXCFramework, modulemapPath: modulemapPath)
|
||||
|
||||
let debugConfig = XCBuildConfiguration(name: "Debug", buildSettings: debugSettings)
|
||||
let releaseConfig = XCBuildConfiguration(name: "Release", buildSettings: releaseSettings)
|
||||
|
|
@ -215,7 +485,7 @@ class TargetBuilder {
|
|||
return configList
|
||||
}
|
||||
|
||||
private func createBuildSettings(for module: ModuleDefinition, isDebug: Bool, isXCFramework: Bool) -> BuildSettings {
|
||||
private func createBuildSettings(for module: ModuleDefinition, isDebug: Bool, isXCFramework: Bool, modulemapPath: Path? = nil) -> BuildSettings {
|
||||
var settings: BuildSettings = [
|
||||
"PRODUCT_NAME": "$(TARGET_NAME)",
|
||||
"PRODUCT_BUNDLE_IDENTIFIER": "org.telegram.\(module.name)",
|
||||
|
|
@ -229,6 +499,7 @@ class TargetBuilder {
|
|||
// Swift settings
|
||||
if moduleType == .swiftLibrary {
|
||||
settings["SWIFT_VERSION"] = "5.0"
|
||||
settings["DEFINES_MODULE"] = "YES"
|
||||
|
||||
if let copts = module.copts, !copts.isEmpty {
|
||||
let filtered = copts.filter { !$0.hasPrefix("-warnings") }
|
||||
|
|
@ -244,12 +515,14 @@ class TargetBuilder {
|
|||
|
||||
// C/ObjC settings
|
||||
if moduleType == .objcLibrary || moduleType == .ccLibrary {
|
||||
// Always suppress deprecated warnings (e.g., OSSpinLock) and don't treat warnings as errors
|
||||
var cflags = ["-Wno-deprecated-declarations"]
|
||||
if let copts = module.copts, !copts.isEmpty {
|
||||
let filtered = copts.filter { !$0.hasPrefix("-warnings") && !$0.hasPrefix("-W") }
|
||||
if !filtered.isEmpty {
|
||||
settings["OTHER_CFLAGS"] = "$(inherited) " + filtered.joined(separator: " ")
|
||||
}
|
||||
cflags.append(contentsOf: filtered)
|
||||
}
|
||||
settings["OTHER_CFLAGS"] = "$(inherited) " + cflags.joined(separator: " ")
|
||||
settings["GCC_TREAT_WARNINGS_AS_ERRORS"] = "NO"
|
||||
|
||||
if let cxxopts = module.cxxopts, !cxxopts.isEmpty {
|
||||
let filtered = cxxopts.filter { !$0.hasPrefix("-std=") }
|
||||
|
|
@ -262,15 +535,22 @@ class TargetBuilder {
|
|||
settings["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) " + defines.joined(separator: " ")
|
||||
}
|
||||
|
||||
if let includes = module.includes, !includes.isEmpty {
|
||||
let paths = includes.map { inc -> String in
|
||||
if inc == "." {
|
||||
return "$(SRCROOT)/\(module.path)"
|
||||
}
|
||||
return "$(SRCROOT)/\(module.path)/\(inc)"
|
||||
// Always include module's own path for header search
|
||||
var headerPaths = ["$(SRCROOT)/\(module.path)"]
|
||||
if let includes = module.includes {
|
||||
for inc in includes where inc != "." {
|
||||
headerPaths.append("$(SRCROOT)/\(module.path)/\(inc)")
|
||||
}
|
||||
settings["HEADER_SEARCH_PATHS"] = "$(inherited) " + paths.joined(separator: " ")
|
||||
}
|
||||
settings["HEADER_SEARCH_PATHS"] = "$(inherited) " + headerPaths.joined(separator: " ")
|
||||
|
||||
// Use custom modulemap if provided (SPM-style)
|
||||
if let modmap = modulemapPath {
|
||||
// Get relative path from SRCROOT
|
||||
let relativePath = modmap.string.replacingOccurrences(of: outputDir.string + "/", with: "")
|
||||
settings["MODULEMAP_FILE"] = "$(SRCROOT)/\(relativePath)"
|
||||
}
|
||||
settings["DEFINES_MODULE"] = "YES"
|
||||
}
|
||||
|
||||
return settings
|
||||
|
|
@ -352,4 +632,110 @@ class TargetBuilder {
|
|||
default: return "text"
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the relative path within a module from a source path
|
||||
/// Handles both regular paths (submodules/Foo/...) and bazel-out paths (bazel-out/.../bin/submodules/Foo/...)
|
||||
private func relativePathInModule(source: String, modulePath: String) -> String {
|
||||
if source.hasPrefix(modulePath + "/") {
|
||||
return String(source.dropFirst(modulePath.count + 1))
|
||||
} else if source.hasPrefix("bazel-out/") {
|
||||
// Generated file - extract path after the module path portion
|
||||
if let range = source.range(of: modulePath + "/") {
|
||||
return String(source[range.upperBound...])
|
||||
} else {
|
||||
return Path(source).lastComponent
|
||||
}
|
||||
} else {
|
||||
return Path(source).lastComponent
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a source file is in one of the includes directories
|
||||
private func isInIncludesDirectory(source: String, modulePath: String, includes: [String]?) -> Bool {
|
||||
guard let includes = includes, !includes.isEmpty else {
|
||||
return false
|
||||
}
|
||||
let relative = relativePathInModule(source: source, modulePath: modulePath)
|
||||
return includes.contains { inc in
|
||||
if inc == "." {
|
||||
return true // All files in module are public
|
||||
} else {
|
||||
return relative.hasPrefix(inc + "/") || relative == inc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates an explicit module.modulemap for ObjC/C++ modules (SPM-style)
|
||||
/// Returns the path to the modulemap, or nil if no public headers
|
||||
func generateModulemap(for module: ModuleDefinition) throws -> Path? {
|
||||
let moduleType = ModuleType(from: module)
|
||||
guard moduleType == .objcLibrary || moduleType == .ccLibrary else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine public header prefix from includes
|
||||
let publicHeaderPrefix: String
|
||||
if let includes = module.includes, !includes.isEmpty {
|
||||
let firstInclude = includes[0]
|
||||
publicHeaderPrefix = firstInclude == "." ? "" : firstInclude
|
||||
} else {
|
||||
publicHeaderPrefix = ""
|
||||
}
|
||||
|
||||
// Determine public headers
|
||||
let explicitPublicHeaders = Set(module.hdrs ?? [])
|
||||
|
||||
let allFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? [])
|
||||
let allHeaders = allFiles.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") }
|
||||
|
||||
// Determine which headers are public
|
||||
let publicHeaders: [String]
|
||||
if !explicitPublicHeaders.isEmpty {
|
||||
publicHeaders = allHeaders.filter { explicitPublicHeaders.contains($0) }
|
||||
} else if module.includes != nil && !module.includes!.isEmpty {
|
||||
// Use helper that properly handles bazel-out paths
|
||||
publicHeaders = allHeaders.filter { header in
|
||||
isInIncludesDirectory(source: header, modulePath: module.path, includes: module.includes)
|
||||
}
|
||||
} else {
|
||||
publicHeaders = []
|
||||
}
|
||||
|
||||
guard !publicHeaders.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate explicit modulemap content (SPM-style)
|
||||
var content = "// module.modulemap for \(module.name)\n"
|
||||
content += "// Auto-generated - do not edit\n\n"
|
||||
content += "module \(module.name) {\n"
|
||||
|
||||
for header in publicHeaders {
|
||||
// Calculate the symlinked path - matches the symlink creation in buildFrameworkTarget
|
||||
// The symlink is at: outputDir + module.path + relativeToModule
|
||||
let relativeToModule = relativePathInModule(source: header, modulePath: module.path)
|
||||
let symlinkPath = outputDir + module.path + relativeToModule
|
||||
content += " header \"\(symlinkPath.string)\"\n"
|
||||
}
|
||||
|
||||
content += " export *\n"
|
||||
content += "}\n"
|
||||
|
||||
// Write modulemap to the public headers directory
|
||||
let modulemapDir: Path
|
||||
if !publicHeaderPrefix.isEmpty {
|
||||
modulemapDir = outputDir + module.path + publicHeaderPrefix
|
||||
} else {
|
||||
modulemapDir = outputDir + module.path
|
||||
}
|
||||
let modulemapPath = modulemapDir + "module.modulemap"
|
||||
|
||||
try modulemapDir.mkpath()
|
||||
try modulemapPath.write(content)
|
||||
|
||||
// Track this file so it doesn't get cleaned up
|
||||
symlinkManager.markFile(modulemapPath)
|
||||
|
||||
return modulemapPath
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]) {
|
||||
|
|
|
|||
|
|
@ -1696,7 +1696,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
private var emptyRecentAnimationNode: AnimatedStickerNode?
|
||||
private var emptyRecentAnimationSize = CGSize()
|
||||
|
||||
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
|
||||
private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)?
|
||||
|
||||
private let ready = Promise<Bool>()
|
||||
private var didSetReady: Bool = false
|
||||
|
|
@ -3495,7 +3495,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
self.interaction.openStories?(id, sourceNode.avatarNode)
|
||||
}
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
|
@ -4640,8 +4639,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
self.playlistStateAndType = nil
|
||||
}
|
||||
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
self.playlistLocation = playlistStateAndType?.1.playlistLocation
|
||||
|
|
@ -4758,10 +4757,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||
let hadValidLayout = self.currentParams != nil
|
||||
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
|
||||
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
|
||||
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.topInset != topInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
|
||||
self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData)
|
||||
|
||||
var topPanelHeight: CGFloat = 0.0
|
||||
if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType {
|
||||
|
|
@ -5035,9 +5034,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
|
||||
transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight)))
|
||||
|
||||
let topInset: CGFloat = topPanelHeight
|
||||
let topInset: CGFloat = topInset + topPanelHeight
|
||||
let overflowInset: CGFloat = 20.0
|
||||
let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
|
||||
let insets = UIEdgeInsets(top: topInset + topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
|
||||
|
||||
self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height))
|
||||
self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, key: !(self.searchQueryValue?.isEmpty ?? true) && self.key == .media ? .chats : self.key, hasSelection: self.selectedMessages != nil, transition: transition)
|
||||
|
|
@ -5480,8 +5479,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
strongSelf.emptyResultsButtonSubtitleText = nil
|
||||
}
|
||||
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
|
||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams {
|
||||
strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
if strongSelf.key == .downloads {
|
||||
|
|
@ -5783,7 +5782,6 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ protocol ChatListSearchPaneNode: ASDisplayNode {
|
|||
var isReady: Signal<Bool, NoError> { get }
|
||||
var isCurrent: Bool { get set }
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
|
||||
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
|
||||
func scrollToTop() -> Bool
|
||||
func cancelPreviewGestures()
|
||||
func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||
|
|
@ -32,21 +32,21 @@ final class ChatListSearchPaneWrapper {
|
|||
let key: ChatListSearchPaneKey
|
||||
let node: ChatListSearchPaneNode
|
||||
var isAnimatingOut: Bool = false
|
||||
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, PresentationData)?
|
||||
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, CGFloat, PresentationData)?
|
||||
|
||||
init(key: ChatListSearchPaneKey, node: ChatListSearchPaneNode) {
|
||||
self.key = key
|
||||
self.node = node
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||
if let (currentSize, currentSideInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
|
||||
if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
|
||||
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||
if let (currentSize, currentSideInset, currentTopInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
|
||||
if currentSize == size && currentSideInset == sideInset && currentTopInset == topInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.appliedParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
|
||||
self.node.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
|
||||
self.appliedParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData)
|
||||
self.node.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
|
||||
var isAdjacentLoadingEnabled = false
|
||||
|
||||
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])?
|
||||
private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])?
|
||||
|
||||
private(set) var currentPaneKey: ChatListSearchPaneKey?
|
||||
var pendingSwitchToPaneKey: ChatListSearchPaneKey?
|
||||
|
|
@ -251,8 +251,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
if self.currentPanes[key] != nil {
|
||||
self.currentPaneKey = key
|
||||
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
if case .apps = key {
|
||||
|
|
@ -261,8 +261,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
} else if self.pendingSwitchToPaneKey != key {
|
||||
self.pendingSwitchToPaneKey = key
|
||||
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
if case .apps = key {
|
||||
|
|
@ -275,7 +275,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
super.didLoad()
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||
guard let strongSelf = self, let (_, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
|
||||
guard let strongSelf = self, let (_, _, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else {
|
||||
return []
|
||||
}
|
||||
if index == 0 {
|
||||
|
|
@ -321,7 +321,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
|
||||
cancelContextGestures(view: self.view)
|
||||
case .changed:
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
|
||||
self.isAdjacentLoadingEnabled = true
|
||||
let translation = recognizer.translation(in: self.view)
|
||||
var transitionFraction = translation.x / size.width
|
||||
|
|
@ -332,10 +332,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
transitionFraction = max(0.0, transitionFraction)
|
||||
}
|
||||
self.transitionFraction = transitionFraction
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate)
|
||||
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate)
|
||||
}
|
||||
case .cancelled, .ended:
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) {
|
||||
let translation = recognizer.translation(in: self.view)
|
||||
let velocity = recognizer.velocity(in: self.view)
|
||||
var directionIsToRight: Bool?
|
||||
|
|
@ -364,7 +364,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
}
|
||||
}
|
||||
self.transitionFraction = 0.0
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring))
|
||||
self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
|
@ -396,7 +396,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) {
|
||||
func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) {
|
||||
let previousAvailablePanes = self.currentAvailablePanes ?? []
|
||||
self.currentAvailablePanes = availablePanes
|
||||
|
||||
|
|
@ -430,7 +430,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
currentIndex = nil
|
||||
}
|
||||
|
||||
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes)
|
||||
self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes)
|
||||
|
||||
switch self.location {
|
||||
case .forum, .savedMessagesChats:
|
||||
|
|
@ -489,12 +489,12 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams {
|
||||
if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams {
|
||||
var transition: ContainedViewLayoutTransition = .immediate
|
||||
if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil {
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
}
|
||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition)
|
||||
strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition)
|
||||
}
|
||||
}
|
||||
if leftScope {
|
||||
|
|
@ -504,14 +504,14 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
)
|
||||
self.pendingPanes[key] = pane
|
||||
pane.pane.node.frame = paneFrame
|
||||
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
|
||||
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate)
|
||||
leftScope = true
|
||||
}
|
||||
}
|
||||
|
||||
for (key, pane) in self.pendingPanes {
|
||||
pane.pane.node.frame = paneFrame
|
||||
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
|
||||
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate)
|
||||
|
||||
if pane.isReady {
|
||||
self.pendingPanes.removeValue(forKey: key)
|
||||
|
|
@ -587,7 +587,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
paneCompletion()
|
||||
})
|
||||
}
|
||||
pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
|
||||
pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
|
||||
pane.node.isCurrent = key == self.currentPaneKey
|
||||
if paneWasAdded && key == self.currentPaneKey {
|
||||
pane.node.didBecomeFocused()
|
||||
|
|
@ -598,7 +598,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||
for (_, pane) in self.pendingPanes {
|
||||
let paneTransition: ContainedViewLayoutTransition = .immediate
|
||||
paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame)
|
||||
pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
|
||||
pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition)
|
||||
}
|
||||
if !self.didSetIsReady {
|
||||
if let currentPaneKey = self.currentPaneKey, let currentPane = self.currentPanes[currentPaneKey] {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import ChatListHeaderComponent
|
|||
import UndoUI
|
||||
import NewSessionInfoScreen
|
||||
import PresentationDataUtils
|
||||
import GlobalControlPanelsContext
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList(appendContacts: Bool)
|
||||
|
|
@ -110,7 +111,6 @@ public final class ChatListNodeInteraction {
|
|||
let hideChatFolderUpdates: () -> Void
|
||||
let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
|
||||
let openStarsTopup: (Int64?) -> Void
|
||||
let dismissNotice: (ChatListNotice) -> Void
|
||||
let editPeer: (ChatListItem) -> Void
|
||||
let openWebApp: (TelegramUser) -> Void
|
||||
let openPhotoSetup: () -> Void
|
||||
|
|
@ -171,7 +171,6 @@ public final class ChatListNodeInteraction {
|
|||
hideChatFolderUpdates: @escaping () -> Void,
|
||||
openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void,
|
||||
openStarsTopup: @escaping (Int64?) -> Void,
|
||||
dismissNotice: @escaping (ChatListNotice) -> Void,
|
||||
editPeer: @escaping (ChatListItem) -> Void,
|
||||
openWebApp: @escaping (TelegramUser) -> Void,
|
||||
openPhotoSetup: @escaping () -> Void,
|
||||
|
|
@ -219,7 +218,6 @@ public final class ChatListNodeInteraction {
|
|||
self.hideChatFolderUpdates = hideChatFolderUpdates
|
||||
self.openStories = openStories
|
||||
self.openStarsTopup = openStarsTopup
|
||||
self.dismissNotice = dismissNotice
|
||||
self.editPeer = editPeer
|
||||
self.openWebApp = openWebApp
|
||||
self.openPhotoSetup = openPhotoSetup
|
||||
|
|
@ -751,47 +749,6 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
|
||||
hideChatListContacts(context: context)
|
||||
} : nil), directionHint: entry.directionHint)
|
||||
case let .Notice(presentationData, notice):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
|
||||
switch action {
|
||||
case .activate:
|
||||
switch notice {
|
||||
case .clearStorage:
|
||||
nodeInteraction?.openStorageManagement()
|
||||
case .setupPassword:
|
||||
nodeInteraction?.openPasswordSetup()
|
||||
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
|
||||
nodeInteraction?.openPremiumIntro()
|
||||
case .xmasPremiumGift:
|
||||
nodeInteraction?.openPremiumGift([], nil)
|
||||
case .premiumGrace:
|
||||
nodeInteraction?.openPremiumManagement()
|
||||
case .setupBirthday:
|
||||
nodeInteraction?.openBirthdaySetup()
|
||||
case let .birthdayPremiumGift(peers, birthdays):
|
||||
nodeInteraction?.openPremiumGift(peers, birthdays)
|
||||
case .reviewLogin:
|
||||
break
|
||||
case let .starsSubscriptionLowBalance(amount, _):
|
||||
nodeInteraction?.openStarsTopup(amount.value)
|
||||
case .setupPhoto:
|
||||
nodeInteraction?.openPhotoSetup()
|
||||
case .accountFreeze:
|
||||
nodeInteraction?.openAccountFreezeInfo()
|
||||
case let .link(_, url, _, _):
|
||||
nodeInteraction?.openUrl(url)
|
||||
}
|
||||
case .hide:
|
||||
nodeInteraction?.dismissNotice(notice)
|
||||
case let .buttonChoice(isPositive):
|
||||
switch notice {
|
||||
case let .reviewLogin(newSessionReview, _):
|
||||
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1101,47 +1058,6 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
|
||||
hideChatListContacts(context: context)
|
||||
} : nil), directionHint: entry.directionHint)
|
||||
case let .Notice(presentationData, notice):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
|
||||
switch action {
|
||||
case .activate:
|
||||
switch notice {
|
||||
case .clearStorage:
|
||||
nodeInteraction?.openStorageManagement()
|
||||
case .setupPassword:
|
||||
nodeInteraction?.openPasswordSetup()
|
||||
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
|
||||
nodeInteraction?.openPremiumIntro()
|
||||
case .xmasPremiumGift:
|
||||
nodeInteraction?.openPremiumGift([], nil)
|
||||
case .premiumGrace:
|
||||
nodeInteraction?.openPremiumManagement()
|
||||
case .setupBirthday:
|
||||
nodeInteraction?.openBirthdaySetup()
|
||||
case let .birthdayPremiumGift(peers, birthdays):
|
||||
nodeInteraction?.openPremiumGift(peers, birthdays)
|
||||
case .reviewLogin:
|
||||
break
|
||||
case let .starsSubscriptionLowBalance(amount, _):
|
||||
nodeInteraction?.openStarsTopup(amount.value)
|
||||
case .setupPhoto:
|
||||
nodeInteraction?.openPhotoSetup()
|
||||
case .accountFreeze:
|
||||
nodeInteraction?.openAccountFreezeInfo()
|
||||
case let .link(_, url, _, _):
|
||||
nodeInteraction?.openUrl(url)
|
||||
}
|
||||
case .hide:
|
||||
nodeInteraction?.dismissNotice(notice)
|
||||
case let .buttonChoice(isPositive):
|
||||
switch notice {
|
||||
case let .reviewLogin(newSessionReview, _):
|
||||
nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
case .HeaderEntry:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
|
||||
|
|
@ -1282,7 +1198,7 @@ public final class ChatListNode: ListView {
|
|||
return []
|
||||
}
|
||||
}
|
||||
private var interaction: ChatListNodeInteraction?
|
||||
public private(set) var interaction: ChatListNodeInteraction?
|
||||
|
||||
private var dequeuedInitialTransitionOnLayout = false
|
||||
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
|
||||
|
|
@ -1383,7 +1299,6 @@ public final class ChatListNode: ListView {
|
|||
private let autoSetReady: Bool
|
||||
|
||||
public let isMainTab = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let suggestedChatListNotice = Promise<ChatListNotice?>(nil)
|
||||
|
||||
public var synchronousDrawingWhenNotAnimated: Bool = false
|
||||
|
||||
|
|
@ -1868,38 +1783,6 @@ public final class ChatListNode: ListView {
|
|||
return
|
||||
}
|
||||
self.openStarsTopup?(amount)
|
||||
}, dismissNotice: { [weak self] notice in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
switch notice {
|
||||
case .xmasPremiumGift:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone()
|
||||
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
case .setupBirthday:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone()
|
||||
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
case .birthdayPremiumGift:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone()
|
||||
self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
case .premiumGrace:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone()
|
||||
case .setupPhoto:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone()
|
||||
case .starsSubscriptionLowBalance:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone()
|
||||
case let .link(id, _, _, _):
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { [weak self] user in
|
||||
guard let self else {
|
||||
|
|
@ -1992,172 +1875,12 @@ public final class ChatListNode: ListView {
|
|||
} else {
|
||||
displayArchiveIntro = .single(false)
|
||||
}
|
||||
|
||||
let starsSubscriptionsContextPromise = Promise<StarsSubscriptionsContext?>(nil)
|
||||
|
||||
self.updateIsMainTabDisposable = (self.isMainTab.get()
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] isMainTab in
|
||||
guard let self else {
|
||||
return
|
||||
|> deliverOnMainQueue).startStrict(next: { isMainTab in
|
||||
if isMainTab {
|
||||
let _ = context.engine.privacy.cleanupSessionReviews().startStandalone()
|
||||
}
|
||||
|
||||
guard case .chatList(groupId: .root) = location, isMainTab else {
|
||||
self.suggestedChatListNotice.set(.single(nil))
|
||||
return
|
||||
}
|
||||
|
||||
let _ = context.engine.privacy.cleanupSessionReviews().startStandalone()
|
||||
|
||||
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
|
||||
|
||||
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> AppConfiguration in
|
||||
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
return appConfiguration
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> map { appConfiguration -> AccountFreezeConfiguration in
|
||||
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
|
||||
})
|
||||
|
||||
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
|
||||
context.engine.notices.getServerProvidedSuggestions(),
|
||||
context.engine.notices.getServerDismissedSuggestions(),
|
||||
twoStepData,
|
||||
newSessionReviews(postbox: context.account.postbox),
|
||||
context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
|
||||
),
|
||||
context.account.stateManager.contactBirthdays,
|
||||
starsSubscriptionsContextPromise.get(),
|
||||
accountFreezeConfiguration
|
||||
)
|
||||
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
|
||||
let (accountPeer, birthday) = data
|
||||
|
||||
if let newSessionReview = newSessionReviews.first {
|
||||
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
|
||||
}
|
||||
if suggestions.contains(.setupPassword), let configuration {
|
||||
var notSet = false
|
||||
switch configuration {
|
||||
case let .notSet(pendingEmail):
|
||||
if pendingEmail == nil {
|
||||
notSet = true
|
||||
}
|
||||
case .set:
|
||||
break
|
||||
}
|
||||
if notSet {
|
||||
return .single(.setupPassword)
|
||||
}
|
||||
}
|
||||
|
||||
let today = Calendar(identifier: .gregorian).component(.day, from: Date())
|
||||
var todayBirthdayPeerIds: [EnginePeer.Id] = []
|
||||
for (peerId, birthday) in birthdays {
|
||||
if birthday.day == today {
|
||||
todayBirthdayPeerIds.append(peerId)
|
||||
}
|
||||
}
|
||||
todayBirthdayPeerIds.sort { lhs, rhs in
|
||||
return lhs < rhs
|
||||
}
|
||||
|
||||
if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) {
|
||||
todayBirthdayPeerIds = []
|
||||
}
|
||||
|
||||
if let _ = accountFreezeConfiguration.freezeUntilDate {
|
||||
return .single(.accountFreeze)
|
||||
} else if suggestions.contains(.starsSubscriptionLowBalance) {
|
||||
if let starsSubscriptionsContext {
|
||||
return starsSubscriptionsContext.state
|
||||
|> map { state in
|
||||
if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty {
|
||||
return .starsSubscriptionLowBalance(
|
||||
amount: state.balance,
|
||||
peers: state.subscriptions.map { $0.peer }
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
|
||||
return .single(nil)
|
||||
}
|
||||
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
|
||||
return .single(.setupPhoto(accountPeer))
|
||||
} else if suggestions.contains(.gracePremium) {
|
||||
return .single(.premiumGrace)
|
||||
} else if suggestions.contains(.xmasPremiumGift) {
|
||||
return .single(.xmasPremiumGift)
|
||||
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||
return inAppPurchaseManager.availableProducts
|
||||
|> map { products -> ChatListNotice? in
|
||||
if products.count > 1 {
|
||||
let shortestOptionPrice: (Int64, NSDecimalNumber)
|
||||
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
|
||||
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
|
||||
} else {
|
||||
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
||||
}
|
||||
for product in products {
|
||||
if product.id.hasSuffix(".annual") {
|
||||
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0)
|
||||
let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0)
|
||||
if discount > 0 {
|
||||
if suggestions.contains(.restorePremium) {
|
||||
return .premiumRestore(discount: discount)
|
||||
} else if suggestions.contains(.annualPremium) {
|
||||
return .premiumAnnualDiscount(discount: discount)
|
||||
} else if suggestions.contains(.upgradePremium) {
|
||||
return .premiumUpgrade(discount: discount)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
if !GlobalExperimentalSettings.isAppStoreBuild {
|
||||
if suggestions.contains(.restorePremium) {
|
||||
return .premiumRestore(discount: 0)
|
||||
} else if suggestions.contains(.annualPremium) {
|
||||
return .premiumAnnualDiscount(discount: 0)
|
||||
} else if suggestions.contains(.upgradePremium) {
|
||||
return .premiumUpgrade(discount: 0)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else if !todayBirthdayPeerIds.isEmpty {
|
||||
return context.engine.data.get(
|
||||
EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
)
|
||||
|> map { result -> ChatListNotice? in
|
||||
var todayBirthdayPeers: [EnginePeer] = []
|
||||
for (peerId, _) in birthdays {
|
||||
if let maybePeer = result[peerId], let peer = maybePeer {
|
||||
todayBirthdayPeers.append(peer)
|
||||
}
|
||||
}
|
||||
return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays)
|
||||
}
|
||||
} else if suggestions.contains(.setupBirthday) && birthday == nil {
|
||||
return .single(.setupBirthday)
|
||||
} else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) {
|
||||
return .single(.link(id: id, url: url, title: title, subtitle: subtitle))
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
self.suggestedChatListNotice.set(suggestedChatListNoticeSignal)
|
||||
}).strict()
|
||||
|
||||
let storageInfo: Signal<Double?, NoError>
|
||||
|
|
@ -2346,7 +2069,6 @@ public final class ChatListNode: ListView {
|
|||
hideArchivedFolderByDefault,
|
||||
displayArchiveIntro,
|
||||
storageInfo,
|
||||
suggestedChatListNotice.get(),
|
||||
savedMessagesPeer,
|
||||
chatListViewUpdate,
|
||||
self.statePromise.get(),
|
||||
|
|
@ -2354,23 +2076,14 @@ public final class ChatListNode: ListView {
|
|||
chatListFilters,
|
||||
accountIsPremium
|
||||
)
|
||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
|
||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
|
||||
let (update, filter) = updateAndFilter
|
||||
|
||||
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
||||
|
||||
let notice: ChatListNotice?
|
||||
if let suggestedChatListNotice {
|
||||
notice = suggestedChatListNotice
|
||||
} else if let storageInfo {
|
||||
notice = .clearStorage(sizeFraction: storageInfo)
|
||||
} else {
|
||||
notice = nil
|
||||
}
|
||||
|
||||
let innerIsMainTab = location == .chatList(groupId: .root) && chatListFilter == nil
|
||||
|
||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab)
|
||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab)
|
||||
var isEmpty = true
|
||||
var entries = rawEntries.filter { entry in
|
||||
switch entry {
|
||||
|
|
@ -2697,7 +2410,6 @@ public final class ChatListNode: ListView {
|
|||
var didIncludeRemovingPeerId = false
|
||||
var didIncludeHiddenByDefaultArchive = false
|
||||
var didIncludeHiddenThread = false
|
||||
var didIncludeNotice = false
|
||||
if let previous = previousView {
|
||||
for entry in previous.filteredEntries {
|
||||
if case let .PeerEntry(peerEntry) = entry {
|
||||
|
|
@ -2724,15 +2436,12 @@ public final class ChatListNode: ListView {
|
|||
}
|
||||
} else if case let .GroupReferenceEntry(groupReferenceEntry) = entry {
|
||||
didIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault
|
||||
} else if case .Notice = entry {
|
||||
didIncludeNotice = true
|
||||
}
|
||||
}
|
||||
}
|
||||
var doesIncludeRemovingPeerId = false
|
||||
var doesIncludeArchive = false
|
||||
var doesIncludeHiddenByDefaultArchive = false
|
||||
var doesIncludeNotice = false
|
||||
|
||||
var doesIncludeHiddenThread = false
|
||||
for entry in processedView.filteredEntries {
|
||||
|
|
@ -2761,8 +2470,6 @@ public final class ChatListNode: ListView {
|
|||
} else if case let .GroupReferenceEntry(groupReferenceEntry) = entry {
|
||||
doesIncludeArchive = true
|
||||
doesIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault
|
||||
} else if case .Notice = entry {
|
||||
doesIncludeNotice = true
|
||||
}
|
||||
}
|
||||
if previousPinnedChats != updatedPinnedChats || previousPinnedThreads != updatedPinnedThreads {
|
||||
|
|
@ -2789,9 +2496,6 @@ public final class ChatListNode: ListView {
|
|||
if didIncludeHiddenThread != doesIncludeHiddenThread {
|
||||
disableAnimations = false
|
||||
}
|
||||
if didIncludeNotice != doesIncludeNotice {
|
||||
disableAnimations = false
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
|
||||
|
|
@ -3658,7 +3362,7 @@ public final class ChatListNode: ListView {
|
|||
} else {
|
||||
break loop
|
||||
}
|
||||
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory:
|
||||
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .HeaderEntry, .AdditionalCategory:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ public final class CombinedComponentContext<ComponentType: Component> {
|
|||
public let component: ComponentType
|
||||
public let availableSize: CGSize
|
||||
public let transition: ComponentTransition
|
||||
private let addImpl: (_ updatedComponent: _UpdatedChildComponent) -> Void
|
||||
private let addImpl: (_ updatedComponent: _UpdatedChildComponent, _ container: UIView?) -> Void
|
||||
|
||||
public var environment: Environment<ComponentType.EnvironmentType> {
|
||||
return self.context.environment
|
||||
|
|
@ -425,7 +425,7 @@ public final class CombinedComponentContext<ComponentType: Component> {
|
|||
component: ComponentType,
|
||||
availableSize: CGSize,
|
||||
transition: ComponentTransition,
|
||||
add: @escaping (_ updatedComponent: _UpdatedChildComponent) -> Void
|
||||
add: @escaping (_ updatedComponent: _UpdatedChildComponent, _ container: UIView?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.view = view
|
||||
|
|
@ -436,7 +436,11 @@ public final class CombinedComponentContext<ComponentType: Component> {
|
|||
}
|
||||
|
||||
public func add(_ updatedComponent: _UpdatedChildComponent) {
|
||||
self.addImpl(updatedComponent)
|
||||
self.addImpl(updatedComponent, nil)
|
||||
}
|
||||
|
||||
public func addWithExternalContainer(_ updatedComponent: _UpdatedChildComponent, container: UIView) {
|
||||
self.addImpl(updatedComponent, container)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -671,7 +675,7 @@ public extension CombinedComponent {
|
|||
component: self,
|
||||
availableSize: availableSize,
|
||||
transition: transition,
|
||||
add: { updatedChild in
|
||||
add: { updatedChild, optionalContainer in
|
||||
if !addedChildIds.insert(updatedChild.id).inserted {
|
||||
preconditionFailure("Child component can only be added once")
|
||||
}
|
||||
|
|
@ -692,7 +696,11 @@ public extension CombinedComponent {
|
|||
context.childViewIndices.remove(at: previousView.index)
|
||||
context.childViewIndices.insert(updatedChild.id, at: index)
|
||||
previousView.index = index
|
||||
view.insertSubview(previousView.view, at: index)
|
||||
if let optionalContainer {
|
||||
optionalContainer.addSubview(previousView.view)
|
||||
} else {
|
||||
view.insertSubview(previousView.view, at: index)
|
||||
}
|
||||
}
|
||||
|
||||
previousView.updateGestures(updatedChild.gestures)
|
||||
|
|
@ -715,7 +723,11 @@ public extension CombinedComponent {
|
|||
childView.transition = updatedChild.transitionDisappear
|
||||
childView.transitionWithGuide = updatedChild.transitionDisappearWithGuide
|
||||
|
||||
view.insertSubview(updatedChild.view, at: index)
|
||||
if let optionalContainer {
|
||||
optionalContainer.addSubview(updatedChild.view)
|
||||
} else {
|
||||
view.insertSubview(updatedChild.view, at: index)
|
||||
}
|
||||
|
||||
updatedChild.view.layer.anchorPoint = updatedChild._anchorPoint ?? CGPoint(x: 0.5, y: 0.5)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
|
||||
private let context: AccountContext
|
||||
private let glass: Bool
|
||||
private let externalSearchBar: Bool
|
||||
private let isPeerEnabled: (ContactListPeer) -> Bool
|
||||
private let addContact: ((String) -> Void)?
|
||||
private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void
|
||||
|
|
@ -265,6 +266,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
public init(
|
||||
context: AccountContext,
|
||||
glass: Bool = false,
|
||||
externalSearchBar: Bool = false,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
onlyWriteable: Bool,
|
||||
categories: ContactsSearchCategories,
|
||||
|
|
@ -278,6 +280,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
) {
|
||||
self.context = context
|
||||
self.glass = glass
|
||||
self.externalSearchBar = externalSearchBar
|
||||
self.isPeerEnabled = isPeerEnabled
|
||||
self.addContact = addContact
|
||||
self.openPeer = openPeer
|
||||
|
|
@ -686,7 +689,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
self.containerViewLayout = (layout, navigationBarHeight)
|
||||
|
||||
let topInset = navigationBarHeight
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)))
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
||||
|
||||
|
|
@ -714,7 +717,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||
textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
|
||||
self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize)
|
||||
|
||||
if self.glass {
|
||||
if self.glass && !self.externalSearchBar {
|
||||
let searchInputSize = self.searchInput.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -59,113 +59,6 @@ extension SettingsSearchableItemIcon {
|
|||
}
|
||||
}
|
||||
|
||||
final class SettingsSearchItem: ItemListControllerSearch {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let placeholder: String
|
||||
let activated: Bool
|
||||
let updateActivated: (Bool) -> Void
|
||||
let presentController: (ViewController, Any?) -> Void
|
||||
let pushController: (ViewController) -> Void
|
||||
let getNavigationController: (() -> NavigationController?)?
|
||||
let resolvedFaqUrl: Signal<ResolvedUrl?, NoError>
|
||||
let exceptionsList: Signal<NotificationExceptionsList?, NoError>
|
||||
let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>
|
||||
let privacySettings: Signal<AccountPrivacySettings?, NoError>
|
||||
let hasTwoStepAuth: Signal<Bool?, NoError>
|
||||
let twoStepAuthData: Signal<TwoStepVerificationAccessConfiguration?, NoError>
|
||||
let activeSessionsContext: Signal<ActiveSessionsContext?, NoError>
|
||||
let webSessionsContext: Signal<WebSessionsContext?, NoError>
|
||||
|
||||
private var updateActivity: ((Bool) -> Void)?
|
||||
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
|
||||
private let activityDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal<ResolvedUrl?, NoError>, exceptionsList: Signal<NotificationExceptionsList?, NoError>, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal<AccountPrivacySettings?, NoError>, hasTwoStepAuth: Signal<Bool?, NoError>, twoStepAuthData: Signal<TwoStepVerificationAccessConfiguration?, NoError>, activeSessionsContext: Signal<ActiveSessionsContext?, NoError>, webSessionsContext: Signal<WebSessionsContext?, NoError>) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.placeholder = placeholder
|
||||
self.activated = activated
|
||||
self.updateActivated = updateActivated
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.getNavigationController = getNavigationController
|
||||
self.resolvedFaqUrl = resolvedFaqUrl
|
||||
self.exceptionsList = exceptionsList
|
||||
self.archivedStickerPacks = archivedStickerPacks
|
||||
self.privacySettings = privacySettings
|
||||
self.hasTwoStepAuth = hasTwoStepAuth
|
||||
self.twoStepAuthData = twoStepAuthData
|
||||
self.activeSessionsContext = activeSessionsContext
|
||||
self.webSessionsContext = webSessionsContext
|
||||
self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal<Bool, NoError> in
|
||||
if value {
|
||||
return .single(value) |> delay(0.2, queue: Queue.mainQueue())
|
||||
} else {
|
||||
return .single(value)
|
||||
}
|
||||
}).start(next: { [weak self] value in
|
||||
self?.updateActivity?(value)
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.activityDisposable.dispose()
|
||||
}
|
||||
|
||||
func isEqual(to: ItemListControllerSearch) -> Bool {
|
||||
if let to = to as? SettingsSearchItem {
|
||||
if self.context !== to.context || self.theme !== to.theme || self.placeholder != to.placeholder || self.activated != to.activated {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? {
|
||||
let updateActivated: (Bool) -> Void = self.updateActivated
|
||||
if let current = current as? NavigationBarSearchContentNode {
|
||||
current.updateThemeAndPlaceholder(theme: self.theme, placeholder: self.placeholder)
|
||||
return current
|
||||
} else {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
return NavigationBarSearchContentNode(theme: presentationData.theme, placeholder: presentationData.strings.Settings_Search, activate: {
|
||||
updateActivated(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode {
|
||||
let updateActivated: (Bool) -> Void = self.updateActivated
|
||||
let presentController: (ViewController, Any?) -> Void = self.presentController
|
||||
let pushController: (ViewController) -> Void = self.pushController
|
||||
|
||||
if let current = current as? SettingsSearchItemNode, let titleContentNode = titleContentNode as? NavigationBarSearchContentNode {
|
||||
current.updatePresentationData(self.context.sharedContext.currentPresentationData.with { $0 })
|
||||
if current.isSearching != self.activated {
|
||||
if self.activated {
|
||||
current.activateSearch(placeholderNode: titleContentNode.placeholderNode)
|
||||
} else {
|
||||
current.deactivateSearch(placeholderNode: titleContentNode.placeholderNode)
|
||||
}
|
||||
}
|
||||
return current
|
||||
} else {
|
||||
return SettingsSearchItemNode(context: self.context, cancel: {
|
||||
updateActivated(false)
|
||||
}, updateActivity: { [weak self] value in
|
||||
self?.activity.set(value)
|
||||
}, pushController: { c in
|
||||
pushController(c)
|
||||
}, presentController: { c, a in
|
||||
presentController(c, a)
|
||||
}, getNavigationController: self.getNavigationController, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasTwoStepAuth: self.hasTwoStepAuth, twoStepAuthData: self.twoStepAuthData, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class SettingsSearchInteraction {
|
||||
let openItem: (SettingsSearchableItem) -> Void
|
||||
let deleteRecentItem: (SettingsSearchableItemId) -> Void
|
||||
|
|
|
|||
|
|
@ -228,7 +228,6 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
|||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
|
|
|||
|
|
@ -377,7 +377,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ swift_library(
|
|||
"//submodules/Emoji",
|
||||
"//submodules/TelegramCore/FlatBuffers",
|
||||
"//submodules/TelegramCore/FlatSerialization",
|
||||
"//submodules/MurMurHash32"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -901,8 +901,6 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
},
|
||||
openStarsTopup: { _ in
|
||||
},
|
||||
dismissNotice: { _ in
|
||||
},
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import GlobalControlPanelsContext
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
public final class ChatListHeaderNoticeComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let data: GlobalControlPanelsContext.ChatListNotice
|
||||
public let activateAction: (GlobalControlPanelsContext.ChatListNotice) -> Void
|
||||
public let dismissAction: (GlobalControlPanelsContext.ChatListNotice) -> Void
|
||||
public let selectAction: (GlobalControlPanelsContext.ChatListNotice, Bool) -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
data: GlobalControlPanelsContext.ChatListNotice,
|
||||
activateAction: @escaping (GlobalControlPanelsContext.ChatListNotice) -> Void,
|
||||
dismissAction: @escaping (GlobalControlPanelsContext.ChatListNotice) -> Void,
|
||||
selectAction: @escaping (GlobalControlPanelsContext.ChatListNotice, Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.data = data
|
||||
self.activateAction = activateAction
|
||||
self.dismissAction = dismissAction
|
||||
self.selectAction = selectAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatListHeaderNoticeComponent, rhs: ChatListHeaderNoticeComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.data != rhs.data {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var panel: ChatListNoticeItemNode?
|
||||
|
||||
private var component: ChatListHeaderNoticeComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
component.activateAction(component.data)
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: ChatListHeaderNoticeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let itemNode: ChatListNoticeItemNode
|
||||
if let current = self.panel {
|
||||
itemNode = current
|
||||
} else {
|
||||
itemNode = ChatListNoticeItemNode()
|
||||
self.panel = itemNode
|
||||
self.addSubview(itemNode.view)
|
||||
}
|
||||
|
||||
let item = ChatListNoticeItem(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
notice: component.data,
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case .activate:
|
||||
component.activateAction(component.data)
|
||||
case .hide:
|
||||
component.dismissAction(component.data)
|
||||
case let .buttonChoice(isPositive):
|
||||
component.selectAction(component.data, isPositive)
|
||||
}
|
||||
}
|
||||
)
|
||||
let (nodeLayout, apply) = itemNode.asyncLayout()(item, ListViewItemLayoutParams(
|
||||
width: availableSize.width,
|
||||
leftInset: 0.0,
|
||||
rightInset: 0.0,
|
||||
availableHeight: 10000.0,
|
||||
isStandalone: true
|
||||
), false)
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: nodeLayout.contentSize.height)
|
||||
let panelFrame = CGRect(origin: CGPoint(), size: size)
|
||||
transition.setFrame(view: itemNode.view, frame: panelFrame)
|
||||
apply()
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ swift_library(
|
|||
"//submodules/AccountContext",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/TelegramCallsUI",
|
||||
"//submodules/Display",
|
||||
"//submodules/UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramUIPreferences
|
||||
import TelegramCallsUI
|
||||
import Display
|
||||
import UndoUI
|
||||
|
||||
public final class GlobalControlPanelsContext {
|
||||
public final class MediaPlayback: Equatable {
|
||||
|
|
@ -62,17 +65,37 @@ public final class GlobalControlPanelsContext {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatListNotice: Equatable {
|
||||
case clearStorage(sizeFraction: Double)
|
||||
case setupPassword
|
||||
case premiumUpgrade(discount: Int32)
|
||||
case premiumAnnualDiscount(discount: Int32)
|
||||
case premiumRestore(discount: Int32)
|
||||
case xmasPremiumGift
|
||||
case setupBirthday
|
||||
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
|
||||
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
||||
case premiumGrace
|
||||
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
|
||||
case setupPhoto(EnginePeer)
|
||||
case accountFreeze
|
||||
case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text)
|
||||
}
|
||||
|
||||
public final class State {
|
||||
public let mediaPlayback: MediaPlayback?
|
||||
public let liveLocation: LiveLocation?
|
||||
public let chatListNotice: ChatListNotice?
|
||||
|
||||
public init(
|
||||
mediaPlayback: MediaPlayback?,
|
||||
liveLocation: LiveLocation?
|
||||
liveLocation: LiveLocation?,
|
||||
chatListNotice: ChatListNotice?
|
||||
) {
|
||||
self.mediaPlayback = mediaPlayback
|
||||
self.liveLocation = liveLocation
|
||||
self.chatListNotice = chatListNotice
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,12 +117,15 @@ public final class GlobalControlPanelsContext {
|
|||
|
||||
var liveLocationState: (mode: LiveLocationMode, peers: [EnginePeer], messages: [EngineMessage.Id: EngineMessage], canClose: Bool, version: Int)?
|
||||
var liveLocationDisposable: Disposable?
|
||||
|
||||
var chatListNotice: ChatListNotice?
|
||||
var suggestedChatListNoticeDisposable: Disposable?
|
||||
|
||||
init(queue: Queue, context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?) {
|
||||
init(queue: Queue, context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?, chatListNotices: Bool) {
|
||||
self.queue = queue
|
||||
self.context = context
|
||||
|
||||
self.stateValue = State(mediaPlayback: nil, liveLocation: nil)
|
||||
self.stateValue = State(mediaPlayback: nil, liveLocation: nil, chatListNotice: nil)
|
||||
|
||||
if mediaPlayback {
|
||||
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|
||||
|
|
@ -227,11 +253,176 @@ public final class GlobalControlPanelsContext {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
if chatListNotices {
|
||||
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
|
||||
|
||||
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> AppConfiguration in
|
||||
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
return appConfiguration
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> map { appConfiguration -> AccountFreezeConfiguration in
|
||||
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
|
||||
})
|
||||
|
||||
let starsSubscriptionsContextPromise = Promise<StarsSubscriptionsContext?>(nil)
|
||||
|
||||
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
|
||||
context.engine.notices.getServerProvidedSuggestions(),
|
||||
context.engine.notices.getServerDismissedSuggestions(),
|
||||
twoStepData,
|
||||
newSessionReviews(postbox: context.account.postbox),
|
||||
context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
|
||||
),
|
||||
context.account.stateManager.contactBirthdays,
|
||||
starsSubscriptionsContextPromise.get(),
|
||||
accountFreezeConfiguration
|
||||
)
|
||||
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
|
||||
let (accountPeer, birthday) = data
|
||||
|
||||
if let newSessionReview = newSessionReviews.first {
|
||||
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
|
||||
}
|
||||
if suggestions.contains(.setupPassword), let configuration {
|
||||
var notSet = false
|
||||
switch configuration {
|
||||
case let .notSet(pendingEmail):
|
||||
if pendingEmail == nil {
|
||||
notSet = true
|
||||
}
|
||||
case .set:
|
||||
break
|
||||
}
|
||||
if notSet {
|
||||
return .single(.setupPassword)
|
||||
}
|
||||
}
|
||||
|
||||
let today = Calendar(identifier: .gregorian).component(.day, from: Date())
|
||||
var todayBirthdayPeerIds: [EnginePeer.Id] = []
|
||||
for (peerId, birthday) in birthdays {
|
||||
if birthday.day == today {
|
||||
todayBirthdayPeerIds.append(peerId)
|
||||
}
|
||||
}
|
||||
todayBirthdayPeerIds.sort { lhs, rhs in
|
||||
return lhs < rhs
|
||||
}
|
||||
|
||||
if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) {
|
||||
todayBirthdayPeerIds = []
|
||||
}
|
||||
|
||||
if let _ = accountFreezeConfiguration.freezeUntilDate {
|
||||
return .single(.accountFreeze)
|
||||
} else if suggestions.contains(.starsSubscriptionLowBalance) {
|
||||
if let starsSubscriptionsContext {
|
||||
return starsSubscriptionsContext.state
|
||||
|> map { state in
|
||||
if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty {
|
||||
return .starsSubscriptionLowBalance(
|
||||
amount: state.balance,
|
||||
peers: state.subscriptions.map { $0.peer }
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
|
||||
return .single(nil)
|
||||
}
|
||||
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
|
||||
return .single(.setupPhoto(accountPeer))
|
||||
} else if suggestions.contains(.gracePremium) {
|
||||
return .single(.premiumGrace)
|
||||
} else if suggestions.contains(.xmasPremiumGift) {
|
||||
return .single(.xmasPremiumGift)
|
||||
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||
return inAppPurchaseManager.availableProducts
|
||||
|> map { products -> ChatListNotice? in
|
||||
if products.count > 1 {
|
||||
let shortestOptionPrice: (Int64, NSDecimalNumber)
|
||||
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
|
||||
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
|
||||
} else {
|
||||
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
||||
}
|
||||
for product in products {
|
||||
if product.id.hasSuffix(".annual") {
|
||||
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0)
|
||||
let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0)
|
||||
if discount > 0 {
|
||||
if suggestions.contains(.restorePremium) {
|
||||
return .premiumRestore(discount: discount)
|
||||
} else if suggestions.contains(.annualPremium) {
|
||||
return .premiumAnnualDiscount(discount: discount)
|
||||
} else if suggestions.contains(.upgradePremium) {
|
||||
return .premiumUpgrade(discount: discount)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
if !GlobalExperimentalSettings.isAppStoreBuild {
|
||||
if suggestions.contains(.restorePremium) {
|
||||
return .premiumRestore(discount: 0)
|
||||
} else if suggestions.contains(.annualPremium) {
|
||||
return .premiumAnnualDiscount(discount: 0)
|
||||
} else if suggestions.contains(.upgradePremium) {
|
||||
return .premiumUpgrade(discount: 0)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else if !todayBirthdayPeerIds.isEmpty {
|
||||
return context.engine.data.get(
|
||||
EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
)
|
||||
|> map { result -> ChatListNotice? in
|
||||
var todayBirthdayPeers: [EnginePeer] = []
|
||||
for (peerId, _) in birthdays {
|
||||
if let maybePeer = result[peerId], let peer = maybePeer {
|
||||
todayBirthdayPeers.append(peer)
|
||||
}
|
||||
}
|
||||
return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays)
|
||||
}
|
||||
} else if suggestions.contains(.setupBirthday) && birthday == nil {
|
||||
return .single(.setupBirthday)
|
||||
} else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) {
|
||||
return .single(.link(id: id, url: url, title: title, subtitle: subtitle))
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
self.suggestedChatListNoticeDisposable = (suggestedChatListNoticeSignal
|
||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] chatListNotice in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.chatListNotice != chatListNotice {
|
||||
self.chatListNotice = chatListNotice
|
||||
self.notifyStateUpdated()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.mediaStatusDisposable?.dispose()
|
||||
self.liveLocationDisposable?.dispose()
|
||||
self.suggestedChatListNoticeDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func notifyStateUpdated() {
|
||||
|
|
@ -256,10 +447,42 @@ public final class GlobalControlPanelsContext {
|
|||
canClose: liveLocationState.canClose,
|
||||
version: liveLocationState.version
|
||||
)
|
||||
}
|
||||
},
|
||||
chatListNotice: self.chatListNotice
|
||||
)
|
||||
self.statePipe.putNext(self.stateValue)
|
||||
}
|
||||
|
||||
func dismissChatListNotice(parentController: ViewController, notice: ChatListNotice) {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
switch notice {
|
||||
case .xmasPremiumGift:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone()
|
||||
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
case .setupBirthday:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone()
|
||||
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
case .birthdayPremiumGift:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone()
|
||||
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
case .premiumGrace:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone()
|
||||
case .setupPhoto:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone()
|
||||
case .starsSubscriptionLowBalance:
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone()
|
||||
case let .link(id, _, _, _):
|
||||
let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let impl: QueueLocalObject<Impl>
|
||||
|
|
@ -270,9 +493,15 @@ public final class GlobalControlPanelsContext {
|
|||
}
|
||||
}
|
||||
|
||||
public init(context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?) {
|
||||
public init(context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: GroupCallPanelSource?, chatListNotices: Bool) {
|
||||
self.impl = QueueLocalObject(queue: .mainQueue(), generate: {
|
||||
return Impl(queue: .mainQueue(), context: context, mediaPlayback: mediaPlayback, liveLocationMode: liveLocationMode, groupCalls: groupCalls)
|
||||
return Impl(queue: .mainQueue(), context: context, mediaPlayback: mediaPlayback, liveLocationMode: liveLocationMode, groupCalls: groupCalls, chatListNotices: chatListNotices)
|
||||
})
|
||||
}
|
||||
|
||||
public func dismissChatListNotice(parentController: ViewController, notice: ChatListNotice) {
|
||||
self.impl.with { impl in
|
||||
impl.dismissChatListNotice(parentController: parentController, notice: notice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -205,8 +205,6 @@ final class GreetingMessageListItemComponent: Component {
|
|||
},
|
||||
openStarsTopup: { _ in
|
||||
},
|
||||
dismissNotice: { _ in
|
||||
},
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -871,7 +871,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
|
|||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,6 @@ public final class ThemeGridController: ViewController {
|
|||
private let presentationDataPromise = Promise<PresentationData>()
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
private var isEmpty: Bool?
|
||||
private var editingMode: Bool = false
|
||||
|
||||
|
|
@ -81,9 +79,6 @@ public final class ThemeGridController: ViewController {
|
|||
|
||||
self.scrollToTop = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
}
|
||||
strongSelf.controllerNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +177,6 @@ public final class ThemeGridController: ViewController {
|
|||
} else {
|
||||
uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.deactivateSearch(animated: false)
|
||||
strongSelf.controllerNode.scrollToTop(animated: false)
|
||||
}
|
||||
if let controller = controller {
|
||||
|
|
@ -392,9 +386,6 @@ public final class ThemeGridController: ViewController {
|
|||
}
|
||||
})
|
||||
self.controllerNode.navigationBar = self.navigationBar
|
||||
self.controllerNode.requestDeactivateSearch = { [weak self] in
|
||||
self?.deactivateSearch(animated: true)
|
||||
}
|
||||
self.controllerNode.requestWallpaperRemoval = { [weak self] in
|
||||
if let self {
|
||||
self.completion(.remove)
|
||||
|
|
@ -403,10 +394,6 @@ public final class ThemeGridController: ViewController {
|
|||
}
|
||||
self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in
|
||||
if let strongSelf = self {
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateGridVisibleContentOffset(offset)
|
||||
}
|
||||
|
||||
var previousContentOffsetValue: CGFloat?
|
||||
if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset {
|
||||
previousContentOffsetValue = value
|
||||
|
|
@ -427,12 +414,6 @@ public final class ThemeGridController: ViewController {
|
|||
strongSelf.previousContentOffset = offset
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerNode.gridNode.scrollingCompleted = { [weak self] in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
let _ = strongSelf.controllerNode.fixNavigationSearchableGridNodeScrolling(searchNode: searchContentNode)
|
||||
}
|
||||
}
|
||||
|
||||
self._ready.set(self.controllerNode.ready.get())
|
||||
|
||||
|
|
@ -492,34 +473,6 @@ public final class ThemeGridController: ViewController {
|
|||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
func activateSearch() {
|
||||
if self.displayNavigationBar {
|
||||
let _ = (self.controllerNode.ready.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let scrollToTop = strongSelf.scrollToTop {
|
||||
scrollToTop()
|
||||
}
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
strongSelf.controllerNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
|
||||
}
|
||||
strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func deactivateSearch(animated: Bool) {
|
||||
if !self.displayNavigationBar {
|
||||
self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
|
||||
if let searchContentNode = self.searchContentNode {
|
||||
self.controllerNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func editPressed() {
|
||||
self.editingMode = true
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
|
|
|
|||
|
|
@ -162,7 +162,6 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||
private let emptyStateUpdated: (Bool) -> Void
|
||||
private let resetWallpapers: () -> Void
|
||||
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestWallpaperRemoval: (() -> Void)?
|
||||
|
||||
let ready = ValuePromise<Bool>()
|
||||
|
|
@ -958,75 +957,6 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
|
||||
guard let (containerLayout, navigationBarHeight) = self.validLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ThemeGridSearchContentNode(context: context, openResult: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentPreviewController(.contextResult(result))
|
||||
}
|
||||
}), cancel: { [weak self] in
|
||||
self?.requestDeactivateSearch?()
|
||||
})
|
||||
|
||||
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
|
||||
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
|
||||
if isSearchBar {
|
||||
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
|
||||
} else {
|
||||
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
|
||||
}
|
||||
}
|
||||
}, placeholder: placeholderNode)
|
||||
}
|
||||
|
||||
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
|
||||
self.searchDisplayController = nil
|
||||
}
|
||||
}
|
||||
|
||||
func fixNavigationSearchableGridNodeScrolling(searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
|
||||
let scrollToItem: GridNodeScrollToItem
|
||||
let targetProgress: CGFloat
|
||||
|
||||
let duration: Double = 0.3
|
||||
let curve = ContainedViewLayoutTransitionCurve.slide
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: curve)
|
||||
let timingFunction = curve.timingFunction
|
||||
let mediaTimingFunction = curve.mediaTimingFunction
|
||||
|
||||
if searchNode.expansionProgress < 0.6 {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(navigationBarSearchContentHeight), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
|
||||
targetProgress = 0.0
|
||||
} else {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
|
||||
targetProgress = 1.0
|
||||
}
|
||||
|
||||
let previousOffset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top)
|
||||
searchNode.updateExpansionProgress(targetProgress, animated: true)
|
||||
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in })
|
||||
|
||||
let offset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top) - previousOffset
|
||||
|
||||
self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.backgroundNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
|
||||
self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.separatorNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
|
||||
self.colorItemNode.layer.animatePosition(from: self.colorItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.colorItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
|
||||
self.galleryItemNode.layer.animatePosition(from: self.galleryItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.galleryItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
|
||||
self.descriptionItemNode.layer.animatePosition(from: self.descriptionItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.descriptionItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction)
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func scrollToTop(animated: Bool = true) {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.contentNode.scrollToTop()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -291,7 +291,6 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
|
|||
}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
}, openStarsTopup: { _ in
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,8 +175,6 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
|
|||
},
|
||||
openStarsTopup: { _ in
|
||||
},
|
||||
dismissNotice: { _ in
|
||||
},
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
|
|
|
|||
|
|
@ -3510,7 +3510,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
|
||||
private func updateNavigationButtons() {
|
||||
if case .attachMenu = self.source {
|
||||
let barButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||
let closeComponent: AnyComponentWithIdentity<Empty> = AnyComponentWithIdentity(
|
||||
id: "close",
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue