diff --git a/Example/App/AppDelegate.swift b/Example/App/AppDelegate.swift index dd6b7f5..dc19b12 100644 --- a/Example/App/AppDelegate.swift +++ b/Example/App/AppDelegate.swift @@ -45,8 +45,20 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { credentialsRefresher: tinkoffId, signOutInitializer: tinkoffId ) - - window?.rootViewController = authController + authController.tabBarItem = UITabBarItem(title: "Auth", image: nil, tag: 0) + + let tinkoffButtonsController = TinkoffButtonsViewController() + tinkoffButtonsController.tabBarItem = UITabBarItem(title: "UI", image: nil, tag: 1) + + let tabBarController = UITabBarController() + + if #available(iOS 13.0, *) { + authController.tabBarItem.image = UIImage(systemName: "person") + tinkoffButtonsController.tabBarItem.image = UIImage(systemName: "pencil") + } + tabBarController.viewControllers = [authController, tinkoffButtonsController] + + window?.rootViewController = tabBarController window?.makeKeyAndVisible() } diff --git a/Example/App/AuthViewController.swift b/Example/App/AuthViewController.swift index 609bab0..8dbe708 100644 --- a/Example/App/AuthViewController.swift +++ b/Example/App/AuthViewController.swift @@ -28,7 +28,10 @@ final class AuthViewController: UIViewController { // MARK: - UI lazy var credentialsLabel = UILabel() - lazy var signInButton = TinkoffIDButtonBuilder.build(.default) + lazy var signInButton = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(size: .large), + title: "Войти с " + ) lazy var signOutButton = UIButton() lazy var refreshButton = UIButton() diff --git a/Example/App/TinkoffButtonsViewController.swift b/Example/App/TinkoffButtonsViewController.swift new file mode 100644 index 0000000..e28d678 --- /dev/null +++ b/Example/App/TinkoffButtonsViewController.swift @@ -0,0 +1,131 @@ +// +// UIViewController.swift +// TinkoffIDExample +// +// Created by Margarita Shishkina on 14.07.2022. +// Copyright © 2022 Tinkoff. All rights reserved. +// + +import UIKit +import TinkoffID + +class TinkoffButtonsViewController: UIViewController { + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.alignment = .leading + stackView.axis = .vertical + stackView.distribution = .fill + stackView.spacing = 4 + return stackView + }() + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .white + + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(scrollView) + NSLayoutConstraint.activate([ + scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), + scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8), + scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8), + scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16) + ]) + + scrollView.addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor), + stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor), + stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor) + ]) + + setupButtons() + } + + private func setupButtons() { + let buttonSmallDark = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(colorStyle: .alternativeDark, size: .small) + ) + stackView.addArrangedSubview(createLabel(with: "Small, alternativeDark")) + stackView.addArrangedSubview(buttonSmallDark) + stackView.setCustomSpacing(15, after: buttonSmallDark) + + let buttonSmall = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(size: .small), + title: "Войти с " + ) + stackView.addArrangedSubview(createLabel(with: "Small, custom title")) + stackView.addArrangedSubview(buttonSmall) + stackView.setCustomSpacing(15, after: buttonSmall) + + let buttonMedium = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(colorStyle: .alternativeLight) + ) + stackView.addArrangedSubview(createLabel(with: "Medium, alternativeLight")) + stackView.addArrangedSubview(buttonMedium) + stackView.setCustomSpacing(15, after: buttonMedium) + + let buttonLarge = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration( + colorStyle: .alternativeDark, + size: .large, + font: UIFont(name: "Noteworthy", size: 15)! + ), + title: "Войти с " + ) + stackView.addArrangedSubview(createLabel(with: "Large, alternativeDark, custom font")) + stackView.addArrangedSubview(buttonLarge) + stackView.setCustomSpacing(15, after: buttonLarge) + + let buttonSmallBadge = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(size: .small), + badge: "5% кэшбэк" + ) + stackView.addArrangedSubview(createLabel(with: "Small, badge")) + stackView.addArrangedSubview(buttonSmallBadge) + stackView.setCustomSpacing(15, after: buttonSmallBadge) + + let buttonCustomSizeBadge = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(colorStyle: .alternativeLight, cornerRadius: 20), + badge: "5% кэшбэк" + ) + stackView.addArrangedSubview(createLabel(with: "Custom size, badge, alternativeLight, corner radius 20")) + stackView.addArrangedSubview(buttonCustomSizeBadge) + stackView.setCustomSpacing(15, after: buttonCustomSizeBadge) + buttonCustomSizeBadge.heightAnchor.constraint(equalToConstant: 80).isActive = true + buttonCustomSizeBadge.widthAnchor.constraint(equalTo: stackView.widthAnchor).isActive = true + + let buttonBadge = TinkoffIDButtonBuilder.build( + configuration: TinkoffIDButtonConfiguration(colorStyle: .alternativeDark), + badge: "500000% кэшбэк" + ) + stackView.addArrangedSubview(createLabel(with: "badge, alternativeDark")) + stackView.addArrangedSubview(buttonBadge) + stackView.setCustomSpacing(15, after: buttonBadge) + + let compactButton = TinkoffIDButtonBuilder.buildCompact() + stackView.addArrangedSubview(createLabel(with: "Compact button")) + stackView.addArrangedSubview(compactButton) + stackView.setCustomSpacing(15, after: compactButton) + + let compactButtonLight = TinkoffIDButtonBuilder.buildCompact(colorStyle: .alternativeLight) + stackView.addArrangedSubview(createLabel(with: "Compact button, alternativeLight, custom size")) + stackView.addArrangedSubview(compactButtonLight) + compactButtonLight.heightAnchor.constraint(equalToConstant: 70).isActive = true + compactButtonLight.widthAnchor.constraint(equalToConstant: 70).isActive = true + } + + private func createLabel(with text: String) -> UILabel { + let label = UILabel() + label.textColor = .black + label.font = UIFont.systemFont(ofSize: 14) + label.text = text + return label + } +} diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 0348e4a..79bf1fc 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,7 +1,7 @@ PODS: - SnapKit (5.0.1) - - TinkoffID (0.0.1) - - TinkoffID/Tests (0.0.1) + - TinkoffID (1.1.0) + - TinkoffID/Tests (1.1.0) DEPENDENCIES: - SnapKit @@ -18,7 +18,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb - TinkoffID: ac03552becea632de0f13d9461621770444ae0b8 + TinkoffID: 5f8c16ed1ec6ab3c055d76b162d5770a324ad3db PODFILE CHECKSUM: ebbf3aa204a2c47033aee78dc783acf8bc5423bd diff --git a/Example/TinkoffIDExample.xcodeproj/project.pbxproj b/Example/TinkoffIDExample.xcodeproj/project.pbxproj index b88f963..44dc21d 100644 --- a/Example/TinkoffIDExample.xcodeproj/project.pbxproj +++ b/Example/TinkoffIDExample.xcodeproj/project.pbxproj @@ -12,10 +12,13 @@ 0A65BBBC2590E8F000F613D5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A65BBB92590E8F000F613D5 /* LaunchScreen.storyboard */; }; 0A65BBBD2590E8F000F613D5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A65BBBA2590E8F000F613D5 /* Images.xcassets */; }; 0ABFC7892590E86000168951 /* TinkoffID.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 0ABFC7882590E85F00168951 /* TinkoffID.podspec */; }; - 51600987FEAC3FA656A81460 /* Pods_TinkoffIDExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2A6E6CE33587FF27858033 /* Pods_TinkoffIDExample.framework */; }; + 40741E50BFF4B766A19FC37A /* Pods_TinkoffIDExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 035CA265801E31090C779B3B /* Pods_TinkoffIDExample.framework */; }; + B8B1F7102880026C008EAB8E /* TinkoffButtonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B1F70F2880026C008EAB8E /* TinkoffButtonsViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 02D6235BCEC7A72B88084B7A /* Pods-TinkoffIDExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TinkoffIDExample.release.xcconfig"; path = "Target Support Files/Pods-TinkoffIDExample/Pods-TinkoffIDExample.release.xcconfig"; sourceTree = ""; }; + 035CA265801E31090C779B3B /* Pods_TinkoffIDExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TinkoffIDExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0A65BBAE2590E8D300F613D5 /* AuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 0A65BBB12590E8D300F613D5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 0A65BBB92590E8F000F613D5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; @@ -23,13 +26,10 @@ 0A65BBBB2590E8F000F613D5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0ABFC7882590E85F00168951 /* TinkoffID.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = TinkoffID.podspec; path = ../TinkoffID.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 25F4E23B300426AEB76132F3 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - 44F9956D4542D8782CD4C3D8 /* Pods-TinkoffIDExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TinkoffIDExample.release.xcconfig"; path = "Target Support Files/Pods-TinkoffIDExample/Pods-TinkoffIDExample.release.xcconfig"; sourceTree = ""; }; + 5C61E8899896A5A3B2571A79 /* Pods-TinkoffIDExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TinkoffIDExample.debug.xcconfig"; path = "Target Support Files/Pods-TinkoffIDExample/Pods-TinkoffIDExample.debug.xcconfig"; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* TinkoffIDExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TinkoffIDExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 7F2A6E6CE33587FF27858033 /* Pods_TinkoffIDExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TinkoffIDExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 86F49E1AE66946E513814F6E /* Pods-TinkoffIDExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TinkoffIDExample.debug.xcconfig"; path = "Target Support Files/Pods-TinkoffIDExample/Pods-TinkoffIDExample.debug.xcconfig"; sourceTree = ""; }; - 93A7498398FBAC5533C5966F /* Pods-TinkoffSignIn_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TinkoffSignIn_Example.debug.xcconfig"; path = "Target Support Files/Pods-TinkoffSignIn_Example/Pods-TinkoffSignIn_Example.debug.xcconfig"; sourceTree = ""; }; + B8B1F70F2880026C008EAB8E /* TinkoffButtonsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TinkoffButtonsViewController.swift; sourceTree = ""; }; BA3AB1A1BD565FCA6D3E2744 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; - E32A0480955749A2CEF75FC4 /* Pods-TinkoffSignIn_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TinkoffSignIn_Example.release.xcconfig"; path = "Target Support Files/Pods-TinkoffSignIn_Example/Pods-TinkoffSignIn_Example.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -37,26 +37,19 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 51600987FEAC3FA656A81460 /* Pods_TinkoffIDExample.framework in Frameworks */, + 40741E50BFF4B766A19FC37A /* Pods_TinkoffIDExample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 05689BF3C94E278BB77E91B9 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 7F2A6E6CE33587FF27858033 /* Pods_TinkoffIDExample.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 0A65BBAD2590E8D300F613D5 /* App */ = { isa = PBXGroup; children = ( 0A65BBB12590E8D300F613D5 /* AppDelegate.swift */, 0A65BBAE2590E8D300F613D5 /* AuthViewController.swift */, + B8B1F70F2880026C008EAB8E /* TinkoffButtonsViewController.swift */, 0A65BBB82590E8F000F613D5 /* Resources */, ); path = App; @@ -72,6 +65,14 @@ path = Resources; sourceTree = ""; }; + 4EAEDD680A06A0C99D9474AF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 035CA265801E31090C779B3B /* Pods_TinkoffIDExample.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( @@ -79,7 +80,7 @@ 0A65BBAD2590E8D300F613D5 /* App */, 607FACD11AFB9204008FA782 /* Products */, D4A376ECE1B32FFCA6022572 /* Pods */, - 05689BF3C94E278BB77E91B9 /* Frameworks */, + 4EAEDD680A06A0C99D9474AF /* Frameworks */, ); sourceTree = ""; }; @@ -104,10 +105,8 @@ D4A376ECE1B32FFCA6022572 /* Pods */ = { isa = PBXGroup; children = ( - 93A7498398FBAC5533C5966F /* Pods-TinkoffSignIn_Example.debug.xcconfig */, - E32A0480955749A2CEF75FC4 /* Pods-TinkoffSignIn_Example.release.xcconfig */, - 86F49E1AE66946E513814F6E /* Pods-TinkoffIDExample.debug.xcconfig */, - 44F9956D4542D8782CD4C3D8 /* Pods-TinkoffIDExample.release.xcconfig */, + 5C61E8899896A5A3B2571A79 /* Pods-TinkoffIDExample.debug.xcconfig */, + 02D6235BCEC7A72B88084B7A /* Pods-TinkoffIDExample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -119,11 +118,11 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "TinkoffIDExample" */; buildPhases = ( - 37CFEEDB18E1B7B1ED595B67 /* [CP] Check Pods Manifest.lock */, + 4AAE7DBEBDBA13C7FC4C7236 /* [CP] Check Pods Manifest.lock */, 607FACCC1AFB9204008FA782 /* Sources */, 607FACCD1AFB9204008FA782 /* Frameworks */, 607FACCE1AFB9204008FA782 /* Resources */, - 7C076F48CF4E01832D45CFB4 /* [CP] Embed Pods Frameworks */, + 82A91C430F311D4F3A9538D0 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -183,7 +182,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 37CFEEDB18E1B7B1ED595B67 /* [CP] Check Pods Manifest.lock */ = { + 4AAE7DBEBDBA13C7FC4C7236 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -205,7 +204,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 7C076F48CF4E01832D45CFB4 /* [CP] Embed Pods Frameworks */ = { + 82A91C430F311D4F3A9538D0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -231,6 +230,7 @@ files = ( 0A65BBB62590E8D300F613D5 /* AppDelegate.swift in Sources */, 0A65BBB32590E8D300F613D5 /* AuthViewController.swift in Sources */, + B8B1F7102880026C008EAB8E /* TinkoffButtonsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -345,7 +345,7 @@ }; 607FACF01AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 86F49E1AE66946E513814F6E /* Pods-TinkoffIDExample.debug.xcconfig */; + baseConfigurationReference = 5C61E8899896A5A3B2571A79 /* Pods-TinkoffIDExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; @@ -370,7 +370,7 @@ }; 607FACF11AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 44F9956D4542D8782CD4C3D8 /* Pods-TinkoffIDExample.release.xcconfig */; + baseConfigurationReference = 02D6235BCEC7A72B88084B7A /* Pods-TinkoffIDExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; diff --git a/README.md b/README.md index c1602d1..d45d75c 100755 --- a/README.md +++ b/README.md @@ -198,15 +198,15 @@ tinkoffId.signOut(with: credentials.accessToken, tokenTypeHint: .access, complet При получении `TinkoffTokenPayload` и наличии у него поля `refreshToken` имеет смысл сохранить значение этого поля чтобы иметь возможность запросить новый `accessToken`, когда прежний станет неактивным. Рекомендуемый способ хранения токена - [Keychain Services](https://developer.apple.com/documentation/security/keychain_services) ## UI -SDK поставляет фирменную кнопку входа через Тинькофф. Кнопка представлена в двух стилях: `.default` (высотой 56 точек) и `.compact` (40 точек). -Для получения экземпляра кнопки необходимо использовать статический метод `build` класса `TinkoffIDButtonBuilder`: - +SDK поставляет два варианта фирменных кнопок входа через Тинькофф. +Первый вариант - стандартная прямоугольная кнопка с текстом, с возможностью задать текст, радиус скругления и шрифт. Так же можно выбрать один из трех вариантов цветового стиля и размера. Есть возможность добавить дополнительный текст для привлечения клиентов. +Второй вариант - компактная кнопка без текста, так же можно выбрать один из трех цветовых стилей. ```swift override func viewDidLoad() { super.viewDidLoad() - // Создание кнопки входа - let button = TinkoffIDButtonBuilder.build(.default) + // Создание стандартной кнопки + let button = TinkoffIDButtonBuilder.build() // Добавление обработчика нажатия button.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside) @@ -230,7 +230,7 @@ override func viewDidLoad() { Обратите внимание: после получения кнопки необходимо расположить её на экране, а также добавить обработчик события нажатия. Для верстки рекомендуется использовать `AutoLayout` без указания высоты так как она задается с помощью `intrinsicContentSize`. -Более подробно ознакомиться с правилами размещения кнопки Вы можете [здесь](https://www.figma.com/file/TsgXOeAqFEePVIosk0W7kP/Tinkoff-ID). +Более подробно ознакомиться с правилами размещения кнопки Вы можете [здесь](https://www.figma.com/file/Yj3o7yQotahvBxfIKhBmJc/Tinkoff-ID-guide). ## Отладка без приложения Тинькофф В случаях когда необходима отладка интеграции `Tinkoff ID` без приложения Тинькофф или на симуляторе iOS, SDK предоставляет реализацию TinkoffID для отладки. diff --git a/Sources/Resources/TinkoffID.xcassets/idLogoL.imageset/Contents.json b/Sources/Resources/TinkoffID.xcassets/idLogoL.imageset/Contents.json new file mode 100644 index 0000000..1f2cd8f --- /dev/null +++ b/Sources/Resources/TinkoffID.xcassets/idLogoL.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Group 1096.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Resources/TinkoffID.xcassets/idLogoL.imageset/Group 1096.pdf b/Sources/Resources/TinkoffID.xcassets/idLogoL.imageset/Group 1096.pdf new file mode 100644 index 0000000..b062109 Binary files /dev/null and b/Sources/Resources/TinkoffID.xcassets/idLogoL.imageset/Group 1096.pdf differ diff --git a/Sources/Resources/TinkoffID.xcassets/idLogoM.imageset/Contents.json b/Sources/Resources/TinkoffID.xcassets/idLogoM.imageset/Contents.json new file mode 100644 index 0000000..c7860eb --- /dev/null +++ b/Sources/Resources/TinkoffID.xcassets/idLogoM.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Group 1095.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Resources/TinkoffID.xcassets/idLogoM.imageset/Group 1095.pdf b/Sources/Resources/TinkoffID.xcassets/idLogoM.imageset/Group 1095.pdf new file mode 100644 index 0000000..99d595c Binary files /dev/null and b/Sources/Resources/TinkoffID.xcassets/idLogoM.imageset/Group 1095.pdf differ diff --git a/Sources/Resources/TinkoffID.xcassets/idLogoS.imageset/Contents.json b/Sources/Resources/TinkoffID.xcassets/idLogoS.imageset/Contents.json new file mode 100644 index 0000000..148b2de --- /dev/null +++ b/Sources/Resources/TinkoffID.xcassets/idLogoS.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Group 1094.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Resources/TinkoffID.xcassets/idLogoS.imageset/Group 1094.pdf b/Sources/Resources/TinkoffID.xcassets/idLogoS.imageset/Group 1094.pdf new file mode 100644 index 0000000..1b43d6d Binary files /dev/null and b/Sources/Resources/TinkoffID.xcassets/idLogoS.imageset/Group 1094.pdf differ diff --git a/Sources/Resources/TinkoffID.xcassets/logo.imageset/Contents.json b/Sources/Resources/TinkoffID.xcassets/logo.imageset/Contents.json index 073651b..4f547d0 100644 --- a/Sources/Resources/TinkoffID.xcassets/logo.imageset/Contents.json +++ b/Sources/Resources/TinkoffID.xcassets/logo.imageset/Contents.json @@ -2,20 +2,14 @@ "images" : [ { "filename" : "logo.pdf", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/Sources/Resources/en.lproj/TinkoffID.strings b/Sources/Resources/en.lproj/TinkoffID.strings deleted file mode 100644 index c6701a9..0000000 --- a/Sources/Resources/en.lproj/TinkoffID.strings +++ /dev/null @@ -1 +0,0 @@ -"SignInButton.Title" = "Sign in with Tinkoff"; diff --git a/Sources/Resources/ru.lproj/TinkoffID.strings b/Sources/Resources/ru.lproj/TinkoffID.strings deleted file mode 100644 index 4a96194..0000000 --- a/Sources/Resources/ru.lproj/TinkoffID.strings +++ /dev/null @@ -1 +0,0 @@ -"SignInButton.Title" = "Войти через Тинькофф"; diff --git a/Sources/UI/Button/TinkoffIDButton.swift b/Sources/UI/Button/TinkoffIDButton.swift index 2b5bdec..b7b25c5 100644 --- a/Sources/UI/Button/TinkoffIDButton.swift +++ b/Sources/UI/Button/TinkoffIDButton.swift @@ -19,8 +19,42 @@ import UIKit final class TinkoffIDButton: UIButton { - - private let style: TinkoffIDButtonStyle + + private let font: UIFont + private let title: String + private let badge: String? + private let colorStyle: TinkoffIDButtonColorStyle + private let cornerRadius: CGFloat + + private let defaultTitle = "Tinkoff" + + private var cachedSize: CGSize = .zero + private var size: TinkoffIDButtonSize + + private lazy var badgeLabel: UILabel = { + let label = UILabel() + label.textColor = colorStyle.badgeTextColor + label.text = badge + label.font = font.withSize(size.badgeFontSize) + label.sizeToFit() + label.textAlignment = .center + return label + }() + + private lazy var badgeInfoField: UIView = { + let view = UIView() + view.isUserInteractionEnabled = false + view.backgroundColor = colorStyle.badgeFieldColor + return view + }() + + private lazy var imageBorder: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.layer.borderWidth = size.borderWidth + view.layer.borderColor = UIColor.white.cgColor + return view + }() override var isHighlighted: Bool { didSet { @@ -28,8 +62,13 @@ final class TinkoffIDButton: UIButton { } } - init(_ style: TinkoffIDButtonStyle) { - self.style = style + init(configuration: TinkoffIDButtonConfiguration, title: String? = nil, badge: String? = nil) { + self.size = configuration.size + self.colorStyle = configuration.colorStyle + self.cornerRadius = configuration.cornerRadius + self.font = configuration.font + self.badge = badge + self.title = title ?? "" super.init(frame: .zero) @@ -37,18 +76,117 @@ final class TinkoffIDButton: UIButton { } required init?(coder: NSCoder) { - self.style = .default + self.size = .medium + self.colorStyle = .default + self.cornerRadius = 8 + self.font = UIFont.systemFont(ofSize: 15) + self.badge = nil + self.title = "" super.init(coder: coder) + + self.setTitle(defaultTitle, for: .normal) didInitialize() } override var intrinsicContentSize: CGSize { - CGSize(width: super.intrinsicContentSize.width, height: style.height) + if badge != nil { + return CGSize( + width: super.intrinsicContentSize.width + badgeInfoField.frame.width + size.badgeLeftOffset, + height: size.defaultHeight + ) + } else { + return CGSize(width: super.intrinsicContentSize.width, height: size.defaultHeight) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + guard cachedSize != bounds.size else { return } + cachedSize = bounds.size + + size = TinkoffIDButtonSize(height: bounds.height) + + updateFonts() + updateImage() + + badgeLabel.sizeToFit() + + if badge != nil { + layoutWithBadge() + } else { + layoutWithoutBadge() + } + if colorStyle == .alternativeDark { + addIconBorder() + } } // MARK: - Private + + private func layoutWithBadge() { + let imageWidth = self.imageView!.frame.width + let titleWidth = self.titleLabel!.frame.width + + let rect = CGRect( + x: self.bounds.width - badgeLabel.frame.width - size.horizontalLabelOffset - size.contentEdgePadding, + y: (self.bounds.height - badgeLabel.frame.height - size.verticalLabelOffset) / 2, + width: badgeLabel.frame.width + size.horizontalLabelOffset, + height: badgeLabel.frame.height + size.verticalLabelOffset + ) + badgeInfoField.frame = rect + badgeLabel.center = badgeInfoField.center + badgeInfoField.layer.cornerRadius = rect.height/2 + + self.imageEdgeInsets = UIEdgeInsets( + top: .zero, + left: titleWidth + size.imageRightPadding, + bottom: .zero, + right: -titleWidth - size.imageRightPadding + ) + self.titleEdgeInsets = UIEdgeInsets( + top: .zero, + left: -imageWidth, + bottom: .zero, + right: imageWidth + ) + + contentHorizontalAlignment = .left + contentEdgeInsets = UIEdgeInsets( + top: size.contentVerticalPadding, + left: size.contentEdgePadding, + bottom: size.contentVerticalPadding, + right: size.contentEdgePadding + ) + } + + private func layoutWithoutBadge() { + let imageWidth = self.imageView!.frame.width + let titleWidth = self.titleLabel!.frame.width + self.imageEdgeInsets = UIEdgeInsets( + top: 0, + left: titleWidth + size.imageRightPadding / 2, + bottom: 0, + right: -titleWidth - size.imageRightPadding / 2 + ) + self.titleEdgeInsets = UIEdgeInsets( + top: 0, + left: -imageWidth - size.imageRightPadding / 2, + bottom: 0, + right: imageWidth + size.imageRightPadding / 2 + ) + } + + private func addIconBorder() { + imageBorder.frame.size = CGSize( + width: imageView!.frame.width + 2 * size.borderWidth, + height: imageView!.frame.height + 2 * size.borderWidth + ) + imageBorder.center = imageView!.center + imageBorder.layer.cornerRadius = imageBorder.frame.height/2 + } private func didInitialize() { configure() @@ -57,108 +195,205 @@ final class TinkoffIDButton: UIButton { private func configure() { // Title - setTitle(style.title, for: .normal) + titleLabel?.sizeToFit() // Image imageView?.contentMode = .scaleAspectFit - setImage(style.image, for: .normal) - setImage(style.image, for: .highlighted) - - imageEdgeInsets = UIEdgeInsets(top: .zero, - left: -style.imageRightPadding, - bottom: .zero, - right: style.imageRightPadding) - + // Content - contentEdgeInsets = UIEdgeInsets(top: style.contentVerticalPadding, - left: style.contentHorizontalPadding + style.imageRightPadding, - bottom: style.contentVerticalPadding, - right: style.contentHorizontalPadding) + contentEdgeInsets = UIEdgeInsets( + top: size.contentVerticalPadding, + left: size.contentHorizontalPadding, + bottom: size.contentVerticalPadding, + right: size.contentHorizontalPadding + ) setContentHuggingPriority(.required, for: .vertical) - + + if badge != nil { + addSubview(badgeInfoField) + addSubview(badgeLabel) + } + if colorStyle == .alternativeDark { + addSubview(imageBorder) + } + // Appearance - layer.cornerRadius = style.cornerRadius - titleLabel?.font = style.titleFont + layer.cornerRadius = cornerRadius + updateFonts() + updateImage() + } + + private func updateFonts() { + let titleText = NSMutableAttributedString( + string: title, + attributes: [.font: font.withSize(size.titleFontSize), .foregroundColor: colorStyle.titleColor] + ) + let defaultTitleText = NSAttributedString( + string: defaultTitle, + attributes: [ + .font: font.createBoldFont().withSize(size.titleFontSize), + .foregroundColor: colorStyle.titleColor + ] + ) + titleText.append(defaultTitleText) + setAttributedTitle(titleText, for: .normal) + + badgeLabel.font = font.withSize(size.badgeFontSize) + } + + private func updateImage() { + setImage(size.image, for: .normal) + setImage(size.image, for: .highlighted) } private func updateAppearanceForCurrentState() { - backgroundColor = style.backgroundColorFor(state: state) - setTitleColor(style.titleColorFor(state: state), for: state) + backgroundColor = colorStyle.backgroundColorFor(state: state) } } // MARK: - Private -private extension TinkoffIDButtonStyle { - /// Возвращает цвет фона для соответствующего состояния - /// - Parameter state: Состояние кнопки - func backgroundColorFor(state: UIControl.State) -> UIColor { - switch state { - case .highlighted: - return UIColor(red: 1, green: 0.803922, blue: 0.2, alpha: 1) - default: - return UIColor(red: 1, green: 0.86, blue: 0.1764, alpha: 1) - } - } - - /// Возвращает цвет текста для соответствующего состояния - /// - Parameter state: Состояние кнопки - func titleColorFor(state: UIControl.State) -> UIColor { - UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0) - } - +private extension TinkoffIDButtonSize { + /// Высота кнопки - var height: CGFloat { + var defaultHeight: CGFloat { switch self { - case .compact: + case .small: + return 30 + case .medium: return 40 - case .default: - return 56 + case .large: + return 60 } } - /// Степень скругления краёв - var cornerRadius: CGFloat { + /// Шрифт + var titleFontSize: CGFloat { switch self { - case .compact: - return 12 - case .default: - return 16 + case .small: + return 13 + case .medium: + return 15 + case .large: + return 19 } } - - /// Заголовок - var title: String? { - Bundle.resourcesBundle? - .localizedString("SignInButton.Title") - } - - /// Шрифт - var titleFont: UIFont { - let size: CGFloat - + + /// Отступ между иконкой и текстом + var imageRightPadding: CGFloat { switch self { - case .default: - size = 17 - case .compact: - size = 15 + case .small: + return 4 + case .medium, .large: + return 8 } - - return UIFont.systemFont(ofSize: size, weight: .regular) } - + /// Изображение var image: UIImage? { - Bundle.resourcesBundle? - .imageNamed("logo") + switch self { + case .small: + return Bundle.resourcesBundle? + .imageNamed("idLogoS") + case .medium: + return Bundle.resourcesBundle? + .imageNamed("idLogoM") + case .large: + return Bundle.resourcesBundle? + .imageNamed("idLogoL") + } } /// Отступы по горизонтали - var contentHorizontalPadding: CGFloat { 32 } + var contentHorizontalPadding: CGFloat { + switch self { + case .small: + return 32 + case .medium: + return 40 + case .large: + return 60 + } + } /// Отступы по вертикали - var contentVerticalPadding: CGFloat { 6 } - - /// Отступ между изображением и текстом - var imageRightPadding: CGFloat { 8 } + var contentVerticalPadding: CGFloat { + switch self { + case .small: + return 5 + case .medium: + return 10 + case .large: + return 16 + } + } + + /// Отступ от краев при отображении кэшбэка + var contentEdgePadding: CGFloat { + switch self { + case .small: + return 10 + case .medium: + return 13 + case .large: + return 20 + } + } + + /// Отступы внутри кэшбэка + var horizontalLabelOffset: CGFloat { + switch self { + case .small: + return 8 + case .medium: + return 13 + case .large: + return 16 + } + } + var verticalLabelOffset: CGFloat { + switch self { + case .small: + return 0 + case .medium: + return 4 + case .large: + return 4 + } + } + + + /// Шрифт кэшбэка + var badgeFontSize: CGFloat { + switch self { + case .small, .medium: + return 11 + case .large: + return 13 + } + } + + /// Минимальный отступ от бейджа слева + var badgeLeftOffset: CGFloat { + switch self { + case .small: + return 20 + case .medium: + return 26 + case .large: + return 40 + } + } + + /// Ширина окантовки лого + var borderWidth: CGFloat { 2 } +} + +private extension UIFont { + func createBoldFont() -> UIFont { + guard let descriptor = self.fontDescriptor.withSymbolicTraits(UIFontDescriptor.SymbolicTraits.traitBold) else { + return self + } + return UIFont(descriptor: descriptor, size: 0) + } } diff --git a/Sources/UI/Button/TinkoffIDButtonBuilder.swift b/Sources/UI/Button/TinkoffIDButtonBuilder.swift index 108d7ab..c7c2816 100644 --- a/Sources/UI/Button/TinkoffIDButtonBuilder.swift +++ b/Sources/UI/Button/TinkoffIDButtonBuilder.swift @@ -18,21 +18,79 @@ import UIKit -/// Стиль кнопки входа -public enum TinkoffIDButtonStyle { - /// Стандартная желтая кнопка высотой 56 точек +/// Конфигурация кнопки +public struct TinkoffIDButtonConfiguration { + /// Стиль кнопки + let colorStyle: TinkoffIDButtonColorStyle + /// Размер + let size: TinkoffIDButtonSize + /// Радиус скругления + let cornerRadius: CGFloat + /// Шрифт + let font: UIFont + + public init( + colorStyle: TinkoffIDButtonColorStyle = .default, + size: TinkoffIDButtonSize = .medium, + cornerRadius: CGFloat = 8, + font: UIFont = UIFont.systemFont(ofSize: 15) + ) { + self.colorStyle = colorStyle + self.size = size + self.cornerRadius = cornerRadius + self.font = font + } +} + +/// Размер кнопки входа +public enum TinkoffIDButtonSize { + /// Размер от 30 до 40, по дефолту 30 + case small + /// Размер от 40 до 60, по дефолту 40 + case medium + /// Размер от 60, по дефолту 60 + case large + + init(height: CGFloat) { + if height < 40 { + self = .small + } else if height < 60 { + self = .medium + } else { + self = .large + } + } +} + +/// Цветовой стиль кнопки входа +public enum TinkoffIDButtonColorStyle { + /// Стандартная желтая тема case `default` - /// Желтая кнопка высотой 40 точек - case compact + /// Светлая тема + case alternativeLight + /// Темная тема + case alternativeDark } /// Сборщик кнопки входа через Тинькофф public final class TinkoffIDButtonBuilder { - /// Создает кнопку входа через Тинькофф - /// - Parameter style: Стиль кнопки + /// Создает прямоугольную кнопку входа через Тинькофф + /// - Parameter configuration: Конфигурация кнопки + /// - Parameter title: Текст кнопки + /// - Parameter badge: Дополнительный текст на бейдже + /// - Returns: Контрол кнопки + public static func build( + configuration: TinkoffIDButtonConfiguration = TinkoffIDButtonConfiguration(), + title: String? = nil, + badge: String? = nil + ) -> UIControl { + TinkoffIDButton(configuration: configuration, title: title, badge: badge) + } + /// Создает круглую кнопку входа через Тинькофф + /// - Parameter colorStyle: Цветовая тема кнопки /// - Returns: Контрол кнопки - public static func build(_ style: TinkoffIDButtonStyle) -> UIControl { - TinkoffIDButton(style) + public static func buildCompact(colorStyle: TinkoffIDButtonColorStyle = .default) -> UIControl { + TinkoffIDCompactButton(colorStyle: colorStyle) } } diff --git a/Sources/UI/Button/TinkoffIDButtonColorStyle.swift b/Sources/UI/Button/TinkoffIDButtonColorStyle.swift new file mode 100644 index 0000000..3cd1d77 --- /dev/null +++ b/Sources/UI/Button/TinkoffIDButtonColorStyle.swift @@ -0,0 +1,79 @@ +// +// TinkoffIDButtonColorStyle+Extension.swift +// TinkoffID +// +// Created by Margarita Shishkina on 13.07.2022. +// + +import Foundation + +extension TinkoffIDButtonColorStyle { + /// Возвращает цвет фона для соответствующего состояния + /// - Parameter state: Состояние кнопки + func backgroundColorFor(state: UIControl.State) -> UIColor { + switch state { + case .highlighted: + return highlightedBackgroundColor + default: + return backgroundColor + } + } + + /// цвет текста для соответствующего состояния + var titleColor: UIColor { + switch self { + case .alternativeDark: + return UIColor(red: 1, green: 1, blue: 1, alpha: 1) + case .alternativeLight: + return UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) + case .default: + return UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) + } + } + + /// Цвет поля кэшбэка + var badgeFieldColor: UIColor { + switch self { + case .alternativeDark: + return UIColor(red: 61 / 255, green: 61 / 255, blue: 61 / 255, alpha: 1) + case .alternativeLight: + return UIColor(red: 1, green: 1, blue: 1, alpha: 1) + case .default: + return UIColor(red: 1, green: 238 / 255, blue: 149 / 255, alpha: 1) + } + } + + /// Цвет текста кэшбэка + var badgeTextColor: UIColor { + switch self { + case .alternativeDark: + return UIColor(red: 1, green: 1, blue: 1, alpha: 1) + case .alternativeLight: + return UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) + case .default: + return UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) + } + } + + private var highlightedBackgroundColor: UIColor { + switch self { + case .alternativeDark: + return UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) + case .alternativeLight: + return UIColor(red: 206 / 255, green: 206 / 255, blue: 207 / 255, alpha: 1) + case .default: + return UIColor(red: 250 / 255, green: 182 / 255, blue: 25 / 255, alpha: 1) + } + } + + private var backgroundColor: UIColor { + switch self { + case .alternativeDark: + return UIColor(red: 0, green: 0, blue: 0, alpha: 1) + case .alternativeLight: + return UIColor(red: 245 / 255, green: 245 / 255, blue: 246 / 255, alpha: 1) + case .default: + return UIColor(red: 255 / 255, green: 221 / 255, blue: 45 / 255, alpha: 1) + } + } +} diff --git a/Sources/UI/Button/TinkoffIDCompactButton.swift b/Sources/UI/Button/TinkoffIDCompactButton.swift new file mode 100644 index 0000000..22b0ab5 --- /dev/null +++ b/Sources/UI/Button/TinkoffIDCompactButton.swift @@ -0,0 +1,89 @@ +// +// TinkoffIDRoundButton.swift +// TinkoffID +// +// Copyright (c) 2022 Tinkoff +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import UIKit + +final class TinkoffIDCompactButton: UIButton { + + /// Высота кнопки + private var size: CGFloat { 56 } + + /// Изображение + private var image: UIImage? { + Bundle.resourcesBundle? + .imageNamed("logo") + } + + private let colorStyle: TinkoffIDButtonColorStyle + + override var isHighlighted: Bool { + didSet { + updateAppearanceForCurrentState() + } + } + + init(colorStyle: TinkoffIDButtonColorStyle) { + self.colorStyle = colorStyle + + super.init(frame: .zero) + + didInitialize() + } + + required init?(coder: NSCoder) { + self.colorStyle = .default + + super.init(coder: coder) + didInitialize() + } + + override var intrinsicContentSize: CGSize { + CGSize(width: size, height: size) + } + + override func layoutSubviews() { + super.layoutSubviews() + + layer.cornerRadius = bounds.height/2 + } + + // MARK: - Private + + private func didInitialize() { + configure() + updateAppearanceForCurrentState() + } + + private func configure() { + + // Image + imageView?.contentMode = .scaleToFill + setImage(image, for: .normal) + setImage(image, for: .highlighted) + contentHorizontalAlignment = .fill + contentVerticalAlignment = .fill + + setContentHuggingPriority(.required, for: .vertical) + + contentEdgeInsets = UIEdgeInsets(top: 12, left: 10, bottom: 12, right: 10) + } + + private func updateAppearanceForCurrentState() { + backgroundColor = colorStyle.backgroundColorFor(state: state) + } +}