Comments added

This commit is contained in:
Alexey Gerasimov 2017-05-24 19:09:58 +03:00
parent 98ea69217d
commit 596f25ccc0
21 changed files with 140 additions and 9 deletions

View File

@ -22,10 +22,14 @@
import ObjectMapper
/// Class describes typical response from server, which designed by TouchInstinct
public class ApiResponse: ApiResponseProtocol, ImmutableMappable {
/// nil in case of error, result of request otherwise
public let result: Any?
/// In case of error contains error code, 0 (zero) otherwise
public let errorCode: Int
/// nil in case of success, error description otherwise
public let errorMessage: String?
public required init(map: Map) throws {
@ -36,9 +40,12 @@ public class ApiResponse: ApiResponseProtocol, ImmutableMappable {
}
/// Describes error, which received from server designed by TouchInstinct
public protocol ApiResponseProtocol: ImmutableMappable {
/// Error code
var errorCode: Int { get }
/// Error description
var errorMessage: String? { get }
}

View File

@ -23,6 +23,7 @@
import Foundation
import ObjectMapper
/// Base date formatter class, contains most frequently used formats, including RFC3339
open class BaseDateFormatter {
private static let apiDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
@ -61,37 +62,44 @@ open class BaseDateFormatter {
return dateFormater
}()
// MARK: Public interface
// MARK: - Public interface
/// DateFormatter's locale can be overriden
open class var usedLocale: Locale {
return .current
}
/// Parse date from string with format: yyyy-MM-dd'T'HH:mm:ssZ
public static func backendDate(fromStrDate strDate: String) -> Date? {
apiFormatter.locale = usedLocale
return apiFormatter.date(from: strDate)
}
/// Serialize date into string with format: yyyy-MM-dd'T'HH:mm:ssZ
public static func backendStrDate(withDate date: Date) -> String {
apiFormatter.locale = usedLocale
return apiFormatter.string(from: date)
}
/// Serialize date into string with format: yyyy-MM-dd'T'Z
public static func backendDateWithoutTime(withDate date: Date) -> String {
apiDateWithoutTimeFormatter.locale = usedLocale
return apiDateWithoutTimeFormatter.string(from: date)
}
/// Serialize date into string with format: HH:mm
public static func hourAndMinuteStrDate(withDate date: Date) -> String {
hourAndMinuteFormatter.locale = usedLocale
return hourAndMinuteFormatter.string(from: date)
}
/// Serialize date into string with format: dd MMM
public static func dayAndMonthStrDate(withDate date: Date) -> String {
hourAndMinuteFormatter.locale = usedLocale
return dayAndMonthFormatter.string(from: date)
}
/// Serialize date into string with format: dd.MM.yyyy
public static func dayMonthYearStrDate(withDate date: Date) -> String {
hourAndMinuteFormatter.locale = usedLocale
return dayMonthYearFormatter.string(from: date)
@ -99,6 +107,7 @@ open class BaseDateFormatter {
// MARK: - Transformers
/// Transformer to workaround with dates in Mappable (ObjectMapper) objects
public static var transformFromStringToDate: TransformOf<Date, String> {
return TransformOf<Date, String>(fromJSON: { (stringValue) -> Date? in
if let stringValue = stringValue {

View File

@ -24,11 +24,13 @@ import UIKit
import RxSwift
import RxCocoa
/// Side to which ativity indicator applied
public enum LoadingBarButtonSide {
case left
case right
}
/// Workaround with navigationBarButton, that can change state (UI) into activity indicator
public class LoadingBarButton {
fileprivate weak var navigationItem: UINavigationItem?
@ -54,6 +56,13 @@ public class LoadingBarButton {
}
}
/**
Create an instance of LoadingBarButton
- Parameters:
- navigationItem: item to which apply changes
- side: side where navigationItem would be placed
*/
public init(navigationItem: UINavigationItem, side: LoadingBarButtonSide) {
self.navigationItem = navigationItem
self.side = side
@ -74,6 +83,14 @@ public class LoadingBarButton {
extension Observable {
/**
Reactive extension for LoadingBarButton
Apply transformations on subscribe and on dispose events
- Parameters:
- barButton: LoadingBarButton instance to which transformations would applied
- Returns:
*/
public func changeLoadingUI(using barButton: LoadingBarButton) -> Observable<Observable.E> {
return observeOn(MainScheduler.instance)
.do(onSubscribe: {

View File

@ -20,11 +20,15 @@
// THE SOFTWARE.
//
/// Configuration container for BasePassCodeViewController
public struct PassCodeConfiguration {
/// Pass code length
public var passCodeCharactersNumber: UInt = 4
/// Incorrect pass code attempts count
public var maxAttemptsLoginNumber: UInt = 5
/// Clear input progress when application goes to background
public var shouldResetWhenGoBackground: Bool = true
private init() {}
@ -37,6 +41,7 @@ public struct PassCodeConfiguration {
self.passCodeCharactersNumber = passCodeCharactersNumber
}
/// Returns configuration with default values
public static var defaultConfiguration: PassCodeConfiguration {
return PassCodeConfiguration()
}

View File

@ -20,6 +20,7 @@
// THE SOFTWARE.
//
/// Describes error, which may occures during pass code entering
public enum PassCodeError: Error {
case codesNotMatch
case wrongCode

View File

@ -38,6 +38,7 @@ extension PassCodeHolderProtocol {
}
}
/// Holds information about pass codes during pass code creation process
public class PassCodeHolderCreate: PassCodeHolderProtocol {
public let type: PassCodeControllerType = .create
@ -91,6 +92,7 @@ public class PassCodeHolderCreate: PassCodeHolderProtocol {
}
/// Holds information about pass code during pass code entering process
public class PassCodeHolderEnter: PassCodeHolderProtocol {
public let type: PassCodeControllerType = .enter
@ -120,6 +122,7 @@ public class PassCodeHolderEnter: PassCodeHolderProtocol {
}
/// Holds information about pass codes during pass code changing process
public class PassCodeHolderChange: PassCodeHolderProtocol {
public let type: PassCodeControllerType = .change

View File

@ -20,17 +20,26 @@
// THE SOFTWARE.
//
/// Holds information about enter type (create, change, etc), step
/// Also describes interface to manipulate with entered pass code
public protocol PassCodeHolderProtocol {
/// Type of operation with pass code
var type: PassCodeControllerType { get }
/// Operation step
var enterStep: PassCodeControllerState { get }
/// Add pass code for current step
func add(passCode: String)
/// Reset all progress
func reset()
/// Should been pass code validated
var shouldValidate: Bool { get }
/// Current pass code
var passCode: String? { get }
/// Returns passCode or error if pass code is invalid
func validate() -> PassCodeValidationResult
}
@ -39,6 +48,12 @@ public class PassCodeHolderBuilder {
private init() {}
/**
Creates holder by type (create, change, etc)
- parameter type: type of pass code controller
- returns: pass code information holder, specific by type
*/
public static func build(with type: PassCodeControllerType) -> PassCodeHolderProtocol {
switch type {
case .create:

View File

@ -20,6 +20,7 @@
// THE SOFTWARE.
//
/// Result of pass code validation
public enum PassCodeValidationResult {
case valid(String)

View File

@ -25,17 +25,20 @@ import RxSwift
import RxCocoa
import LeadKit
/// Describes pin image
public enum PinImageType {
case entered
case clear
}
/// Pass code operation type
public enum PassCodeControllerType {
case create
case enter
case change
}
/// Pass code operation state
public enum PassCodeControllerState {
case enter
case repeatEnter
@ -43,6 +46,7 @@ public enum PassCodeControllerState {
case newEnter
}
/// Base view controller that operates with pass code
open class BasePassCodeViewController: UIViewController {
public var viewModel: BasePassCodeViewModel!
@ -157,41 +161,44 @@ open class BasePassCodeViewController: UIViewController {
// MARK: - HAVE TO OVERRIDE
/// Returns prompt that appears on touch id system alert
open var touchIdHint: String {
assertionFailure("You should override this var: touchIdHint")
return ""
}
// override to change Images
/// Override to point certain images
open func imageFor(type: PinImageType) -> UIImage {
assertionFailure("You should override this method: imageFor(type: PinImageType)")
return UIImage()
}
// override to change error text
/// Override to change error description
open func errorDescription(for error: PassCodeError) -> String {
assertionFailure("You should override this method: errorDescription(for error: PassCodeError)")
return ""
}
// override to change action title text
/// Override to change action title text
open func actionTitle(for passCodeControllerState: PassCodeControllerState) -> String {
assertionFailure("You should override this method: actionTitle(for passCodeControllerState: PassCodeControllerState)")
return ""
}
// MARK: - Functions that can you can override to castomise your controller
// MARK: - Functions that you can override to customize your controller
/// Call to show error
open func showError(for error: PassCodeError) {
errorLabel?.text = errorDescription(for: error)
errorLabel?.isHidden = false
}
/// Call to disappear error label
open func hideError() {
errorLabel?.isHidden = true
}
// override to change UI for state
/// Override to change UI for state
open func configureUI(for passCodeControllerState: PassCodeControllerState) {
resetDotsUI()
titleLabel?.text = actionTitle(for: passCodeControllerState)
@ -199,6 +206,7 @@ open class BasePassCodeViewController: UIViewController {
}
// MARK: - ConfigurableController
// We need to implement all functions of ConfigurableController protocol to give ability to override them.
extension BasePassCodeViewController: ConfigurableController {
@ -242,6 +250,7 @@ extension BasePassCodeViewController: ConfigurableController {
}
// MARK: - UITextFieldDelegate
extension BasePassCodeViewController: UITextFieldDelegate {
public func textField(_ textField: UITextField,

View File

@ -24,18 +24,22 @@ import LeadKit
import RxSwift
import RxCocoa
/// Describes types of authentication
public enum PassCodeAuthType {
case passCode(String)
case touchId
}
/// Base view model for passCodeViewController
open class BasePassCodeViewModel: BaseViewModel {
public let controllerType: PassCodeControllerType
public let disposeBag = DisposeBag()
/// TouchId service, which can answer if user is authorized by finger
public let touchIdService: TouchIDService?
/// Contains configuration for pass code operations
public let passCodeConfiguration: PassCodeConfiguration
fileprivate let validationResultHolder = Variable<PassCodeValidationResult?>(nil)
@ -114,21 +118,25 @@ open class BasePassCodeViewModel: BaseViewModel {
// MARK: - HAVE TO OVERRIDE
/// Override to check if entered pass code is equal to stored
open func isEnteredPassCodeValid(_ passCode: String) -> Bool {
assertionFailure("You should override this method: isEnteredPassCodeValid(_ passCode: String)")
return false
}
/// Handler called after successful authentication
open func authSucceed(_ type: PassCodeAuthType) {
assertionFailure("You should override this method: authSucceed(_ type: PassCodeAuthType)")
}
// MARK: - Functions that can you can override to use TouchId
/// Override to be able use touchId during authentication
open var isTouchIdEnabled: Bool {
return false
}
/// You should save user choice about authenticate by touchId
open func activateTouchIdForUser() {
assertionFailure("You should override this method: activateTouchIdForUser()")
}

View File

@ -20,8 +20,7 @@
// THE SOFTWARE.
//
import Foundation
/// Describes possible error, received from back-end
public enum ApiError: Error {
case error(code: Int, message: String)
@ -29,6 +28,7 @@ public enum ApiError: Error {
}
// MARK: - LocalizedError
extension ApiError: LocalizedError {
public init(apiResponse: ApiResponseProtocol) {

View File

@ -20,10 +20,12 @@
// THE SOFTWARE.
//
/// Describes error by raw value (more likely - Int code), received from back-end
public protocol ApiErrorProtocol: RawRepresentable {}
extension Error {
/// Method indicates that error is back-end error
public func isApiError<T: ApiErrorProtocol>(_ apiErrorType: T) -> Bool where T.RawValue == Int {
if let error = self as? ApiError,
case let .error(code: code, message: _) = error,

View File

@ -22,6 +22,7 @@
import Foundation
/// Describes "no connection to server" error
public enum ConnectionError: LocalizedError {
case noConnection

View File

@ -28,6 +28,7 @@ public typealias VoidBlock = () -> Void
public extension Observable {
/// Handles connection errors during request
public func handleConnectionErrors() -> Observable<Observable.E> {
return observeOn(CurrentThreadScheduler.instance)
@ -52,6 +53,13 @@ public extension Observable {
})
}
/**
Allow to configure request to restart if error occured
- parameters:
- errorTypes: list of error types, which triggers request restart
- retryLimit: how many times request can restarts
*/
public func retryWithinErrors(_ errorTypes: [Error.Type] = [ConnectionError.self],
retryLimit: Int = DefaultNetworkService.retryLimit)
-> Observable<Observable.E> {
@ -66,6 +74,13 @@ public extension Observable {
}
}
/**
Add block that executes, when error, described by ApiErrorProtocol, occured during request
- parameters:
- apiErrorType: type of errors, received frim server
- handler: block, that executes, when error occured
*/
public func handleApiError<T: ApiErrorProtocol>(_ apiErrorType: T,
handler: @escaping () -> Void) -> Observable<Observable.E>
where T.RawValue == Int {
@ -78,6 +93,11 @@ public extension Observable {
})
}
/**
Add ability to monitor request status
- parameter isLoading: subject, request state bind to
*/
public func changeLoadingBehaviour(isLoading: PublishSubject<Bool>) -> Observable<Observable.E> {
return observeOn(CurrentThreadScheduler.instance)
.do(onNext: { _ in

View File

@ -24,6 +24,7 @@ import UIKit
extension UIBarButtonItem {
/// Creates activity indicator view and bar button item (based on activity indicator)
public static var activityIndicator: (barButton: UIBarButtonItem, activityIndicator: UIActivityIndicatorView) {
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .white)
let indicatorBar = UIBarButtonItem(customView: indicatorView)

View File

@ -22,13 +22,14 @@
import Foundation
fileprivate enum Keys {
private enum Keys {
static let sessionId = "sessionId"
static let userLogin = "userLogin"
}
public extension UserDefaults {
/// Default place to store session id
public var sessionId: String? {
get {
return string(forKey: Keys.sessionId)
@ -38,6 +39,7 @@ public extension UserDefaults {
}
}
/// Default place to store userLogin
public var userLogin: String? {
get {
return string(forKey: Keys.userLogin)

View File

@ -24,8 +24,10 @@ import KeychainAccess
import CocoaLumberjack
import IDZSwiftCommonCrypto
/// Represents base pass code service which encapsulates pass code storing
open class BasePassCodeService {
/// Override to set specific keychain service name
open class var keychainService: String {
return Bundle.main.bundleIdentifier ?? ""
}
@ -63,10 +65,12 @@ open class BasePassCodeService {
extension BasePassCodeService {
/// Indicates is pass code already saved on this device
public var isPassCodeSaved: Bool {
return keychain[Keys.passCodeHash] != nil
}
/// Indicates is it possible to authenticate on this device via touch id
public var isTouchIdEnabled: Bool {
get {
return keychain[Keys.isTouchIdEnabled] == Values.touchIdEnabled
@ -76,6 +80,7 @@ extension BasePassCodeService {
}
}
/// Saves new pass code
public func save(passCode: String?) {
if let passCode = passCode {
keychain[Keys.passCodeHash] = sha256(passCode)
@ -84,10 +89,12 @@ extension BasePassCodeService {
}
}
/// Check if pass code is correct
public func check(passCode: String) -> Bool {
return sha256(passCode) == passCodeHash
}
/// Reset pass code settings
public func reset() {
save(passCode: nil)
isTouchIdEnabled = false

View File

@ -23,12 +23,14 @@
import RxSwift
import LeadKit
/// Represents service that store basic user information
open class BaseUserService {
public init() {
// Can be overrided
}
/// Returns user login
open var userLogin: String {
guard let defaultsLogin = UserDefaults.standard.userLogin else {
assertionFailure("userLogin is nil. Use isLoggedIn before read userLogin")
@ -38,6 +40,7 @@ open class BaseUserService {
return defaultsLogin
}
/// Returns session id
open var sessionId: String {
guard let defaultsSessionId = UserDefaults.standard.sessionId else {
assertionFailure("sessionId is nil. Use isLoggedIn before read sessionId")
@ -46,10 +49,12 @@ open class BaseUserService {
return defaultsSessionId
}
/// Indicates if user is logged in
open var isLoggedIn: Bool {
return UserDefaults.standard.sessionId != nil
}
/// Reset user information
open class func clearData() {
UserDefaults.standard.sessionId = nil
UserDefaults.standard.userLogin = nil

View File

@ -25,8 +25,10 @@ import Alamofire
import ObjectMapper
import RxSwift
/// Base network service implementation for back-end designed by TouchInstinct
open class ApiNetworkService: DefaultNetworkService {
/// Returns observable for ImmutableMappable response model by parameters
open func request<T: ImmutableMappable>(with parameters: ApiRequestParameters) -> Observable<T> {
let apiResponseRequest = rxRequest(with: parameters) as Observable<(response: HTTPURLResponse, model: ApiResponse)>
@ -41,6 +43,7 @@ open class ApiNetworkService: DefaultNetworkService {
}
}
/// Returns observable for boolean response by parameters
open func requestForResult(with parameters: ApiRequestParameters) -> Observable<Bool> {
let apiResponseRequest = rxRequest(with: parameters) as Observable<(response: HTTPURLResponse, model: ApiResponse)>

View File

@ -25,16 +25,19 @@ import LeadKit
import ObjectMapper
import RxSwift
/// Default implementation of network service, which trust any server and uses default timeout interval
open class DefaultNetworkService: NetworkService {
static let retryLimit = 3
private let disposeBag = DisposeBag()
/// Override to set base server url
open class var baseUrl: String {
fatalError("You should override this var: baseUrl")
}
/// Override to change timeout interval default value
open class var defaultTimeoutInterval: TimeInterval {
return 20.0
}
@ -47,12 +50,14 @@ open class DefaultNetworkService: NetworkService {
// MARK: - Default Values
/// Override to change server trust policies
open class var serverTrustPolicies: [String: ServerTrustPolicy] {
return [
baseUrl: .disableEvaluation
]
}
/// Override to change default urlSession configuration
open class var configuration: URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = defaultTimeoutInterval
@ -60,6 +65,7 @@ open class DefaultNetworkService: NetworkService {
return configuration
}
/// Override to configure alamofire session manager
open class var sessionManager: SessionManager {
let sessionManager = SessionManager(configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))

View File

@ -24,6 +24,7 @@ import LocalAuthentication
public typealias TouchIDServiceAuthHandler = (Bool) -> Void
/// Represents service that provides access to authentication via touch id
public class TouchIDService {
private lazy var laContext: LAContext = {
@ -32,10 +33,18 @@ public class TouchIDService {
public init() {}
/// Indicates is it possible to authenticate on this device via touch id
public var canAuthenticateByTouchId: Bool {
return laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
/**
Initiates system touch id authentication process
- parameters:
- description: prompt on the system alert that describes what for user should attach finger to device
- authHandler: callback, with parameter, indicates if user authenticate successfuly
*/
public func authenticateByTouchId(description: String, authHandler: @escaping TouchIDServiceAuthHandler) {
laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: description) { success, _ in