From fc42b114dc76b8eac53dfa61679e1d7ef8e9d0e8 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 2 Apr 2021 21:40:41 +0400 Subject: [PATCH 01/15] Temporarily roll back busy handler --- submodules/Postbox/Sources/SqliteValueBox.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 0ae732a92a..93e50d9180 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -297,8 +297,8 @@ public final class SqliteValueBox: ValueBox { let _ = try? FileManager.default.removeItem(atPath: path) preconditionFailure("Couldn't open database") } - - sqlite3_busy_timeout(database.handle, 1000 * 10000) + + //sqlite3_busy_timeout(database.handle, 1000 * 10000) var resultCode: Bool = true @@ -409,6 +409,8 @@ public final class SqliteValueBox: ValueBox { } } } + + sqlite3_busy_timeout(database.handle, 1000 * 10000) //database.execute("PRAGMA cache_size=-2097152") resultCode = database.execute("PRAGMA mmap_size=0") From c6bb8fe71df29179ad95dc463a569bc418ce8765 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 2 Apr 2021 23:10:06 +0400 Subject: [PATCH 02/15] Add more logs --- .../Postbox/Sources/SqliteValueBox.swift | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 93e50d9180..02263a44fc 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -234,10 +234,8 @@ public final class SqliteValueBox: ValueBox { let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) let path = basePath + "/db_sqlite" - - #if DEBUG - print("Instance \(self) opening sqlite at \(path)") - #endif + + postboxLog("Instance \(self) opening sqlite at \(path)") #if DEBUG let exists = FileManager.default.fileExists(atPath: path) @@ -298,6 +296,8 @@ public final class SqliteValueBox: ValueBox { preconditionFailure("Couldn't open database") } + postboxLog("Did open DB at \(path)") + //sqlite3_busy_timeout(database.handle, 1000 * 10000) var resultCode: Bool = true @@ -306,6 +306,8 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32") assert(resultCode) + + postboxLog("Did set up cipher") if self.isEncrypted(database) { if let encryptionParameters = encryptionParameters { @@ -410,7 +412,9 @@ public final class SqliteValueBox: ValueBox { } } - sqlite3_busy_timeout(database.handle, 1000 * 10000) + postboxLog("Did set up encryption") + + //sqlite3_busy_timeout(database.handle, 1000 * 10000) //database.execute("PRAGMA cache_size=-2097152") resultCode = database.execute("PRAGMA mmap_size=0") @@ -423,6 +427,9 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) resultCode = database.execute("PRAGMA cipher_memory_security = OFF") assert(resultCode) + + postboxLog("Did set up pragmas") + //resultCode = database.execute("PRAGMA wal_autocheckpoint=500") //database.execute("PRAGMA journal_size_limit=1536") @@ -443,8 +450,12 @@ public final class SqliteValueBox: ValueBox { let _ = self.runPragma(database, "checkpoint_fullfsync = 1") assert(self.runPragma(database, "checkpoint_fullfsync") == "1") + + postboxLog("Did set up checkpoint_fullfsync") self.beginInternal(database: database) + + postboxLog("Did begin transaction") let result = self.getUserVersion(database) @@ -464,8 +475,12 @@ public final class SqliteValueBox: ValueBox { for table in self.listFullTextTables(database) { self.fullTextTables[table.id] = table } + + postboxLog("Did load tables") self.commitInternal(database: database) + + postboxLog("Did commit final") lock.unlock() From 07b284a55e3b89745ec9f16ebbeec9d39f3a16a4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 3 Apr 2021 02:25:05 +0400 Subject: [PATCH 03/15] Even more logs --- submodules/Postbox/Sources/SqliteValueBox.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 02263a44fc..245fbb5c64 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -310,6 +310,8 @@ public final class SqliteValueBox: ValueBox { postboxLog("Did set up cipher") if self.isEncrypted(database) { + postboxLog("Database is encrypted") + if let encryptionParameters = encryptionParameters { precondition(encryptionParameters.salt.data.count == 16) precondition(encryptionParameters.key.data.count == 32) @@ -318,12 +320,15 @@ public final class SqliteValueBox: ValueBox { resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"") assert(resultCode) + + postboxLog("Setting encryption key") if self.isEncrypted(database) { + postboxLog("Encryption key is invalid") + if isTemporary || isReadOnly { return nil } - postboxLog("Encryption key is invalid") for fileName in dabaseFileNames { let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)") @@ -356,6 +361,8 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) } } else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet { + postboxLog("Not encrypted") + let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data) if FileManager.default.fileExists(atPath: path) { @@ -535,7 +542,9 @@ public final class SqliteValueBox: ValueBox { private func isEncrypted(_ database: Database) -> Bool { var statement: OpaquePointer? = nil + postboxLog("isEncrypted prepare...") let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil) + postboxLog("isEncrypted prepare done") if statement == nil { postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]") return true @@ -553,6 +562,7 @@ public final class SqliteValueBox: ValueBox { preparedStatement.destroy() return true } + postboxLog("isEncrypted step done") preparedStatement.destroy() return status == SQLITE_NOTADB } From 77812fe0b439fe50ded63905b34a6220574f6846 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 3 Apr 2021 02:59:18 +0400 Subject: [PATCH 04/15] Add crash --- .../Sources/DebugController.swift | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 9c4b1a535c..fa1ba65d21 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -62,7 +62,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case skipReadHistory(PresentationTheme, Bool) case crashOnSlowQueries(PresentationTheme, Bool) case clearTips(PresentationTheme) - case reimport(PresentationTheme) + case crash(PresentationTheme) case resetData(PresentationTheme) case resetDatabase(PresentationTheme) case resetDatabaseAndCache(PresentationTheme) @@ -92,7 +92,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference: + case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -133,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 12 case .clearTips: return 13 - case .reimport: + case .crash: return 14 case .resetData: return 15 @@ -550,20 +550,9 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() } }) - case let .reimport(theme): - return ItemListActionItem(presentationData: presentationData, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { - let appGroupName = "group.\(Bundle.main.bundleIdentifier!)" - let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) - - guard let appGroupUrl = maybeAppGroupUrl else { - return - } - - let statusPath = appGroupUrl.path + "/Documents/importcompleted" - if FileManager.default.fileExists(atPath: statusPath) { - let _ = try? FileManager.default.removeItem(at: URL(fileURLWithPath: statusPath)) - exit(0) - } + case let .crash(theme): + return ItemListActionItem(presentationData: presentationData, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + preconditionFailure() }) case let .resetData(theme): return ItemListActionItem(presentationData: presentationData, title: "Reset Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { @@ -809,6 +798,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present if isMainApp { entries.append(.clearTips(presentationData.theme)) } + entries.append(.crash(presentationData.theme)) entries.append(.resetData(presentationData.theme)) entries.append(.resetDatabase(presentationData.theme)) entries.append(.resetDatabaseAndCache(presentationData.theme)) From 92b505c59320334d8e345e084095b77dfbf96bbe Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 5 Apr 2021 00:56:03 +0400 Subject: [PATCH 05/15] Add an option to give up --- .../Postbox/Sources/SqliteValueBox.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 245fbb5c64..d58ab1e1ad 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -65,7 +65,7 @@ struct SqlitePreparedStatement { } return res == SQLITE_ROW } - + struct SqlError: Error { var code: Int32 } @@ -298,7 +298,7 @@ public final class SqliteValueBox: ValueBox { postboxLog("Did open DB at \(path)") - //sqlite3_busy_timeout(database.handle, 1000 * 10000) + sqlite3_busy_timeout(database.handle, 5 * 1000) var resultCode: Bool = true @@ -420,8 +420,6 @@ public final class SqliteValueBox: ValueBox { } postboxLog("Did set up encryption") - - //sqlite3_busy_timeout(database.handle, 1000 * 10000) //database.execute("PRAGMA cache_size=-2097152") resultCode = database.execute("PRAGMA mmap_size=0") @@ -543,7 +541,19 @@ public final class SqliteValueBox: ValueBox { private func isEncrypted(_ database: Database) -> Bool { var statement: OpaquePointer? = nil postboxLog("isEncrypted prepare...") + + let allIsOk = Atomic(value: false) + let databasePath = self.databasePath + DispatchQueue.global().asyncAfter(deadline: .now() + 5.0, execute: { + if allIsOk.with({ $0 }) == false { + postboxLog("Timeout reached, discarding database") + try? FileManager.default.removeItem(atPath: databasePath) + + preconditionFailure() + } + }) let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil) + let _ = allIsOk.swap(true) postboxLog("isEncrypted prepare done") if statement == nil { postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]") From 8f55f2c89b47594330e32b88975174fd533cbd96 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 5 Apr 2021 20:57:40 +0400 Subject: [PATCH 06/15] Trigger build --- Random.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Random.txt b/Random.txt index 27ef252295..b9f017556d 100644 --- a/Random.txt +++ b/Random.txt @@ -1 +1 @@ -6098b6ed7c06e42f7bb7226e92744e7951c4c3f89787d702280f907e68a60a15 +6eb592f57eca2cd3cda976727ba368ed From 0dca1b727d0ba01e6e0b89f8c12b6b4094d28dff Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 6 Apr 2021 02:53:32 +0400 Subject: [PATCH 07/15] Add hotfix branch --- .github/workflows/build.yml | 4 +++- .gitlab-ci.yml | 4 ++++ build_number_offset | 1 + buildbox/build-telegram.sh | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 build_number_offset diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34d314c55e..dcf2761983 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,9 +48,11 @@ jobs: cd $SOURCE_DIR + BUILD_NUMBER_OFFSET="$(cat build_number_offset)" + export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);') export COMMIT_COUNT=$(git rev-list --count HEAD) - export COMMIT_COUNT="$(($COMMIT_COUNT+2000))" + export COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))" export BUILD_NUMBER="$COMMIT_COUNT" echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8cad4dbc1..153e1667a4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -70,6 +70,7 @@ beta_testflight: stage: build only: - beta + - hotfix except: - tags script: @@ -87,6 +88,7 @@ deploy_beta_testflight: stage: deploy only: - beta + - hotfix except: - tags script: @@ -100,6 +102,7 @@ verifysanity_beta_testflight: stage: verifysanity only: - beta + - hotfix except: - tags script: @@ -118,6 +121,7 @@ verify_beta_testflight: stage: verify only: - beta + - hotfix except: - tags script: diff --git a/build_number_offset b/build_number_offset new file mode 100644 index 0000000000..29d6383b52 --- /dev/null +++ b/build_number_offset @@ -0,0 +1 @@ +100 diff --git a/buildbox/build-telegram.sh b/buildbox/build-telegram.sh index 70b300f4ae..f2bc29956c 100644 --- a/buildbox/build-telegram.sh +++ b/buildbox/build-telegram.sh @@ -79,7 +79,8 @@ COMMIT_ID="$(git rev-parse HEAD)" COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an') if [ -z "$2" ]; then COMMIT_COUNT=$(git rev-list --count HEAD) - COMMIT_COUNT="$(($COMMIT_COUNT+2000))" + BUILD_NUMBER_OFFSET="$(cat build_number_offset)" + COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))" BUILD_NUMBER="$COMMIT_COUNT" else BUILD_NUMBER="$2" From ec8870816d688e991aa987a156ef8e29b76ea725 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 6 Apr 2021 02:57:11 +0400 Subject: [PATCH 08/15] Backport the alleged sqlite deadlock workaround --- .../Postbox/Sources/SqliteValueBox.swift | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 0ae732a92a..d58ab1e1ad 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -65,7 +65,7 @@ struct SqlitePreparedStatement { } return res == SQLITE_ROW } - + struct SqlError: Error { var code: Int32 } @@ -234,10 +234,8 @@ public final class SqliteValueBox: ValueBox { let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) let path = basePath + "/db_sqlite" - - #if DEBUG - print("Instance \(self) opening sqlite at \(path)") - #endif + + postboxLog("Instance \(self) opening sqlite at \(path)") #if DEBUG let exists = FileManager.default.fileExists(atPath: path) @@ -297,8 +295,10 @@ public final class SqliteValueBox: ValueBox { let _ = try? FileManager.default.removeItem(atPath: path) preconditionFailure("Couldn't open database") } - - sqlite3_busy_timeout(database.handle, 1000 * 10000) + + postboxLog("Did open DB at \(path)") + + sqlite3_busy_timeout(database.handle, 5 * 1000) var resultCode: Bool = true @@ -306,8 +306,12 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) resultCode = database.execute("PRAGMA cipher_default_plaintext_header_size=32") assert(resultCode) + + postboxLog("Did set up cipher") if self.isEncrypted(database) { + postboxLog("Database is encrypted") + if let encryptionParameters = encryptionParameters { precondition(encryptionParameters.salt.data.count == 16) precondition(encryptionParameters.key.data.count == 32) @@ -316,12 +320,15 @@ public final class SqliteValueBox: ValueBox { resultCode = database.execute("PRAGMA key=\"x'\(hexKey)'\"") assert(resultCode) + + postboxLog("Setting encryption key") if self.isEncrypted(database) { + postboxLog("Encryption key is invalid") + if isTemporary || isReadOnly { return nil } - postboxLog("Encryption key is invalid") for fileName in dabaseFileNames { let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)") @@ -354,6 +361,8 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) } } else if let encryptionParameters = encryptionParameters, encryptionParameters.forceEncryptionIfNoSet { + postboxLog("Not encrypted") + let hexKey = hexString(encryptionParameters.key.data + encryptionParameters.salt.data) if FileManager.default.fileExists(atPath: path) { @@ -409,6 +418,8 @@ public final class SqliteValueBox: ValueBox { } } } + + postboxLog("Did set up encryption") //database.execute("PRAGMA cache_size=-2097152") resultCode = database.execute("PRAGMA mmap_size=0") @@ -421,6 +432,9 @@ public final class SqliteValueBox: ValueBox { assert(resultCode) resultCode = database.execute("PRAGMA cipher_memory_security = OFF") assert(resultCode) + + postboxLog("Did set up pragmas") + //resultCode = database.execute("PRAGMA wal_autocheckpoint=500") //database.execute("PRAGMA journal_size_limit=1536") @@ -441,8 +455,12 @@ public final class SqliteValueBox: ValueBox { let _ = self.runPragma(database, "checkpoint_fullfsync = 1") assert(self.runPragma(database, "checkpoint_fullfsync") == "1") + + postboxLog("Did set up checkpoint_fullfsync") self.beginInternal(database: database) + + postboxLog("Did begin transaction") let result = self.getUserVersion(database) @@ -462,8 +480,12 @@ public final class SqliteValueBox: ValueBox { for table in self.listFullTextTables(database) { self.fullTextTables[table.id] = table } + + postboxLog("Did load tables") self.commitInternal(database: database) + + postboxLog("Did commit final") lock.unlock() @@ -518,7 +540,21 @@ public final class SqliteValueBox: ValueBox { private func isEncrypted(_ database: Database) -> Bool { var statement: OpaquePointer? = nil + postboxLog("isEncrypted prepare...") + + let allIsOk = Atomic(value: false) + let databasePath = self.databasePath + DispatchQueue.global().asyncAfter(deadline: .now() + 5.0, execute: { + if allIsOk.with({ $0 }) == false { + postboxLog("Timeout reached, discarding database") + try? FileManager.default.removeItem(atPath: databasePath) + + preconditionFailure() + } + }) let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil) + let _ = allIsOk.swap(true) + postboxLog("isEncrypted prepare done") if statement == nil { postboxLog("isEncrypted: sqlite3_prepare_v2 status = \(status) [\(self.databasePath)]") return true @@ -536,6 +572,7 @@ public final class SqliteValueBox: ValueBox { preparedStatement.destroy() return true } + postboxLog("isEncrypted step done") preparedStatement.destroy() return status == SQLITE_NOTADB } From 6132c9ecc02fb8340083c802f2891646315e7476 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 6 Apr 2021 03:23:25 +0400 Subject: [PATCH 09/15] Bump version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index 9d5f5f8445..6071caeab1 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "7.6.1", + "app": "7.6.2", "bazel": "4.0.0", "xcode": "12.4" } From b075e31ecccc4db2119fa1a0ce8ef912c32a6029 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 6 Apr 2021 03:43:17 +0400 Subject: [PATCH 10/15] Fix build number offset --- build_number_offset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_number_offset b/build_number_offset index 29d6383b52..a97f893390 100644 --- a/build_number_offset +++ b/build_number_offset @@ -1 +1 @@ -100 +2100 From 6442f7925f96f4e40c0b18661877c9e141c4fd53 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 6 Apr 2021 10:52:48 +0400 Subject: [PATCH 11/15] Adjust timeout --- submodules/Postbox/Sources/SqliteValueBox.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index d58ab1e1ad..b9409cecc4 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -549,7 +549,7 @@ public final class SqliteValueBox: ValueBox { postboxLog("Timeout reached, discarding database") try? FileManager.default.removeItem(atPath: databasePath) - preconditionFailure() + exit(0) } }) let status = sqlite3_prepare_v2(database.handle, "SELECT * FROM sqlite_master LIMIT 1", -1, &statement, nil) From aa7a568197a7867afe6ba5accc19d400b300a68e Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 9 Apr 2021 01:30:50 +0400 Subject: [PATCH 12/15] Payments and more --- .../Sources/BotCheckoutActionButton.swift | 43 +++++++++---- .../Sources/BotCheckoutControllerNode.swift | 54 +++++++++++++---- .../Sources/BotCheckoutHeaderItem.swift | 11 ++-- .../Sources/BotCheckoutPriceItem.swift | 59 +++++++++--------- .../Sources/BotCheckoutTipItem.swift | 41 ++++++++++--- .../Sources/BotPaymentFieldItemNode.swift | 2 +- .../Sources/BotReceiptController.swift | 12 ++-- .../Sources/BotReceiptControllerNode.swift | 51 ++++++++++------ .../CurrencyUITextFieldDelegate.swift | 6 ++ .../Sources/DebugController.swift | 24 ++++++-- .../Sources/PresentationGroupCall.swift | 4 +- .../Sources/VoiceChatController.swift | 4 +- .../TelegramCore/Sources/BotPaymentForm.swift | 59 +++++++++++++++--- .../DefaultDarkPresentationTheme.swift | 8 ++- .../DefaultDarkTintedPresentationTheme.swift | 8 ++- .../Sources/DefaultDayPresentationTheme.swift | 8 ++- .../Sources/PresentationTheme.swift | 60 ++++++++++++++++++- .../Sources/PresentationThemeCodable.swift | 40 ++++++++++++- .../Sources/CurrencyFormat.swift | 6 +- .../TelegramUI/Sources/AppDelegate.swift | 4 +- .../TelegramUI/Sources/ChatController.swift | 9 +-- .../ChatMessageInteractiveMediaNode.swift | 2 +- .../Sources/ExperimentalUISettings.swift | 10 +++- .../Sources/GroupCallContext.swift | 8 +-- 24 files changed, 397 insertions(+), 136 deletions(-) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift index 70ccb3b58e..f62ab94796 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift @@ -43,7 +43,7 @@ enum BotCheckoutActionButtonState: Equatable { private let titleFont = Font.semibold(17.0) final class BotCheckoutActionButton: HighlightableButtonNode { - static var diameter: CGFloat = 48.0 + static var height: CGFloat = 52.0 private var inactiveFillColor: UIColor private var activeFillColor: UIColor @@ -62,12 +62,14 @@ final class BotCheckoutActionButton: HighlightableButtonNode { self.inactiveFillColor = inactiveFillColor self.activeFillColor = activeFillColor self.foregroundColor = foregroundColor + + let diameter: CGFloat = 20.0 self.progressBackgroundNode = ASImageNode() self.progressBackgroundNode.displaysAsynchronously = false self.progressBackgroundNode.displayWithoutProcessing = true self.progressBackgroundNode.isLayerBacked = true - self.progressBackgroundNode.image = generateImage(CGSize(width: BotCheckoutActionButton.diameter, height: BotCheckoutActionButton.diameter), rotatedContext: { size, context in + self.progressBackgroundNode.image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) let strokeWidth: CGFloat = 2.0 context.setFillColor(activeFillColor.cgColor) @@ -75,7 +77,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode { context.setFillColor(inactiveFillColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: strokeWidth, y: strokeWidth), size: CGSize(width: size.width - strokeWidth * 2.0, height: size.height - strokeWidth * 2.0))) - let cutout: CGFloat = 10.0 + let cutout: CGFloat = diameter context.fill(CGRect(origin: CGPoint(x: floor((size.width - cutout) / 2.0), y: 0.0), size: CGSize(width: cutout, height: cutout))) }) @@ -83,14 +85,14 @@ final class BotCheckoutActionButton: HighlightableButtonNode { self.inactiveBackgroundNode.displaysAsynchronously = false self.inactiveBackgroundNode.displayWithoutProcessing = true self.inactiveBackgroundNode.isLayerBacked = true - self.inactiveBackgroundNode.image = generateStretchableFilledCircleImage(diameter: BotCheckoutActionButton.diameter, color: self.foregroundColor, strokeColor: activeFillColor, strokeWidth: 2.0) + self.inactiveBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: self.foregroundColor, strokeColor: activeFillColor, strokeWidth: 2.0) self.inactiveBackgroundNode.alpha = 0.0 self.activeBackgroundNode = ASImageNode() self.activeBackgroundNode.displaysAsynchronously = false self.activeBackgroundNode.displayWithoutProcessing = true self.activeBackgroundNode.isLayerBacked = true - self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: BotCheckoutActionButton.diameter, color: activeFillColor) + self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: activeFillColor) self.labelNode = TextNode() self.labelNode.displaysAsynchronously = false @@ -178,10 +180,21 @@ final class BotCheckoutActionButton: HighlightableButtonNode { self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } case .applePay: - if case .applePay = previousState { - - } else { - + if self.applePayButton == nil { + if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { + let applePayButton: PKPaymentButton + if #available(iOS 14.0, *) { + applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black) + } else { + applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black) + } + applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside) + self.view.addSubview(applePayButton) + self.applePayButton = applePayButton + } + } + if let applePayButton = self.applePayButton { + applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: validLayout.width, height: BotCheckoutActionButton.height)) } } } else { @@ -226,15 +239,19 @@ final class BotCheckoutActionButton: HighlightableButtonNode { } } } + + @objc private func applePayButtonPressed() { + self.sendActions(forControlEvents: .touchUpInside, with: nil) + } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.validLayout = size - transition.updateFrame(node: self.progressBackgroundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - BotCheckoutActionButton.diameter) / 2.0), y: 0.0), size: CGSize(width: BotCheckoutActionButton.diameter, height: BotCheckoutActionButton.diameter))) - transition.updateFrame(node: self.inactiveBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter))) - transition.updateFrame(node: self.activeBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter))) + transition.updateFrame(node: self.progressBackgroundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - BotCheckoutActionButton.height) / 2.0), y: 0.0), size: CGSize(width: BotCheckoutActionButton.height, height: BotCheckoutActionButton.height))) + transition.updateFrame(node: self.inactiveBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height))) + transition.updateFrame(node: self.activeBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height))) if let applePayButton = self.applePayButton { - applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.diameter)) + applePayButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: BotCheckoutActionButton.height)) } var labelSize = self.labelNode.bounds.size diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 81e9b20aa9..67ec612242 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -55,7 +55,7 @@ enum BotCheckoutEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { case .header: - return BotCheckoutSection.header.rawValue + return BotCheckoutSection.prices.rawValue case .price, .tip: return BotCheckoutSection.prices.rawValue default: @@ -286,7 +286,7 @@ private func botCheckoutControllerEntries(presentationData: PresentationData, st var index = 0 for price in paymentForm.invoice.prices { - entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: paymentForm.invoice.currency), false, false)) + entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: paymentForm.invoice.currency), false, index == 0)) totalPrice += price.amount index += 1 } @@ -447,7 +447,9 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz private var currentPaymentMethod: BotCheckoutPaymentMethod? private var currentTipAmount: Int64? private var formRequestDisposable: Disposable? - + + private let actionButtonPanelNode: ASDisplayNode + private let actionButtonPanelSeparator: ASDisplayNode private let actionButton: BotCheckoutActionButton private let inProgressDimNode: ASDisplayNode @@ -481,13 +483,20 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz let signal: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError> = combineLatest(context.sharedContext.presentationData, self.state.get(), paymentFormAndInfo.get(), context.account.postbox.loadedPeerWithId(messageId.peerId)) |> map { presentationData, state, paymentFormAndInfo, botPeer -> (ItemListPresentationData, (ItemListNodeState, Any)) in - let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botCheckoutControllerEntries(presentationData: presentationData, state: state, invoice: invoice, paymentForm: paymentFormAndInfo?.0, formInfo: paymentFormAndInfo?.1, validatedFormInfo: paymentFormAndInfo?.2, currentShippingOptionId: paymentFormAndInfo?.3, currentPaymentMethod: paymentFormAndInfo?.4, currentTip: paymentFormAndInfo?.5, botPeer: botPeer), style: .plain, focusItemTag: nil, emptyStateItem: nil, animateChanges: false) + let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botCheckoutControllerEntries(presentationData: presentationData, state: state, invoice: invoice, paymentForm: paymentFormAndInfo?.0, formInfo: paymentFormAndInfo?.1, validatedFormInfo: paymentFormAndInfo?.2, currentShippingOptionId: paymentFormAndInfo?.3, currentPaymentMethod: paymentFormAndInfo?.4, currentTip: paymentFormAndInfo?.5, botPeer: botPeer), style: .blocks, focusItemTag: nil, emptyStateItem: nil, animateChanges: false) return (ItemListPresentationData(presentationData), (nodeState, arguments)) } + + self.actionButtonPanelNode = ASDisplayNode() + self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor + + self.actionButtonPanelSeparator = ASDisplayNode() + self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor) - self.actionButton.setState(.loading) + self.actionButton.setState(.active("")) + self.actionButtonPanelNode.isHidden = true self.inProgressDimNode = ASDisplayNode() self.inProgressDimNode.alpha = 0.0 @@ -522,6 +531,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo { strongSelf.currentPaymentMethod = method strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod, strongSelf.currentTipAmount))) + strongSelf.updateActionButton() } } @@ -753,10 +763,13 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz }, error: { _ in }) + + self.addSubnode(self.actionButtonPanelNode) + self.actionButtonPanelNode.addSubnode(self.actionButtonPanelSeparator) + self.actionButtonPanelNode.addSubnode(self.actionButton) self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside) self.actionButton.isEnabled = false - self.addSubnode(self.actionButton) self.listNode.supernode?.insertSubnode(self.inProgressDimNode, aboveSubnode: self.listNode) } @@ -775,21 +788,36 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } else { payString = self.presentationData.strings.CheckoutInfo_Pay } - if self.actionButton.isEnabled { - self.actionButton.setState(.active(payString)) + if let currentPaymentMethod = self.currentPaymentMethod { + switch currentPaymentMethod { + case .applePay: + self.actionButton.setState(.applePay) + default: + self.actionButton.setState(.active(payString)) + } } else { - self.actionButton.setState(.loading) + self.actionButton.setState(.active(payString)) } + self.actionButtonPanelNode.isHidden = false } override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) { var updatedInsets = layout.intrinsicInsets - updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0 - super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets) - - let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter)) + + let bottomPanelHorizontalInset: CGFloat = 16.0 + let bottomPanelVerticalInset: CGFloat = 16.0 + let bottomPanelHeight = updatedInsets.bottom + bottomPanelVerticalInset * 2.0 + BotCheckoutActionButton.height + + transition.updateFrame(node: self.actionButtonPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: layout.size.width, height: bottomPanelHeight))) + transition.updateFrame(node: self.actionButtonPanelSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + let actionButtonFrame = CGRect(origin: CGPoint(x: bottomPanelHorizontalInset, y: bottomPanelVerticalInset), size: CGSize(width: layout.size.width - bottomPanelHorizontalInset * 2.0, height: BotCheckoutActionButton.height)) transition.updateFrame(node: self.actionButton, frame: actionButtonFrame) self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition) + + updatedInsets.bottom = bottomPanelHeight + + super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets) transition.updateFrame(node: self.inProgressDimNode, frame: self.listNode.frame) } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift index ada9ca2d90..2946d5ca0b 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift @@ -80,7 +80,6 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true - self.backgroundNode.backgroundColor = .white self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -109,7 +108,8 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { self.highlightedBackgroundNode.isLayerBacked = true super.init(layerBacked: false, dynamicBounce: false) - + + self.addSubnode(self.backgroundNode) self.addSubnode(self.imageNode) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) @@ -209,9 +209,9 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { } strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: contentInsets.left, y: contentInsets.top), size: imageSize) - if strongSelf.backgroundNode.supernode != nil { + /*if strongSelf.backgroundNode.supernode != nil { strongSelf.backgroundNode.removeFromSupernode() - } + }*/ if strongSelf.topStripeNode.supernode != nil { strongSelf.topStripeNode.removeFromSupernode() } @@ -231,7 +231,8 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { strongSelf.textNode.frame = textFrame strongSelf.botNameNode.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + textBotNameSpacing), size: botNameLayout.size) - + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1000.0), size: CGSize(width: params.width, height: contentSize.height + 1000.0)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel)) } }) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift index 7e711a4ffe..aa374a19f4 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift @@ -29,7 +29,7 @@ class BotCheckoutPriceItem: ListViewItem, ItemListItem { func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { let node = BotCheckoutPriceItemNode() - let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), previousItem, nextItem) node.contentSize = layout.contentSize node.insets = layout.insets @@ -48,7 +48,7 @@ class BotCheckoutPriceItem: ListViewItem, ItemListItem { let makeLayout = nodeValue.asyncLayout() async { - let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), previousItem, nextItem) Queue.mainQueue().async { completion(layout, { _ in apply() @@ -69,13 +69,13 @@ private func priceItemInsets(_ neighbors: ItemListNeighbors) -> UIEdgeInsets { var insets = UIEdgeInsets() switch neighbors.top { case .otherSection: - insets.top += 8.0 + insets.top += 24.0 case .none, .sameSection: break } switch neighbors.bottom { case .none, .otherSection: - insets.bottom += 8.0 + insets.bottom += 24.0 case .sameSection: break } @@ -86,9 +86,9 @@ class BotCheckoutPriceItemNode: ListViewItemNode { let titleNode: TextNode let labelNode: TextNode + let backgroundNode: ASDisplayNode let separatorNode: ASDisplayNode let bottomSeparatorNode: ASDisplayNode - let spacerNode: ASDisplayNode private var item: BotCheckoutPriceItem? @@ -99,37 +99,44 @@ class BotCheckoutPriceItemNode: ListViewItemNode { self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false + self.backgroundNode = ASDisplayNode() self.separatorNode = ASDisplayNode() self.bottomSeparatorNode = ASDisplayNode() - self.spacerNode = ASDisplayNode() super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.spacerNode) + self.addSubnode(self.backgroundNode) self.addSubnode(self.titleNode) self.addSubnode(self.labelNode) self.addSubnode(self.separatorNode) self.addSubnode(self.bottomSeparatorNode) } - func asyncLayout() -> (_ item: BotCheckoutPriceItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + func asyncLayout() -> (_ item: BotCheckoutPriceItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors, _ previousItem: ListViewItem?, _ nextItem: ListViewItem?) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode) - return { item, params, neighbors in + return { item, params, neighbors, previousItem, nextItem in let rightInset: CGFloat = 16.0 + params.rightInset - let naturalContentHeight: CGFloat = 34.0 - - var contentSize = CGSize(width: params.width, height: naturalContentHeight) - var insets = priceItemInsets(neighbors) - - if item.hasSeparator { - insets.top += 5.0 - } + let naturalContentHeight: CGFloat + var verticalOffset: CGFloat = 0.0 if item.isFinal { - contentSize.height += 34.0 + naturalContentHeight = 44.0 + } else { + naturalContentHeight = 34.0 } + if let _ = previousItem as? BotCheckoutHeaderItem { + verticalOffset += 8.0 + } + + var contentSize = CGSize(width: params.width, height: naturalContentHeight + verticalOffset) + if let nextItem = nextItem as? BotCheckoutPriceItem { + if nextItem.isFinal { + contentSize.height += 8.0 + } + } + let insets = priceItemInsets(neighbors) let textFont: UIFont let textColor: UIColor @@ -154,21 +161,15 @@ class BotCheckoutPriceItemNode: ListViewItemNode { let leftInset: CGFloat = 16.0 + params.leftInset strongSelf.separatorNode.isHidden = !item.hasSeparator - strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor + strongSelf.separatorNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) strongSelf.bottomSeparatorNode.isHidden = !item.isFinal - strongSelf.bottomSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.bottomSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: naturalContentHeight + 10.0), size: CGSize(width: params.width, height: UIScreenPixel)) + strongSelf.bottomSeparatorNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + strongSelf.bottomSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: CGSize(width: params.width, height: UIScreenPixel)) - strongSelf.spacerNode.isHidden = !item.isFinal - strongSelf.spacerNode.backgroundColor = item.theme.list.blocksBackgroundColor - strongSelf.spacerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: naturalContentHeight + 10.0 + UIScreenPixel), size: CGSize(width: params.width, height: max(0.0, contentSize.height - naturalContentHeight - UIScreenPixel))) - - var verticalOffset: CGFloat = 0.0 - if item.hasSeparator { - verticalOffset += 5.0 - } + strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height)) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalOffset + floor((naturalContentHeight - titleLayout.size.height) / 2.0)), size: titleLayout.size) strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: verticalOffset + floor((naturalContentHeight - labelLayout.size.height) / 2.0)), size: labelLayout.size) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift index f2f184efad..10a99ff846 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift @@ -122,7 +122,7 @@ private final class TipValueNode: ASDisplayNode { func update(theme: PresentationTheme, text: String, isHighlighted: Bool, height: CGFloat) -> (CGFloat, (CGFloat) -> Void) { var updateBackground = false - let backgroundColor = isHighlighted ? UIColor(rgb: 0x00A650) : UIColor(rgb: 0xE5F6ED) + let backgroundColor = isHighlighted ? theme.list.paymentOption.activeFillColor : theme.list.paymentOption.inactiveFillColor if let currentBackgroundColor = self.currentBackgroundColor { if !currentBackgroundColor.isEqual(backgroundColor) { updateBackground = true @@ -135,7 +135,7 @@ private final class TipValueNode: ASDisplayNode { self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: backgroundColor) } - self.titleNode.attributedText = NSAttributedString(string: text, font: Font.semibold(15.0), textColor: isHighlighted ? UIColor(rgb: 0xffffff) : UIColor(rgb: 0x00A650)) + self.titleNode.attributedText = NSAttributedString(string: text, font: Font.semibold(15.0), textColor: isHighlighted ? theme.list.paymentOption.activeForegroundColor : theme.list.paymentOption.inactiveForegroundColor) let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: height)) let minWidth: CGFloat = 80.0 @@ -154,20 +154,23 @@ private final class TipValueNode: ASDisplayNode { } class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { + private let backgroundNode: ASDisplayNode let titleNode: TextNode let labelNode: TextNode let tipMeasurementNode: ImmediateTextNode let tipCurrencyNode: ImmediateTextNode private let textNode: TextFieldNode - private var formatterDelegate: CurrencyUITextFieldDelegate? - private let scrollNode: ASScrollNode private var valueNodes: [TipValueNode] = [] private var item: BotCheckoutTipItem? + + private var formatterDelegate: CurrencyUITextFieldDelegate? init() { + self.backgroundNode = ASDisplayNode() + self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false @@ -191,6 +194,8 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { } super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.backgroundNode) self.addSubnode(self.titleNode) self.addSubnode(self.labelNode) @@ -272,7 +277,11 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { var textInputFrame = CGRect(origin: CGPoint(x: params.width - leftInset - 150.0, y: -2.0), size: CGSize(width: 150.0, height: labelsContentHeight)) - var currencyText: (String, String) = formatCurrencyAmountCustom(item.numericValue, currency: item.currency) + let currencyText: (String, String, Bool) = formatCurrencyAmountCustom(item.numericValue, currency: item.currency) + + let currencySymbolOnTheLeft = currencyText.2 + //let currencySymbolOnTheLeft = true + if strongSelf.textNode.textField.text ?? "" != currencyText.0 { strongSelf.textNode.textField.text = currencyText.0 strongSelf.labelNode.isHidden = !currencyText.0.isEmpty @@ -281,10 +290,16 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { strongSelf.tipMeasurementNode.attributedText = NSAttributedString(string: currencyText.0, font: titleFont, textColor: textColor) let inputTextSize = strongSelf.tipMeasurementNode.updateLayout(textInputFrame.size) - strongSelf.tipCurrencyNode.attributedText = NSAttributedString(string: " \(currencyText.1)", font: titleFont, textColor: textColor) + let spaceRect = NSAttributedString(string: " ", font: titleFont, textColor: textColor).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + + strongSelf.tipCurrencyNode.attributedText = NSAttributedString(string: "\(currencyText.1)", font: titleFont, textColor: textColor) let currencySize = strongSelf.tipCurrencyNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude)) - strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize) - textInputFrame.origin.x -= currencySize.width + if currencySymbolOnTheLeft { + strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width - inputTextSize.width - spaceRect.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize) + } else { + strongSelf.tipCurrencyNode.frame = CGRect(origin: CGPoint(x: textInputFrame.maxX - currencySize.width, y: floor((labelsContentHeight - currencySize.height) / 2.0) - 1.0), size: currencySize) + textInputFrame.origin.x -= currencySize.width + spaceRect.width + } strongSelf.textNode.frame = textInputFrame @@ -347,6 +362,9 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: valueY), size: CGSize(width: params.width, height: max(0.0, contentSize.height - valueY))) strongSelf.scrollNode.view.contentSize = CGSize(width: variantsOffset, height: strongSelf.scrollNode.frame.height) + + strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: contentSize.height)) } }) } @@ -382,7 +400,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { if value > item.maxValue { value = item.maxValue - let currencyText: (String, String) = formatCurrencyAmountCustom(value, currency: item.currency) + let currencyText = formatCurrencyAmountCustom(value, currency: item.currency) if self.textNode.textField.text ?? "" != currencyText.0 { self.textNode.textField.text = currencyText.0 } @@ -400,6 +418,11 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + textField.selectedTextRange = textField.textRange(from: textField.endOfDocument, to: textField.endOfDocument) + } + + @objc public func textFieldDidChangeSelection(_ textField: UITextField) { + textField.selectedTextRange = textField.textRange(from: textField.endOfDocument, to: textField.endOfDocument) } @objc public func textFieldDidEndEditing(_ textField: UITextField) { diff --git a/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift b/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift index 615488fc73..947331fc9c 100644 --- a/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift @@ -117,7 +117,7 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate { textInset = max(measuredInset, textInset) - transition.updateFrame(node: self.textField, frame: CGRect(origin: CGPoint(x: textInset, y: 3.0), size: CGSize(width: max(1.0, width - textInset - 8.0), height: 40.0))) + transition.updateFrame(node: self.textField, frame: CGRect(origin: CGPoint(x: textInset, y: 0.0), size: CGSize(width: max(1.0, width - textInset - 8.0), height: 40.0))) return 44.0 } diff --git a/submodules/BotPaymentsUI/Sources/BotReceiptController.swift b/submodules/BotPaymentsUI/Sources/BotReceiptController.swift index 35522c55cb..7469586563 100644 --- a/submodules/BotPaymentsUI/Sources/BotReceiptController.swift +++ b/submodules/BotPaymentsUI/Sources/BotReceiptController.swift @@ -20,16 +20,14 @@ public final class BotReceiptController: ViewController { } private let context: AccountContext - private let invoice: TelegramMediaInvoice private let messageId: MessageId private var presentationData: PresentationData private var didPlayPresentationAnimation = false - public init(context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId) { + public init(context: AccountContext, messageId: MessageId) { self.context = context - self.invoice = invoice self.messageId = messageId self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -38,10 +36,10 @@ public final class BotReceiptController: ViewController { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - var title = self.presentationData.strings.Checkout_Receipt_Title - if invoice.flags.contains(.isTest) { + let title = self.presentationData.strings.Checkout_Receipt_Title + /*if invoice.flags.contains(.isTest) { title += " (Test)" - } + }*/ self.title = title } @@ -54,7 +52,7 @@ public final class BotReceiptController: ViewController { if let strongSelf = self { strongSelf.navigationOffset = offset } - }, context: self.context, invoice: self.invoice, messageId: self.messageId, dismissAnimated: { [weak self] in + }, context: self.context, messageId: self.messageId, dismissAnimated: { [weak self] in self?.dismiss() }) diff --git a/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift index c7159fb6bf..d667482776 100644 --- a/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift @@ -28,7 +28,7 @@ private enum BotReceiptSection: Int32 { enum BotReceiptEntry: ItemListNodeEntry { case header(PresentationTheme, TelegramMediaInvoice, String) - case price(Int, PresentationTheme, String, String, Bool) + case price(Int, PresentationTheme, String, String, Bool, Bool) case paymentMethod(PresentationTheme, String, String) case shippingInfo(PresentationTheme, String, String) case shippingMethod(PresentationTheme, String, String) @@ -39,7 +39,7 @@ enum BotReceiptEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { case .header: - return BotReceiptSection.header.rawValue + return BotReceiptSection.prices.rawValue case .price: return BotReceiptSection.prices.rawValue default: @@ -51,7 +51,7 @@ enum BotReceiptEntry: ItemListNodeEntry { switch self { case .header: return 0 - case let .price(index, _, _, _, _): + case let .price(index, _, _, _, _, _): return 1 + Int32(index) case .paymentMethod: return 10000 + 0 @@ -85,8 +85,8 @@ enum BotReceiptEntry: ItemListNodeEntry { } else { return false } - case let .price(lhsIndex, lhsTheme, lhsText, lhsValue, lhsFinal): - if case let .price(rhsIndex, rhsTheme, rhsText, rhsValue, rhsFinal) = rhs { + case let .price(lhsIndex, lhsTheme, lhsText, lhsValue, lhsHasSeparator, lhsFinal): + if case let .price(rhsIndex, rhsTheme, rhsText, rhsValue, rhsHasSeparator, rhsFinal) = rhs { if lhsIndex != rhsIndex { return false } @@ -99,6 +99,9 @@ enum BotReceiptEntry: ItemListNodeEntry { if lhsValue != rhsValue { return false } + if lhsHasSeparator != rhsHasSeparator { + return false + } if lhsFinal != rhsFinal { return false } @@ -154,8 +157,8 @@ enum BotReceiptEntry: ItemListNodeEntry { switch self { case let .header(theme, invoice, botName): return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section) - case let .price(_, theme, text, value, isFinal): - return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: false, sectionId: self.section) + case let .price(_, theme, text, value, hasSeparator, isFinal): + return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, hasSeparator: hasSeparator, sectionId: self.section) case let .paymentMethod(_, text, value): return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: nil) case let .shippingInfo(_, text, value): @@ -172,21 +175,23 @@ enum BotReceiptEntry: ItemListNodeEntry { } } -private func botReceiptControllerEntries(presentationData: PresentationData, invoice: TelegramMediaInvoice, formInvoice: BotPaymentInvoice?, formInfo: BotPaymentRequestedInfo?, shippingOption: BotPaymentShippingOption?, paymentMethodTitle: String?, botPeer: Peer?) -> [BotReceiptEntry] { +private func botReceiptControllerEntries(presentationData: PresentationData, invoice: TelegramMediaInvoice?, formInvoice: BotPaymentInvoice?, formInfo: BotPaymentRequestedInfo?, shippingOption: BotPaymentShippingOption?, paymentMethodTitle: String?, botPeer: Peer?, tipAmount: Int64?) -> [BotReceiptEntry] { var entries: [BotReceiptEntry] = [] var botName = "" if let botPeer = botPeer { botName = botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } - entries.append(.header(presentationData.theme, invoice, botName)) + if let invoice = invoice { + entries.append(.header(presentationData.theme, invoice, botName)) + } if let formInvoice = formInvoice { var totalPrice: Int64 = 0 var index = 0 for price in formInvoice.prices { - entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), false)) + entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), index == 0, false)) totalPrice += price.amount index += 1 } @@ -196,13 +201,20 @@ private func botReceiptControllerEntries(presentationData: PresentationData, inv shippingOptionString = shippingOption.title for price in shippingOption.prices { - entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), false)) + entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: formInvoice.currency), index == 0, false)) totalPrice += price.amount index += 1 } } + + if let tipAmount = tipAmount, tipAmount != 0 { + //TODO:localize + entries.append(.price(index, presentationData.theme, "Tip", formatCurrencyAmount(tipAmount, currency: formInvoice.currency), index == 0, false)) + totalPrice += tipAmount + index += 1 + } - entries.append(.price(index, presentationData.theme, presentationData.strings.Checkout_TotalAmount, formatCurrencyAmount(totalPrice, currency: formInvoice.currency), true)) + entries.append(.price(index, presentationData.theme, presentationData.strings.Checkout_TotalAmount, formatCurrencyAmount(totalPrice, currency: formInvoice.currency), true, true)) if let paymentMethodTitle = paymentMethodTitle { entries.append(.paymentMethod(presentationData.theme, presentationData.strings.Checkout_PaymentMethod, paymentMethodTitle)) @@ -262,12 +274,12 @@ final class BotReceiptControllerNode: ItemListControllerNode { private var presentationData: PresentationData - private let receiptData = Promise<(BotPaymentInvoice, BotPaymentRequestedInfo?, BotPaymentShippingOption?, String?)?>(nil) + private let receiptData = Promise<(BotPaymentInvoice, BotPaymentRequestedInfo?, BotPaymentShippingOption?, String?, TelegramMediaInvoice, Int64?)?>(nil) private var dataRequestDisposable: Disposable? private let actionButton: BotCheckoutActionButton - init(controller: ItemListController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, dismissAnimated: @escaping () -> Void) { + init(controller: ItemListController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, messageId: MessageId, dismissAnimated: @escaping () -> Void) { self.context = context self.dismissAnimated = dismissAnimated @@ -277,19 +289,19 @@ final class BotReceiptControllerNode: ItemListControllerNode { let signal: Signal<(ItemListPresentationData, (ItemListNodeState, Any)), NoError> = combineLatest(context.sharedContext.presentationData, receiptData.get(), context.account.postbox.loadedPeerWithId(messageId.peerId)) |> map { presentationData, receiptData, botPeer -> (ItemListPresentationData, (ItemListNodeState, Any)) in - let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botReceiptControllerEntries(presentationData: presentationData, invoice: invoice, formInvoice: receiptData?.0, formInfo: receiptData?.1, shippingOption: receiptData?.2, paymentMethodTitle: receiptData?.3, botPeer: botPeer), style: .plain, focusItemTag: nil, emptyStateItem: nil, animateChanges: false) + let nodeState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: botReceiptControllerEntries(presentationData: presentationData, invoice: receiptData?.4, formInvoice: receiptData?.0, formInfo: receiptData?.1, shippingOption: receiptData?.2, paymentMethodTitle: receiptData?.3, botPeer: botPeer, tipAmount: receiptData?.5), style: .blocks, focusItemTag: nil, emptyStateItem: nil, animateChanges: false) return (ItemListPresentationData(presentationData), (nodeState, arguments)) } self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.plainBackgroundColor) - self.actionButton.setState(.inactive(self.presentationData.strings.Common_Done)) + self.actionButton.setState(.active(self.presentationData.strings.Common_Done)) super.init(controller: controller, navigationBar: navigationBar, updateNavigationOffset: updateNavigationOffset, state: signal) self.dataRequestDisposable = (requestBotPaymentReceipt(account: context.account, messageId: messageId) |> deliverOnMainQueue).start(next: { [weak self] receipt in if let strongSelf = self { - strongSelf.receiptData.set(.single((receipt.invoice, receipt.info, receipt.shippingOption, receipt.credentialsTitle))) + strongSelf.receiptData.set(.single((receipt.invoice, receipt.info, receipt.shippingOption, receipt.credentialsTitle, receipt.invoiceMedia, receipt.tipAmount))) } }) @@ -303,10 +315,11 @@ final class BotReceiptControllerNode: ItemListControllerNode { override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition, additionalInsets: UIEdgeInsets) { var updatedInsets = layout.intrinsicInsets - updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0 + updatedInsets.bottom += BotCheckoutActionButton.height + 16.0 * 2.0 + super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition, additionalInsets: additionalInsets) - let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter)) + let actionButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: layout.size.height - 16.0 - BotCheckoutActionButton.height - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 16.0 * 2.0, height: BotCheckoutActionButton.height)) transition.updateFrame(node: self.actionButton, frame: actionButtonFrame) self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition) } diff --git a/submodules/BotPaymentsUI/Sources/UITextFieldDelegate/CurrencyUITextFieldDelegate.swift b/submodules/BotPaymentsUI/Sources/UITextFieldDelegate/CurrencyUITextFieldDelegate.swift index 330214903e..5eeb602a6e 100644 --- a/submodules/BotPaymentsUI/Sources/UITextFieldDelegate/CurrencyUITextFieldDelegate.swift +++ b/submodules/BotPaymentsUI/Sources/UITextFieldDelegate/CurrencyUITextFieldDelegate.swift @@ -108,6 +108,12 @@ extension CurrencyUITextFieldDelegate: UITextFieldDelegate { return false } + + public func textFieldDidChangeSelection(_ textField: UITextField) { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + passthroughDelegate?.textFieldDidChangeSelection?(textField) + } + } } // MARK: - Private diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 891c535c8c..70fdd3a439 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -74,6 +74,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case knockoutWallpaper(PresentationTheme, Bool) case demoVideoChats(Bool) case experimentalCompatibility(Bool) + case enableNoiseSuppression(Bool) case playerEmbedding(Bool) case playlistPlayback(Bool) case voiceConference @@ -93,7 +94,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .experimentalCompatibility, .playerEmbedding, .playlistPlayback, .voiceConference: + case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .experimentalCompatibility, .enableNoiseSuppression, .playerEmbedding, .playlistPlayback, .voiceConference: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -158,14 +159,16 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 24 case .experimentalCompatibility: return 25 - case .playerEmbedding: + case .enableNoiseSuppression: return 26 - case .playlistPlayback: + case .playerEmbedding: return 27 - case .voiceConference: + case .playlistPlayback: return 28 + case .voiceConference: + return 29 case let .preferredVideoCodec(index, _, _, _): - return 29 + index + return 30 + index case .disableVideoAspectScaling: return 100 case .enableVoipTcp: @@ -725,6 +728,16 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) + case let .enableNoiseSuppression(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Noise Suppression", value: value, sectionId: self.section, style: .blocks, updated: { value in + let _ = arguments.sharedContext.accountManager.transaction ({ transaction in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in + var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings + settings.enableNoiseSuppression = value + return settings + }) + }).start() + }) case let .playerEmbedding(value): return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in @@ -834,6 +847,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper)) entries.append(.demoVideoChats(experimentalSettings.demoVideoChats)) entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility)) + entries.append(.enableNoiseSuppression(experimentalSettings.enableNoiseSuppression)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 1ace64c919..4ce7c32901 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1082,6 +1082,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { outgoingAudioBitrateKbit = value } + let enableNoiseSuppression = accountContext.sharedContext.immediateExperimentalUISettings.enableNoiseSuppression + callContext = OngoingGroupCallContext(video: self.videoCapturer, participantDescriptionsRequired: { [weak self] ssrcs in Queue.mainQueue().async { guard let strongSelf = self else { @@ -1098,7 +1100,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { strongSelf.requestCall(movingFromBroadcastToRtc: false) } } - }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: self.isVideo) + }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: self.isVideo, enableNoiseSuppression: enableNoiseSuppression) self.incomingVideoSourcePromise.set(callContext.videoSources |> deliverOnMainQueue |> map { [weak self] sources -> [PeerId: UInt32] in diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index dd553d7919..7eda0af0a5 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1994,7 +1994,7 @@ public final class VoiceChatController: ViewController { } } - items.append(.action(ContextMenuActionItem(text: strongSelf.isNoiseSuppressionEnabled ? "Disable Noise Suppression" : "Enable Noise Suppression", textColor: .primary, icon: { theme in + /*items.append(.action(ContextMenuActionItem(text: strongSelf.isNoiseSuppressionEnabled ? "Disable Noise Suppression" : "Enable Noise Suppression", textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.dismissWithoutContent) @@ -2004,7 +2004,7 @@ public final class VoiceChatController: ViewController { } strongSelf.call.setIsNoiseSuppressionEnabled(!strongSelf.isNoiseSuppressionEnabled) - }))) + })))*/ if let callState = strongSelf.callState, callState.canManageCall { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in diff --git a/submodules/TelegramCore/Sources/BotPaymentForm.swift b/submodules/TelegramCore/Sources/BotPaymentForm.swift index 6ce470b456..50fa562f81 100644 --- a/submodules/TelegramCore/Sources/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/BotPaymentForm.swift @@ -393,11 +393,13 @@ public func sendBotPaymentForm(account: Account, messageId: MessageId, formId: I } } -public struct BotPaymentReceipt : Equatable { +public struct BotPaymentReceipt { public let invoice: BotPaymentInvoice public let info: BotPaymentRequestedInfo? public let shippingOption: BotPaymentShippingOption? public let credentialsTitle: String + public let invoiceMedia: TelegramMediaInvoice + public let tipAmount: Int64? } public enum RequestBotPaymentReceiptError { @@ -418,14 +420,55 @@ public func requestBotPaymentReceipt(account: Account, messageId: MessageId) -> |> mapError { _ -> RequestBotPaymentReceiptError in return .generic } - |> map { result -> BotPaymentReceipt in - switch result { - case let .paymentReceipt(flags, date, botId, providerId, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users): - let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) - let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init) - let shippingOption = shipping.flatMap(BotPaymentShippingOption.init) - return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle) + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> BotPaymentReceipt in + switch result { + case let .paymentReceipt(flags, date, botId, providerId, title, description, photo, invoice, info, shipping, tipAmount, currency, totalAmount, credentialsTitle, users): + var peers: [Peer] = [] + for user in users { + peers.append(TelegramUser(user: user)) + } + updatePeers(transaction: transaction, peers: peers, update: { _, updated in return updated }) + + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + let parsedInfo = info.flatMap(BotPaymentRequestedInfo.init) + let shippingOption = shipping.flatMap(BotPaymentShippingOption.init) + + /*let fields = BotPaymentInvoiceFields() + + let form = BotPaymentForm( + id: 0, + canSaveCredentials: false, + passwordMissing: false, + invoice: BotPaymentInvoice( + isTest: false, + requestedFields: fields, + currency: currency, + prices: [], + tip: nil + ), + providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt32Value(providerId)), + url: "", + nativeProvider: nil, + savedInfo: nil, + savedCredentials: nil + )*/ + + let invoiceMedia = TelegramMediaInvoice( + title: title, + description: description, + photo: photo.flatMap(TelegramMediaWebFile.init), + receiptMessageId: nil, + currency: currency, + totalAmount: totalAmount, + startParam: "", + flags: [] + ) + + return BotPaymentReceipt(invoice: parsedInvoice, info: parsedInfo, shippingOption: shippingOption, credentialsTitle: credentialsTitle, invoiceMedia: invoiceMedia, tipAmount: tipAmount) + } } + |> castError(RequestBotPaymentReceiptError.self) } } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index f81fe6f02d..38173cc8d0 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -359,7 +359,13 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati pageIndicatorInactiveColor: UIColor(white: 1.0, alpha: 0.3), inputClearButtonColor: UIColor(rgb: 0x8b9197), itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0xffffff), color2: UIColor(rgb: 0x929196), color3: UIColor(rgb: 0x333333)), - itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f)) + itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0x0f0f0f), strokeColor: UIColor(rgb: 0x0f0f0f), placeholderColor: UIColor(rgb: 0x8f8f8f), primaryColor: UIColor(rgb: 0xffffff), controlColor: UIColor(rgb: 0x8f8f8f)), + paymentOption: PresentationThemeList.PaymentOption( + inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.3), + inactiveForegroundColor: UIColor(rgb: 0x00A650), + activeFillColor: UIColor(rgb: 0x00A650), + activeForegroundColor: UIColor(rgb: 0xffffff) + ) ) let chatList = PresentationThemeChatList( diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 9a9326fe19..b6e84f35f3 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -612,7 +612,13 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres pageIndicatorInactiveColor: mainSecondaryTextColor.withAlphaComponent(0.4), inputClearButtonColor: mainSecondaryColor, itemBarChart: PresentationThemeItemBarChart(color1: accentColor, color2: mainSecondaryTextColor.withAlphaComponent(0.5), color3: accentColor.withMultiplied(hue: 1.038, saturation: 0.329, brightness: 0.33)), - itemInputField: PresentationInputFieldTheme(backgroundColor: mainInputColor, strokeColor: mainInputColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor) + itemInputField: PresentationInputFieldTheme(backgroundColor: mainInputColor, strokeColor: mainInputColor, placeholderColor: mainSecondaryColor, primaryColor: UIColor(rgb: 0xffffff), controlColor: mainSecondaryColor), + paymentOption: PresentationThemeList.PaymentOption( + inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.3), + inactiveForegroundColor: UIColor(rgb: 0x00A650), + activeFillColor: UIColor(rgb: 0x00A650), + activeForegroundColor: UIColor(rgb: 0xffffff) + ) ) let chatList = PresentationThemeChatList( diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index a980bbbb11..52b547bb3a 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -448,7 +448,13 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio pageIndicatorInactiveColor: UIColor(rgb: 0xe3e3e7), inputClearButtonColor: UIColor(rgb: 0xcccccc), itemBarChart: PresentationThemeItemBarChart(color1: UIColor(rgb: 0x007ee5), color2: UIColor(rgb: 0xc8c7cc), color3: UIColor(rgb: 0xf2f1f7)), - itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0xf2f2f7), strokeColor: UIColor(rgb: 0xf2f2f7), placeholderColor: UIColor(rgb: 0xb6b6bb), primaryColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0xb6b6bb)) + itemInputField: PresentationInputFieldTheme(backgroundColor: UIColor(rgb: 0xf2f2f7), strokeColor: UIColor(rgb: 0xf2f2f7), placeholderColor: UIColor(rgb: 0xb6b6bb), primaryColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0xb6b6bb)), + paymentOption: PresentationThemeList.PaymentOption( + inactiveFillColor: UIColor(rgb: 0x00A650).withMultipliedAlpha(0.1), + inactiveForegroundColor: UIColor(rgb: 0x00A650), + activeFillColor: UIColor(rgb: 0x00A650), + activeForegroundColor: UIColor(rgb: 0xffffff) + ) ) let chatList = PresentationThemeChatList( diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index 6a170942b0..6463a32f4f 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -406,6 +406,25 @@ public final class PresentationInputFieldTheme { } public final class PresentationThemeList { + public final class PaymentOption { + public let inactiveFillColor: UIColor + public let inactiveForegroundColor: UIColor + public let activeFillColor: UIColor + public let activeForegroundColor: UIColor + + public init( + inactiveFillColor: UIColor, + inactiveForegroundColor: UIColor, + activeFillColor: UIColor, + activeForegroundColor: UIColor + ) { + self.inactiveFillColor = inactiveFillColor + self.inactiveForegroundColor = inactiveForegroundColor + self.activeFillColor = activeFillColor + self.activeForegroundColor = activeForegroundColor + } + } + public let blocksBackgroundColor: UIColor public let plainBackgroundColor: UIColor public let itemPrimaryTextColor: UIColor @@ -437,8 +456,42 @@ public final class PresentationThemeList { public let inputClearButtonColor: UIColor public let itemBarChart: PresentationThemeItemBarChart public let itemInputField: PresentationInputFieldTheme + public let paymentOption: PaymentOption - public init(blocksBackgroundColor: UIColor, plainBackgroundColor: UIColor, itemPrimaryTextColor: UIColor, itemSecondaryTextColor: UIColor, itemDisabledTextColor: UIColor, itemAccentColor: UIColor, itemHighlightedColor: UIColor, itemDestructiveColor: UIColor, itemPlaceholderTextColor: UIColor, itemBlocksBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, itemBlocksSeparatorColor: UIColor, itemPlainSeparatorColor: UIColor, disclosureArrowColor: UIColor, sectionHeaderTextColor: UIColor, freeTextColor: UIColor, freeTextErrorColor: UIColor, freeTextSuccessColor: UIColor, freeMonoIconColor: UIColor, itemSwitchColors: PresentationThemeSwitch, itemDisclosureActions: PresentationThemeItemDisclosureActions, itemCheckColors: PresentationThemeFillStrokeForeground, controlSecondaryColor: UIColor, freeInputField: PresentationInputFieldTheme, freePlainInputField: PresentationInputFieldTheme, mediaPlaceholderColor: UIColor, scrollIndicatorColor: UIColor, pageIndicatorInactiveColor: UIColor, inputClearButtonColor: UIColor, itemBarChart: PresentationThemeItemBarChart, itemInputField: PresentationInputFieldTheme) { + public init( + blocksBackgroundColor: UIColor, + plainBackgroundColor: UIColor, + itemPrimaryTextColor: UIColor, + itemSecondaryTextColor: UIColor, + itemDisabledTextColor: UIColor, + itemAccentColor: UIColor, + itemHighlightedColor: UIColor, + itemDestructiveColor: UIColor, + itemPlaceholderTextColor: UIColor, + itemBlocksBackgroundColor: UIColor, + itemHighlightedBackgroundColor: UIColor, + itemBlocksSeparatorColor: UIColor, + itemPlainSeparatorColor: UIColor, + disclosureArrowColor: UIColor, + sectionHeaderTextColor: UIColor, + freeTextColor: UIColor, + freeTextErrorColor: UIColor, + freeTextSuccessColor: UIColor, + freeMonoIconColor: UIColor, + itemSwitchColors: PresentationThemeSwitch, + itemDisclosureActions: PresentationThemeItemDisclosureActions, + itemCheckColors: PresentationThemeFillStrokeForeground, + controlSecondaryColor: UIColor, + freeInputField: PresentationInputFieldTheme, + freePlainInputField: PresentationInputFieldTheme, + mediaPlaceholderColor: UIColor, + scrollIndicatorColor: UIColor, + pageIndicatorInactiveColor: UIColor, + inputClearButtonColor: UIColor, + itemBarChart: PresentationThemeItemBarChart, + itemInputField: PresentationInputFieldTheme, + paymentOption: PaymentOption + ) { self.blocksBackgroundColor = blocksBackgroundColor self.plainBackgroundColor = plainBackgroundColor self.itemPrimaryTextColor = itemPrimaryTextColor @@ -470,10 +523,11 @@ public final class PresentationThemeList { self.inputClearButtonColor = inputClearButtonColor self.itemBarChart = itemBarChart self.itemInputField = itemInputField + self.paymentOption = paymentOption } - public func withUpdated(blocksBackgroundColor: UIColor? = nil, plainBackgroundColor: UIColor? = nil, itemPrimaryTextColor: UIColor? = nil, itemSecondaryTextColor: UIColor? = nil, itemDisabledTextColor: UIColor? = nil, itemAccentColor: UIColor? = nil, itemHighlightedColor: UIColor? = nil, itemDestructiveColor: UIColor? = nil, itemPlaceholderTextColor: UIColor? = nil, itemBlocksBackgroundColor: UIColor? = nil, itemHighlightedBackgroundColor: UIColor? = nil, itemBlocksSeparatorColor: UIColor? = nil, itemPlainSeparatorColor: UIColor? = nil, disclosureArrowColor: UIColor? = nil, sectionHeaderTextColor: UIColor? = nil, freeTextColor: UIColor? = nil, freeTextErrorColor: UIColor? = nil, freeTextSuccessColor: UIColor? = nil, freeMonoIconColor: UIColor? = nil, itemSwitchColors: PresentationThemeSwitch? = nil, itemDisclosureActions: PresentationThemeItemDisclosureActions? = nil, itemCheckColors: PresentationThemeFillStrokeForeground? = nil, controlSecondaryColor: UIColor? = nil, freeInputField: PresentationInputFieldTheme? = nil, freePlainInputField: PresentationInputFieldTheme? = nil, mediaPlaceholderColor: UIColor? = nil, scrollIndicatorColor: UIColor? = nil, pageIndicatorInactiveColor: UIColor? = nil, inputClearButtonColor: UIColor? = nil, itemBarChart: PresentationThemeItemBarChart? = nil, itemInputField: PresentationInputFieldTheme? = nil) -> PresentationThemeList { - return PresentationThemeList(blocksBackgroundColor: blocksBackgroundColor ?? self.blocksBackgroundColor, plainBackgroundColor: plainBackgroundColor ?? self.plainBackgroundColor, itemPrimaryTextColor: itemPrimaryTextColor ?? self.itemPrimaryTextColor, itemSecondaryTextColor: itemSecondaryTextColor ?? self.itemSecondaryTextColor, itemDisabledTextColor: itemDisabledTextColor ?? self.itemDisabledTextColor, itemAccentColor: itemAccentColor ?? self.itemAccentColor, itemHighlightedColor: itemHighlightedColor ?? self.itemHighlightedColor, itemDestructiveColor: itemDestructiveColor ?? self.itemDestructiveColor, itemPlaceholderTextColor: itemPlaceholderTextColor ?? self.itemPlaceholderTextColor, itemBlocksBackgroundColor: itemBlocksBackgroundColor ?? self.itemBlocksBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, itemBlocksSeparatorColor: itemBlocksSeparatorColor ?? self.itemBlocksSeparatorColor, itemPlainSeparatorColor: itemPlainSeparatorColor ?? self.itemPlainSeparatorColor, disclosureArrowColor: disclosureArrowColor ?? self.disclosureArrowColor, sectionHeaderTextColor: sectionHeaderTextColor ?? self.sectionHeaderTextColor, freeTextColor: freeTextColor ?? self.freeTextColor, freeTextErrorColor: freeTextErrorColor ?? self.freeTextErrorColor, freeTextSuccessColor: freeTextSuccessColor ?? self.freeTextSuccessColor, freeMonoIconColor: freeMonoIconColor ?? self.freeMonoIconColor, itemSwitchColors: itemSwitchColors ?? self.itemSwitchColors, itemDisclosureActions: itemDisclosureActions ?? self.itemDisclosureActions, itemCheckColors: itemCheckColors ?? self.itemCheckColors, controlSecondaryColor: controlSecondaryColor ?? self.controlSecondaryColor, freeInputField: freeInputField ?? self.freeInputField, freePlainInputField: freePlainInputField ?? self.freePlainInputField, mediaPlaceholderColor: mediaPlaceholderColor ?? self.mediaPlaceholderColor, scrollIndicatorColor: scrollIndicatorColor ?? self.scrollIndicatorColor, pageIndicatorInactiveColor: pageIndicatorInactiveColor ?? self.pageIndicatorInactiveColor, inputClearButtonColor: inputClearButtonColor ?? self.inputClearButtonColor, itemBarChart: itemBarChart ?? self.itemBarChart, itemInputField: itemInputField ?? self.itemInputField) + public func withUpdated(blocksBackgroundColor: UIColor? = nil, plainBackgroundColor: UIColor? = nil, itemPrimaryTextColor: UIColor? = nil, itemSecondaryTextColor: UIColor? = nil, itemDisabledTextColor: UIColor? = nil, itemAccentColor: UIColor? = nil, itemHighlightedColor: UIColor? = nil, itemDestructiveColor: UIColor? = nil, itemPlaceholderTextColor: UIColor? = nil, itemBlocksBackgroundColor: UIColor? = nil, itemHighlightedBackgroundColor: UIColor? = nil, itemBlocksSeparatorColor: UIColor? = nil, itemPlainSeparatorColor: UIColor? = nil, disclosureArrowColor: UIColor? = nil, sectionHeaderTextColor: UIColor? = nil, freeTextColor: UIColor? = nil, freeTextErrorColor: UIColor? = nil, freeTextSuccessColor: UIColor? = nil, freeMonoIconColor: UIColor? = nil, itemSwitchColors: PresentationThemeSwitch? = nil, itemDisclosureActions: PresentationThemeItemDisclosureActions? = nil, itemCheckColors: PresentationThemeFillStrokeForeground? = nil, controlSecondaryColor: UIColor? = nil, freeInputField: PresentationInputFieldTheme? = nil, freePlainInputField: PresentationInputFieldTheme? = nil, mediaPlaceholderColor: UIColor? = nil, scrollIndicatorColor: UIColor? = nil, pageIndicatorInactiveColor: UIColor? = nil, inputClearButtonColor: UIColor? = nil, itemBarChart: PresentationThemeItemBarChart? = nil, itemInputField: PresentationInputFieldTheme? = nil, paymentOption: PaymentOption? = nil) -> PresentationThemeList { + return PresentationThemeList(blocksBackgroundColor: blocksBackgroundColor ?? self.blocksBackgroundColor, plainBackgroundColor: plainBackgroundColor ?? self.plainBackgroundColor, itemPrimaryTextColor: itemPrimaryTextColor ?? self.itemPrimaryTextColor, itemSecondaryTextColor: itemSecondaryTextColor ?? self.itemSecondaryTextColor, itemDisabledTextColor: itemDisabledTextColor ?? self.itemDisabledTextColor, itemAccentColor: itemAccentColor ?? self.itemAccentColor, itemHighlightedColor: itemHighlightedColor ?? self.itemHighlightedColor, itemDestructiveColor: itemDestructiveColor ?? self.itemDestructiveColor, itemPlaceholderTextColor: itemPlaceholderTextColor ?? self.itemPlaceholderTextColor, itemBlocksBackgroundColor: itemBlocksBackgroundColor ?? self.itemBlocksBackgroundColor, itemHighlightedBackgroundColor: itemHighlightedBackgroundColor ?? self.itemHighlightedBackgroundColor, itemBlocksSeparatorColor: itemBlocksSeparatorColor ?? self.itemBlocksSeparatorColor, itemPlainSeparatorColor: itemPlainSeparatorColor ?? self.itemPlainSeparatorColor, disclosureArrowColor: disclosureArrowColor ?? self.disclosureArrowColor, sectionHeaderTextColor: sectionHeaderTextColor ?? self.sectionHeaderTextColor, freeTextColor: freeTextColor ?? self.freeTextColor, freeTextErrorColor: freeTextErrorColor ?? self.freeTextErrorColor, freeTextSuccessColor: freeTextSuccessColor ?? self.freeTextSuccessColor, freeMonoIconColor: freeMonoIconColor ?? self.freeMonoIconColor, itemSwitchColors: itemSwitchColors ?? self.itemSwitchColors, itemDisclosureActions: itemDisclosureActions ?? self.itemDisclosureActions, itemCheckColors: itemCheckColors ?? self.itemCheckColors, controlSecondaryColor: controlSecondaryColor ?? self.controlSecondaryColor, freeInputField: freeInputField ?? self.freeInputField, freePlainInputField: freePlainInputField ?? self.freePlainInputField, mediaPlaceholderColor: mediaPlaceholderColor ?? self.mediaPlaceholderColor, scrollIndicatorColor: scrollIndicatorColor ?? self.scrollIndicatorColor, pageIndicatorInactiveColor: pageIndicatorInactiveColor ?? self.pageIndicatorInactiveColor, inputClearButtonColor: inputClearButtonColor ?? self.inputClearButtonColor, itemBarChart: itemBarChart ?? self.itemBarChart, itemInputField: itemInputField ?? self.itemInputField, paymentOption: paymentOption ?? self.paymentOption) } } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift index 850e04a645..ca6049a1ff 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeCodable.swift @@ -745,6 +745,33 @@ extension PresentationInputFieldTheme: Codable { } } +extension PresentationThemeList.PaymentOption: Codable { + enum CodingKeys: String, CodingKey { + case inactiveFill + case inactiveForeground + case activeFill + case activeForeground + } + + public convenience init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + self.init( + inactiveFillColor: try decodeColor(values, .inactiveFill), + inactiveForegroundColor: try decodeColor(values, .inactiveForeground), + activeFillColor: try decodeColor(values, .activeFill), + activeForegroundColor: try decodeColor(values, .activeForeground) + ) + } + + public func encode(to encoder: Encoder) throws { + var values = encoder.container(keyedBy: CodingKeys.self) + try encodeColor(&values, self.activeFillColor, .inactiveFill) + try encodeColor(&values, self.activeForegroundColor, .inactiveForeground) + try encodeColor(&values, self.activeFillColor, .activeFill) + try encodeColor(&values, self.activeForegroundColor, .activeForeground) + } +} + extension PresentationThemeList: Codable { enum CodingKeys: String, CodingKey { case blocksBg @@ -778,6 +805,7 @@ extension PresentationThemeList: Codable { case inputClearButton case itemBarChart case itemInputField + case paymentOption } public convenience init(from decoder: Decoder) throws { @@ -789,6 +817,8 @@ extension PresentationThemeList: Codable { } else { freePlainInputField = try values.decode(PresentationInputFieldTheme.self, forKey: .freeInputField) } + + let freeTextSuccessColor = try decodeColor(values, .freeTextSuccess) self.init( blocksBackgroundColor: try decodeColor(values, .blocksBg), @@ -808,7 +838,7 @@ extension PresentationThemeList: Codable { sectionHeaderTextColor: try decodeColor(values, .sectionHeaderText), freeTextColor: try decodeColor(values, .freeText), freeTextErrorColor: try decodeColor(values, .freeTextError), - freeTextSuccessColor: try decodeColor(values, .freeTextSuccess), + freeTextSuccessColor: freeTextSuccessColor, freeMonoIconColor: try decodeColor(values, .freeMonoIcon), itemSwitchColors: try values.decode(PresentationThemeSwitch.self, forKey: .switch), itemDisclosureActions: try values.decode(PresentationThemeItemDisclosureActions.self, forKey: .disclosureActions), @@ -821,7 +851,13 @@ extension PresentationThemeList: Codable { pageIndicatorInactiveColor: try decodeColor(values, .pageIndicatorInactive), inputClearButtonColor: try decodeColor(values, .inputClearButton), itemBarChart: try values.decode(PresentationThemeItemBarChart.self, forKey: .itemBarChart), - itemInputField: try values.decode(PresentationInputFieldTheme.self, forKey: .itemInputField) + itemInputField: try values.decode(PresentationInputFieldTheme.self, forKey: .itemInputField), + paymentOption: (try? values.decode(PresentationThemeList.PaymentOption.self, forKey: .paymentOption)) ?? PresentationThemeList.PaymentOption( + inactiveFillColor: freeTextSuccessColor.withMultipliedAlpha(0.3), + inactiveForegroundColor: freeTextSuccessColor, + activeFillColor: freeTextSuccessColor, + activeForegroundColor: UIColor(rgb: 0xffffff) + ) ) } diff --git a/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift b/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift index ac1c6729b3..632299a592 100644 --- a/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift +++ b/submodules/TelegramStringFormatting/Sources/CurrencyFormat.swift @@ -165,7 +165,7 @@ public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String { } } -public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (String, String) { +public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (String, String, Bool) { if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] { var result = "" if amount < 0 { @@ -198,8 +198,8 @@ public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (St result.append(entry.symbol) }*/ - return (result, entry.symbol) + return (result, entry.symbol, entry.symbolOnLeft) } else { - return ("", "") + return ("", "", false) } } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 87d08d9814..b1cbaba978 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -815,14 +815,14 @@ final class SharedApplicationContext { } }) - self.mainWindow.debugAction = { + /*self.mainWindow.debugAction = { self.mainWindow.debugAction = nil let presentationData = sharedContext.currentPresentationData.with { $0 } let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme)) navigationController.viewControllers = [debugController(sharedContext: sharedContext, context: nil)] self.mainWindow.present(navigationController, on: .root) - } + }*/ presentationDataPromise.set(sharedContext.presentationData) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index cc7cb5115d..4f9dbc49d0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -627,12 +627,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.presentAutoremoveSetup() } case .paymentSent: - for attribute in message.attributes { + strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + /*for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { - strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId)) + //strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId)) break } - } + }*/ return true default: break @@ -1870,7 +1871,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let invoice = media as? TelegramMediaInvoice { strongSelf.chatDisplayNode.dismissInput() if let receiptMessageId = invoice.receiptMessageId { - strongSelf.present(BotReceiptController(context: strongSelf.context, invoice: invoice, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } else { strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, messageId: messageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index 67f5880c87..c06c89b74b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -926,7 +926,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate) - videoNode.frame = imageFrame + videoNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size) if strongSelf.visibility { if !videoNode.canAttachContent { diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index d79184a23d..1ae9c83d4c 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -16,6 +16,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { public var enableVoipTcp: Bool public var demoVideoChats: Bool public var experimentalCompatibility: Bool + public var enableNoiseSuppression: Bool public static var defaultSettings: ExperimentalUISettings { return ExperimentalUISettings( @@ -31,7 +32,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { disableVideoAspectScaling: false, enableVoipTcp: false, demoVideoChats: false, - experimentalCompatibility: false + experimentalCompatibility: false, + enableNoiseSuppression: false ) } @@ -48,7 +50,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { disableVideoAspectScaling: Bool, enableVoipTcp: Bool, demoVideoChats: Bool, - experimentalCompatibility: Bool + experimentalCompatibility: Bool, + enableNoiseSuppression: Bool ) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory @@ -63,6 +66,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { self.enableVoipTcp = enableVoipTcp self.demoVideoChats = demoVideoChats self.experimentalCompatibility = experimentalCompatibility + self.enableNoiseSuppression = enableNoiseSuppression } public init(decoder: PostboxDecoder) { @@ -79,6 +83,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0 self.demoVideoChats = decoder.decodeInt32ForKey("demoVideoChats", orElse: 0) != 0 self.experimentalCompatibility = decoder.decodeInt32ForKey("experimentalCompatibility", orElse: 0) != 0 + self.enableNoiseSuppression = decoder.decodeInt32ForKey("enableNoiseSuppression", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { @@ -97,6 +102,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp") encoder.encodeInt32(self.demoVideoChats ? 1 : 0, forKey: "demoVideoChats") encoder.encodeInt32(self.experimentalCompatibility ? 1 : 0, forKey: "experimentalCompatibility") + encoder.encodeInt32(self.enableNoiseSuppression ? 1 : 0, forKey: "enableNoiseSuppression") } public func isEqual(to: PreferencesEntry) -> Bool { diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index df588262fc..8beb545dc0 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -180,7 +180,7 @@ public final class OngoingGroupCallContext { private var broadcastPartsSource: BroadcastPartSource? - init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool) { + init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool, enableNoiseSuppression: Bool) { self.queue = queue var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)? @@ -224,7 +224,7 @@ public final class OngoingGroupCallContext { }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit ?? 32, enableVideo: enableVideo, - enableNoiseSuppression: true + enableNoiseSuppression: enableNoiseSuppression ) let queue = self.queue @@ -529,10 +529,10 @@ public final class OngoingGroupCallContext { } } - public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool) { + public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, participantDescriptionsRequired: @escaping (Set) -> Void, audioStreamData: AudioStreamData?, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, enableVideo: Bool, enableNoiseSuppression: Bool) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, participantDescriptionsRequired: participantDescriptionsRequired, audioStreamData: audioStreamData, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: enableVideo) + return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, participantDescriptionsRequired: participantDescriptionsRequired, audioStreamData: audioStreamData, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, enableVideo: enableVideo, enableNoiseSuppression: enableNoiseSuppression) }) } From 5a31f90acf21cce2623048db7d6b1bdaedbf977f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 9 Apr 2021 02:07:57 +0400 Subject: [PATCH 13/15] Support native provider --- .../Sources/BotCheckoutControllerNode.swift | 70 ++++- ...BotCheckoutNativeCardEntryController.swift | 15 +- ...heckoutNativeCardEntryControllerNode.swift | 270 ++++++++++++------ 3 files changed, 266 insertions(+), 89 deletions(-) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 67ec612242..769e802cf8 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -561,7 +561,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz var dismissImpl: (() -> Void)? let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing - let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, additionalFields: additionalFields, publishableKey: publishableKey, completion: { method in + let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, provider: .stripe(additionalFields: additionalFields, publishableKey: publishableKey), completion: { method in guard let strongSelf = self else { return } @@ -616,6 +616,74 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz controller?.dismiss() } strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } else if let nativeProvider = paymentForm.nativeProvider, nativeProvider.name == "smartglocal" { + guard let paramsData = nativeProvider.params.data(using: .utf8) else { + return + } + guard let nativeParams = (try? JSONSerialization.jsonObject(with: paramsData)) as? [String: Any] else { + return + } + guard let publicToken = nativeParams["public_token"] as? String else { + return + } + + var dismissImpl: (() -> Void)? + let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing + let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, provider: .smartglobal(isTesting: paymentForm.invoice.isTest, publicToken: publicToken), completion: { method in + guard let strongSelf = self else { + return + } + if canSave && paymentForm.passwordMissing { + switch method { + case let .webToken(webToken) where webToken.saveOnServer: + var text = strongSelf.presentationData.strings.Checkout_NewCard_SaveInfoEnableHelp + text = text.replacingOccurrences(of: "[", with: "") + text = text.replacingOccurrences(of: "]", with: "") + present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_NotNow, action: { + var updatedToken = webToken + updatedToken.saveOnServer = false + applyPaymentMethod(.webToken(updatedToken)) + }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: { + guard let strongSelf = self else { + return + } + if paymentForm.passwordMissing { + var updatedToken = webToken + updatedToken.saveOnServer = false + applyPaymentMethod(.webToken(updatedToken)) + + let controller = SetupTwoStepVerificationController(context: strongSelf.context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in + if shouldDismiss { + controller.dismiss() + } + switch update { + case .noPassword, .awaitingEmailConfirmation: + break + case .passwordSet: + var updatedToken = webToken + updatedToken.saveOnServer = true + applyPaymentMethod(.webToken(updatedToken)) + } + }) + strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } else { + var updatedToken = webToken + updatedToken.saveOnServer = true + applyPaymentMethod(.webToken(updatedToken)) + } + })]), nil) + default: + applyPaymentMethod(method) + } + } else { + applyPaymentMethod(method) + } + dismissImpl?() + }) + dismissImpl = { [weak controller] in + controller?.dismiss() + } + strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } else { var dismissImpl: (() -> Void)? let controller = BotCheckoutWebInteractionController(context: context, url: paymentForm.url, intent: .addPaymentMethod({ [weak self] token in diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift index c0449e73e2..7ddb525ce3 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift @@ -30,13 +30,17 @@ struct BotCheckoutNativeCardEntryAdditionalFields: OptionSet { } final class BotCheckoutNativeCardEntryController: ViewController { + enum Provider { + case stripe(additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String) + case smartglobal(isTesting: Bool, publicToken: String) + } + private var controllerNode: BotCheckoutNativeCardEntryControllerNode { return super.displayNode as! BotCheckoutNativeCardEntryControllerNode } private let context: AccountContext - private let additionalFields: BotCheckoutNativeCardEntryAdditionalFields - private let publishableKey: String + private let provider: Provider private let completion: (BotCheckoutPaymentMethod) -> Void private var presentationData: PresentationData @@ -46,10 +50,9 @@ final class BotCheckoutNativeCardEntryController: ViewController { private var doneItem: UIBarButtonItem? private var activityItem: UIBarButtonItem? - public init(context: AccountContext, additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String, completion: @escaping (BotCheckoutPaymentMethod) -> Void) { + public init(context: AccountContext, provider: Provider, completion: @escaping (BotCheckoutPaymentMethod) -> Void) { self.context = context - self.additionalFields = additionalFields - self.publishableKey = publishableKey + self.provider = provider self.completion = completion self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -71,7 +74,7 @@ final class BotCheckoutNativeCardEntryController: ViewController { } override public func loadDisplayNode() { - self.displayNode = BotCheckoutNativeCardEntryControllerNode(additionalFields: self.additionalFields, publishableKey: self.publishableKey, theme: self.presentationData.theme, strings: self.presentationData.strings, present: { [weak self] c, a in + self.displayNode = BotCheckoutNativeCardEntryControllerNode(provider: self.provider, theme: self.presentationData.theme, strings: self.presentationData.strings, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, dismiss: { [weak self] in self?.presentingViewController?.dismiss(animated: false, completion: nil) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift index d0b2b3e9d5..bbf511a6e5 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift @@ -42,7 +42,7 @@ private final class BotCheckoutNativeCardEntryScrollerNode: ASDisplayNode { } final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { - private let publishableKey: String + private let provider: BotCheckoutNativeCardEntryController.Provider private let present: (ViewController, Any?) -> Void private let dismiss: () -> Void @@ -70,9 +70,11 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, private var currentCardData: BotPaymentCardInputData? private var currentCountryIso2: String? + + private var dataTask: URLSessionDataTask? - init(additionalFields: BotCheckoutNativeCardEntryAdditionalFields, publishableKey: String, theme: PresentationTheme, strings: PresentationStrings, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, openCountrySelection: @escaping () -> Void, updateStatus: @escaping (BotCheckoutNativeCardEntryStatus) -> Void, completion: @escaping (BotCheckoutPaymentMethod) -> Void) { - self.publishableKey = publishableKey + init(provider: BotCheckoutNativeCardEntryController.Provider, theme: PresentationTheme, strings: PresentationStrings, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, openCountrySelection: @escaping () -> Void, updateStatus: @escaping (BotCheckoutNativeCardEntryStatus) -> Void, completion: @escaping (BotCheckoutPaymentMethod) -> Void) { + self.provider = provider self.present = present self.dismiss = dismiss @@ -95,46 +97,53 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, cardUpdatedImpl?(data) } itemNodes.append([BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_PaymentCard), self.cardItem]) - - if additionalFields.contains(.cardholderName) { - var sectionItems: [BotPaymentItemNode] = [] - - sectionItems.append(BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_CardholderNameTitle)) - - let cardholderItem = BotPaymentFieldItemNode(title: "", placeholder: strings.Checkout_NewCard_CardholderNamePlaceholder, contentType: .name) - self.cardholderItem = cardholderItem - sectionItems.append(cardholderItem) - - itemNodes.append(sectionItems) - } else { - self.cardholderItem = nil - } - - if additionalFields.contains(.country) || additionalFields.contains(.zipCode) { - var sectionItems: [BotPaymentItemNode] = [] - - sectionItems.append(BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_PostcodeTitle)) - - if additionalFields.contains(.country) { - let countryItem = BotPaymentDisclosureItemNode(title: "", placeholder: strings.CheckoutInfo_ShippingInfoCountryPlaceholder, text: "") - countryItem.action = { - openCountrySelectionImpl?() + + switch provider { + case let .stripe(additionalFields, _): + if additionalFields.contains(.cardholderName) { + var sectionItems: [BotPaymentItemNode] = [] + + sectionItems.append(BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_CardholderNameTitle)) + + let cardholderItem = BotPaymentFieldItemNode(title: "", placeholder: strings.Checkout_NewCard_CardholderNamePlaceholder, contentType: .name) + self.cardholderItem = cardholderItem + sectionItems.append(cardholderItem) + + itemNodes.append(sectionItems) + } else { + self.cardholderItem = nil + } + + if additionalFields.contains(.country) || additionalFields.contains(.zipCode) { + var sectionItems: [BotPaymentItemNode] = [] + + sectionItems.append(BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_PostcodeTitle)) + + if additionalFields.contains(.country) { + let countryItem = BotPaymentDisclosureItemNode(title: "", placeholder: strings.CheckoutInfo_ShippingInfoCountryPlaceholder, text: "") + countryItem.action = { + openCountrySelectionImpl?() + } + self.countryItem = countryItem + sectionItems.append(countryItem) + } else { + self.countryItem = nil } - self.countryItem = countryItem - sectionItems.append(countryItem) + if additionalFields.contains(.zipCode) { + let zipCodeItem = BotPaymentFieldItemNode(title: "", placeholder: strings.Checkout_NewCard_PostcodePlaceholder, contentType: .address) + self.zipCodeItem = zipCodeItem + sectionItems.append(zipCodeItem) + } else { + self.zipCodeItem = nil + } + + itemNodes.append(sectionItems) } else { self.countryItem = nil - } - if additionalFields.contains(.zipCode) { - let zipCodeItem = BotPaymentFieldItemNode(title: "", placeholder: strings.Checkout_NewCard_PostcodePlaceholder, contentType: .address) - self.zipCodeItem = zipCodeItem - sectionItems.append(zipCodeItem) - } else { self.zipCodeItem = nil } - - itemNodes.append(sectionItems) - } else { + case .smartglobal: + self.cardholderItem = nil self.countryItem = nil self.zipCodeItem = nil } @@ -214,6 +223,7 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, deinit { self.verifyDisposable.dispose() + self.dataTask?.cancel() } func updateCountry(_ iso2: String) { @@ -232,53 +242,149 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, guard let cardData = self.currentCardData else { return } - - let configuration = STPPaymentConfiguration.shared().copy() as! STPPaymentConfiguration - configuration.smsAutofillDisabled = true - configuration.publishableKey = self.publishableKey - configuration.appleMerchantIdentifier = "merchant.ph.telegra.Telegraph" - - let apiClient = STPAPIClient(configuration: configuration) - - let card = STPCardParams() - card.number = cardData.number - card.cvc = cardData.code - card.expYear = cardData.year - card.expMonth = cardData.month - card.name = self.cardholderItem?.text - card.addressCountry = self.currentCountryIso2 - card.addressZip = self.zipCodeItem?.text - - let createToken: Signal = Signal { subscriber in - apiClient.createToken(withCard: card, completion: { token, error in - if let error = error { - subscriber.putError(error) - } else if let token = token { - subscriber.putNext(token) - subscriber.putCompletion() + + switch self.provider { + case let .stripe(_, publishableKey): + let configuration = STPPaymentConfiguration.shared().copy() as! STPPaymentConfiguration + configuration.smsAutofillDisabled = true + configuration.publishableKey = publishableKey + configuration.appleMerchantIdentifier = "merchant.ph.telegra.Telegraph" + + let apiClient = STPAPIClient(configuration: configuration) + + let card = STPCardParams() + card.number = cardData.number + card.cvc = cardData.code + card.expYear = cardData.year + card.expMonth = cardData.month + card.name = self.cardholderItem?.text + card.addressCountry = self.currentCountryIso2 + card.addressZip = self.zipCodeItem?.text + + let createToken: Signal = Signal { subscriber in + apiClient.createToken(withCard: card, completion: { token, error in + if let error = error { + subscriber.putError(error) + } else if let token = token { + subscriber.putNext(token) + subscriber.putCompletion() + } + }) + + return ActionDisposable { + let _ = apiClient.publishableKey + } + } + + self.isVerifying = true + self.verifyDisposable.set((createToken |> deliverOnMainQueue).start(next: { [weak self] token in + if let strongSelf = self, let card = token.card { + let last4 = card.last4() + let brand = STPAPIClient.string(with: card.brand) + strongSelf.completion(.webToken(BotCheckoutPaymentWebToken(title: "\(brand)*\(last4)", data: "{\"type\": \"card\", \"id\": \"\(token.tokenId)\"}", saveOnServer: strongSelf.saveInfoItem.isOn))) + } + }, error: { [weak self] error in + if let strongSelf = self { + strongSelf.isVerifying = false + strongSelf.updateDone() + } + })) + + self.updateDone() + case let .smartglobal(isTesting, publicToken): + let url: String + if isTesting { + url = "https://tgb-playground.smart-glocal.com/cds/v1/tokenize/card" + } else { + url = "https://tgb.smart-glocal.com/cds/v1/tokenize/card" + } + + let jsonPayload: [String: Any] = [ + "card": [ + "number": cardData.number, + "expiration_month": "\(cardData.month)", + "expiration_year": "\(cardData.year)", + "security_code": "\(cardData.code)" + ] as [String: Any] + ] + + guard let parsedUrl = URL(string: url) else { + return + } + + var request = URLRequest(url: parsedUrl) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue(publicToken, forHTTPHeaderField: "X-PUBLIC-TOKEN") + guard let requestBody = try? JSONSerialization.data(withJSONObject: jsonPayload, options: []) else { + return + } + request.httpBody = requestBody + + let session = URLSession.shared + let dataTask = session.dataTask(with: request, completionHandler: { [weak self] data, response, error in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + + enum ReponseError: Error { + case generic + } + + do { + guard let data = data else { + throw ReponseError.generic + } + + let jsonRaw = try JSONSerialization.jsonObject(with: data, options: []) + guard let json = jsonRaw as? [String: Any] else { + throw ReponseError.generic + } + guard let resultData = json["data"] as? [String: Any] else { + throw ReponseError.generic + } + guard let resultInfo = resultData["info"] as? [String: Any] else { + throw ReponseError.generic + } + guard let token = resultData["token"] as? String else { + throw ReponseError.generic + } + guard let maskedCardNumber = resultInfo["masked_card_number"] as? String else { + throw ReponseError.generic + } + + let responseJson: [String: Any] = [ + "type": "card", + "id": "\(token)" + ] + + let serializedResponseJson = try JSONSerialization.data(withJSONObject: responseJson, options: []) + + guard let serializedResponseString = String(data: serializedResponseJson, encoding: .utf8) else { + throw ReponseError.generic + } + + strongSelf.completion(.webToken(BotCheckoutPaymentWebToken( + title: maskedCardNumber, + data: serializedResponseString, + saveOnServer: strongSelf.saveInfoItem.isOn + ))) + } catch { + strongSelf.isVerifying = false + strongSelf.updateDone() + } } }) - - return ActionDisposable { - let _ = apiClient.publishableKey - } + self.dataTask = dataTask + + self.isVerifying = true + self.updateDone() + + dataTask.resume() + + break } - - self.isVerifying = true - self.verifyDisposable.set((createToken |> deliverOnMainQueue).start(next: { [weak self] token in - if let strongSelf = self, let card = token.card { - let last4 = card.last4() - let brand = STPAPIClient.string(with: card.brand) - strongSelf.completion(.webToken(BotCheckoutPaymentWebToken(title: "\(brand)*\(last4)", data: "{\"type\": \"card\", \"id\": \"\(token.tokenId)\"}", saveOnServer: strongSelf.saveInfoItem.isOn))) - } - }, error: { [weak self] error in - if let strongSelf = self { - strongSelf.isVerifying = false - strongSelf.updateDone() - } - })) - - self.updateDone() } private func updateDone() { From 976c3f64bf854b8ea4f35f25f63ee18f26f8bd4c Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 9 Apr 2021 02:16:54 +0400 Subject: [PATCH 14/15] Bump version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index 6071caeab1..e51975cf2f 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "7.6.2", + "app": "7.7", "bazel": "4.0.0", "xcode": "12.4" } From 0b9c938e92a9045ba0ef96a3e53bcd56b1b89982 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 9 Apr 2021 02:26:17 +0400 Subject: [PATCH 15/15] Fix build --- submodules/DebugSettingsUI/Sources/DebugController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 6a67bd90c5..bc10d7a500 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -94,7 +94,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference: + case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableNoiseSuppression: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue