diff --git a/GoMoney.xcodeproj/project.pbxproj b/GoMoney.xcodeproj/project.pbxproj index 59a6823..08bbaa4 100644 --- a/GoMoney.xcodeproj/project.pbxproj +++ b/GoMoney.xcodeproj/project.pbxproj @@ -967,8 +967,8 @@ 08C8729B28F655CD00DC859D /* Sources */, 08C8729C28F655CD00DC859D /* Frameworks */, 08C8729D28F655CD00DC859D /* Resources */, - 8291F096518328B4CB2334CA /* [CP] Embed Pods Frameworks */, 08EEA005291CC29F003B35B8 /* Embed Foundation Extensions */, + C7F73096535B070AD90AE7EF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1085,7 +1085,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; }; - 8291F096518328B4CB2334CA /* [CP] Embed Pods Frameworks */ = { + C7F73096535B070AD90AE7EF /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( diff --git a/GoMoney/AppDelegate/AppDelegate.swift b/GoMoney/AppDelegate/AppDelegate.swift index b6319ee..0b30cec 100644 --- a/GoMoney/AppDelegate/AppDelegate.swift +++ b/GoMoney/AppDelegate/AppDelegate.swift @@ -1,40 +1,32 @@ -// -// AppDelegate.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import FirebaseCore import GoogleSignIn import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { return GIDSignIn.sharedInstance.handle(url) } - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - + FirebaseApp.configure() - + return true } // MARK: UISceneSession Lifecycle - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + func application(_: UIApplication, didDiscardSceneSessions _: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } - diff --git a/GoMoney/AppDelegate/SceneDelegate.swift b/GoMoney/AppDelegate/SceneDelegate.swift index 068db82..dce591d 100644 --- a/GoMoney/AppDelegate/SceneDelegate.swift +++ b/GoMoney/AppDelegate/SceneDelegate.swift @@ -1,10 +1,3 @@ -// -// SceneDelegate.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -12,7 +5,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let settingManager = SettingsManager.shared - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). @@ -44,29 +37,29 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() } - func sceneDidDisconnect(_ scene: UIScene) { + func sceneDidDisconnect(_: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - func sceneDidBecomeActive(_ scene: UIScene) { + func sceneDidBecomeActive(_: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - func sceneWillResignActive(_ scene: UIScene) { + func sceneWillResignActive(_: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - func sceneWillEnterForeground(_ scene: UIScene) { + func sceneWillEnterForeground(_: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - func sceneDidEnterBackground(_ scene: UIScene) { + func sceneDidEnterBackground(_: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. diff --git a/GoMoney/Base/GMMainViewController.swift b/GoMoney/Base/GMMainViewController.swift index 13dbfd9..cc23a02 100644 --- a/GoMoney/Base/GMMainViewController.swift +++ b/GoMoney/Base/GMMainViewController.swift @@ -1,10 +1,3 @@ -// -// GMViewController.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import Reachability import UIKit @@ -55,17 +48,20 @@ class GMMainViewController: GMViewController { image: leftBarImage, style: .done, target: self, - action: nil) + action: nil + ) let leftBarTitle = UIBarButtonItem( title: leftTitle, style: .plain, target: self, - action: nil) + action: nil + ) leftBarTitle.setTitleTextAttributes( [.foregroundColor: UIColor.white, .font: K.Theme.titleFont], - for: .disabled) + for: .disabled + ) leftBarIcon.isEnabled = false leftBarTitle.isEnabled = false @@ -77,7 +73,8 @@ class GMMainViewController: GMViewController { image: rightBarImage, style: .done, target: self, - action: nil) + action: nil + ) navigationItem.rightBarButtonItem = rightBarIcon } @@ -106,7 +103,8 @@ class GMMainViewController: GMViewController { if self?.networkAvailable == false { self?.snackBar( message: "Connection restored", - actionIcon: UIImage(named: "ic_wifi")?.color(.green)) + actionIcon: UIImage(named: "ic_wifi")?.color(.green) + ) self?.networkAvailable = true } } @@ -119,7 +117,8 @@ class GMMainViewController: GMViewController { self?.snackBar( message: "Connection lost", - actionIcon: UIImage(named: "ic_wifi_off")?.color(.red)) + actionIcon: UIImage(named: "ic_wifi_off")?.color(.red) + ) } } diff --git a/GoMoney/Base/GMViewController.swift b/GoMoney/Base/GMViewController.swift index 169f2e5..6dd6985 100644 --- a/GoMoney/Base/GMViewController.swift +++ b/GoMoney/Base/GMViewController.swift @@ -1,10 +1,3 @@ -// -// GMViewController.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import UIKit class GMViewController: UIViewController { @@ -70,8 +63,8 @@ class GMViewController: UIViewController { func setupKeyboard( onKeyboardWillShow: ((CGFloat, Double) -> Void)?, onKeyboardWillHide: ((CGFloat, Double) -> Void)?, - hideKeyboarOnTap: Bool = true) - { + hideKeyboarOnTap: Bool = true + ) { self.onKeyboardWillShow = onKeyboardWillShow self.onKeyboardWillHide = onKeyboardWillHide @@ -79,13 +72,15 @@ class GMViewController: UIViewController { self, selector: #selector(handleKeyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, - object: nil) + object: nil + ) NotificationCenter.default.addObserver( self, selector: #selector(handleKeyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, - object: nil) + object: nil + ) if hideKeyboarOnTap { hideKeyboardOnTap() diff --git a/GoMoney/Common/AsyncImage/AsyncImageView.swift b/GoMoney/Common/AsyncImage/AsyncImageView.swift index 5aa6314..838d908 100644 --- a/GoMoney/Common/AsyncImage/AsyncImageView.swift +++ b/GoMoney/Common/AsyncImage/AsyncImageView.swift @@ -44,7 +44,7 @@ public class AsyncImageView: UIView { imageView.trailingAnchor.constraint(equalTo: trailingAnchor), activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), - activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor) + activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor), ]) } @@ -53,13 +53,13 @@ public class AsyncImageView: UIView { loader.loadImage(imageURL) { [weak self] result in DispatchQueue.main.async { switch result { - case .success(let image): - self?.activityIndicator.stopAnimating() - self?.imageView.image = image + case let .success(image): + self?.activityIndicator.stopAnimating() + self?.imageView.image = image - case .failure: - self?.activityIndicator.stopAnimating() - self?.imageView.image = defaultImage + case .failure: + self?.activityIndicator.stopAnimating() + self?.imageView.image = defaultImage } } } diff --git a/GoMoney/Common/Chart/Formatter/DayAxisValueFormatter.swift b/GoMoney/Common/Chart/Formatter/DayAxisValueFormatter.swift index 68e587c..e8872ec 100644 --- a/GoMoney/Common/Chart/Formatter/DayAxisValueFormatter.swift +++ b/GoMoney/Common/Chart/Formatter/DayAxisValueFormatter.swift @@ -8,7 +8,7 @@ class BarChartXAxisFormatter: IndexAxisValueFormatter { self.tagExpenses = tagExpenses } - override func stringForValue(_ value: Double, axis: AxisBase?) -> String { + override func stringForValue(_ value: Double, axis _: AxisBase?) -> String { guard let tagExpenses = tagExpenses, tagExpenses.count > 0, diff --git a/GoMoney/Common/Chart/Formatter/LineChartXAxisFormatter.swift b/GoMoney/Common/Chart/Formatter/LineChartXAxisFormatter.swift index 41871db..54c3a0a 100644 --- a/GoMoney/Common/Chart/Formatter/LineChartXAxisFormatter.swift +++ b/GoMoney/Common/Chart/Formatter/LineChartXAxisFormatter.swift @@ -10,7 +10,7 @@ class LineChartXAxisFormatter: IndexAxisValueFormatter { self.dateType = dateType } - override func stringForValue(_ value: Double, axis: AxisBase?) -> String { + override func stringForValue(_ value: Double, axis _: AxisBase?) -> String { guard let dateAmount = dateAmount, let dateType = dateType, diff --git a/GoMoney/Common/Currency/MoneyFormatter.swift b/GoMoney/Common/Currency/MoneyFormatter.swift index ea07936..df35f44 100644 --- a/GoMoney/Common/Currency/MoneyFormatter.swift +++ b/GoMoney/Common/Currency/MoneyFormatter.swift @@ -1,4 +1,4 @@ -class MoneyFormatter { +enum MoneyFormatter { static func formatShorter(amount: Double, currency: CurrencyUnit) -> String { switch currency { case .dong: diff --git a/GoMoney/Common/InputView/AccessoryView.swift b/GoMoney/Common/InputView/AccessoryView.swift index c36fe95..5268db0 100644 --- a/GoMoney/Common/InputView/AccessoryView.swift +++ b/GoMoney/Common/InputView/AccessoryView.swift @@ -92,7 +92,7 @@ public class AccessoryView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Common/InputView/CategoryPickerInputView.swift b/GoMoney/Common/InputView/CategoryPickerInputView.swift index 2787f15..c1b1536 100644 --- a/GoMoney/Common/InputView/CategoryPickerInputView.swift +++ b/GoMoney/Common/InputView/CategoryPickerInputView.swift @@ -46,7 +46,7 @@ public class CategoryPickerInputView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -69,7 +69,7 @@ public class CategoryPickerInputView: UIView { } extension CategoryPickerInputView: UICollectionViewDelegate, UICollectionViewDataSource { - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + public func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { return type == .expense ? expenseTags.count : incomeTags.count } @@ -82,7 +82,7 @@ extension CategoryPickerInputView: UICollectionViewDelegate, UICollectionViewDat return UICollectionViewCell() } - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + public func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { let tag = type == .expense ? expenseTags[indexPath.row] : incomeTags[indexPath.row] didSelect?(tag) } @@ -110,7 +110,7 @@ class CategoryPickerCell: UICollectionViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Common/InputView/DatePickerInputView.swift b/GoMoney/Common/InputView/DatePickerInputView.swift index ab564a3..5413bf3 100644 --- a/GoMoney/Common/InputView/DatePickerInputView.swift +++ b/GoMoney/Common/InputView/DatePickerInputView.swift @@ -20,12 +20,12 @@ public class DatePickerInputView: UIView { ) { super.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 200)) self.didSelect = didSelect - self.pickerMode = mode + pickerMode = mode setupView() } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Common/InputView/ExpenseTextField.swift b/GoMoney/Common/InputView/ExpenseTextField.swift index b975397..addf2bc 100644 --- a/GoMoney/Common/InputView/ExpenseTextField.swift +++ b/GoMoney/Common/InputView/ExpenseTextField.swift @@ -5,36 +5,36 @@ import UIKit public class ExpenseTextField: UITextField { // MARK: - Open variables - - + open var focusedborderColor = UIColor.action - + open var borderColor = UIColor.lightGray { didSet { layer.borderColor = borderColor.cgColor } } - + open var borderWidth: CGFloat = 1.0 { didSet { layer.borderWidth = borderWidth } } - + open var focusedBorderWidth: CGFloat = 1.2 { didSet { layer.borderWidth = focusedBorderWidth } } - + open var cornerRadius: CGFloat = 8 { didSet { layer.cornerRadius = cornerRadius } } - + private let padding: CGFloat = 16 - + // MARK: Private - + private func initializeTextField() { configureTextField() addObservers() } - + private func configureTextField() { autocorrectionType = .no spellCheckingType = .no @@ -42,45 +42,45 @@ public class ExpenseTextField: UITextField { layer.cornerRadius = cornerRadius layer.borderColor = borderColor.cgColor } - + private func addObservers() { addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) } - + private func activateTextField() { UIView.animate(withDuration: 0.2) { self.layer.borderColor = self.focusedborderColor.cgColor self.layer.borderWidth = self.focusedBorderWidth } } - + private func deactivateTextField() { UIView.animate(withDuration: 0.3) { self.layer.borderColor = self.borderColor.cgColor self.layer.borderWidth = self.borderWidth } } - + @objc private func textFieldDidChange() { UIView.animate(withDuration: 0.2) { self.layer.borderColor = self.focusedborderColor.cgColor } } - + // MARK: UIKit methods - + @discardableResult override open func becomeFirstResponder() -> Bool { activateTextField() return super.becomeFirstResponder() } - + @discardableResult override open func resignFirstResponder() -> Bool { deactivateTextField() return super.resignFirstResponder() } - + override open func textRect(forBounds bounds: CGRect) -> CGRect { let superRect = super.textRect(forBounds: bounds) let rect = CGRect( @@ -91,7 +91,7 @@ public class ExpenseTextField: UITextField { ) return rect } - + override open func editingRect(forBounds bounds: CGRect) -> CGRect { let superRect = super.editingRect(forBounds: bounds) let rect = CGRect( @@ -102,7 +102,7 @@ public class ExpenseTextField: UITextField { ) return rect } - + override public func target(forAction action: Selector, withSender sender: Any?) -> Any? { if action == #selector(UIResponderStandardEditActions.paste(_:)) || @@ -114,12 +114,12 @@ public class ExpenseTextField: UITextField { } // MARK: Init - + override init(frame: CGRect) { super.init(frame: frame) initializeTextField() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initializeTextField() diff --git a/GoMoney/Common/RealmConverter/PathKit.swift b/GoMoney/Common/RealmConverter/PathKit.swift index 3cf558f..f7a3104 100644 --- a/GoMoney/Common/RealmConverter/PathKit.swift +++ b/GoMoney/Common/RealmConverter/PathKit.swift @@ -1,732 +1,717 @@ // PathKit - Effortless path operations #if os(Linux) -import Glibc + import Glibc -let system_glob = Glibc.glob + let system_glob = Glibc.glob #else -import Darwin + import Darwin -let system_glob = Darwin.glob + let system_glob = Darwin.glob #endif import Foundation - /// Represents a filesystem path. public struct Path { - /// The character used by the OS to separate two path elements - public static let separator = "/" - - /// The underlying string representation - internal let path: String - - internal static let fileManager = FileManager.default - - internal let fileSystemInfo: FileSystemInfo - - // MARK: Init - - public init() { - self.init("") - } - - /// Create a Path from a given String - public init(_ path: String) { - self.init(path, fileSystemInfo: DefaultFileSystemInfo()) - } - - internal init(_ path: String, fileSystemInfo: FileSystemInfo) { - self.path = path - self.fileSystemInfo = fileSystemInfo - } - - internal init(fileSystemInfo: FileSystemInfo) { - self.init("", fileSystemInfo: fileSystemInfo) - } - - /// Create a Path by joining multiple path components together - public init(components: S) where S.Iterator.Element == String { - let path: String - if components.isEmpty { - path = "." - } else if components.first == Path.separator && components.count > 1 { - let p = components.joined(separator: Path.separator) - path = String(p[p.index(after: p.startIndex)...]) - } else { - path = components.joined(separator: Path.separator) + /// The character used by the OS to separate two path elements + public static let separator = "/" + + /// The underlying string representation + internal let path: String + + internal static let fileManager = FileManager.default + + internal let fileSystemInfo: FileSystemInfo + + // MARK: Init + + public init() { + self.init("") + } + + /// Create a Path from a given String + public init(_ path: String) { + self.init(path, fileSystemInfo: DefaultFileSystemInfo()) } - self.init(path) - } -} + internal init(_ path: String, fileSystemInfo: FileSystemInfo) { + self.path = path + self.fileSystemInfo = fileSystemInfo + } + + internal init(fileSystemInfo: FileSystemInfo) { + self.init("", fileSystemInfo: fileSystemInfo) + } + + /// Create a Path by joining multiple path components together + public init(components: S) where S.Iterator.Element == String { + let path: String + if components.isEmpty { + path = "." + } else if components.first == Path.separator, components.count > 1 { + let p = components.joined(separator: Path.separator) + path = String(p[p.index(after: p.startIndex)...]) + } else { + path = components.joined(separator: Path.separator) + } + self.init(path) + } +} // MARK: StringLiteralConvertible -extension Path : ExpressibleByStringLiteral { - public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType - public typealias UnicodeScalarLiteralType = StringLiteralType +extension Path: ExpressibleByStringLiteral { + public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType + public typealias UnicodeScalarLiteralType = StringLiteralType - public init(extendedGraphemeClusterLiteral path: StringLiteralType) { - self.init(stringLiteral: path) - } + public init(extendedGraphemeClusterLiteral path: StringLiteralType) { + self.init(stringLiteral: path) + } - public init(unicodeScalarLiteral path: StringLiteralType) { - self.init(stringLiteral: path) - } + public init(unicodeScalarLiteral path: StringLiteralType) { + self.init(stringLiteral: path) + } - public init(stringLiteral value: StringLiteralType) { - self.init(value) - } + public init(stringLiteral value: StringLiteralType) { + self.init(value) + } } - // MARK: CustomStringConvertible -extension Path : CustomStringConvertible { - public var description: String { - return self.path - } +extension Path: CustomStringConvertible { + public var description: String { + return path + } } - // MARK: Conversion -extension Path { - public var string: String { - return self.path - } +public extension Path { + var string: String { + return path + } - public var url: URL { - return URL(fileURLWithPath: path) - } + var url: URL { + return URL(fileURLWithPath: path) + } } - // MARK: Hashable -extension Path : Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(self.path.hashValue) - } +extension Path: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(path.hashValue) + } } - // MARK: Path Info -extension Path { - /// Test whether a path is absolute. - /// - /// - Returns: `true` iff the path begins with a slash - /// - public var isAbsolute: Bool { - return path.hasPrefix(Path.separator) - } - - /// Test whether a path is relative. - /// - /// - Returns: `true` iff a path is relative (not absolute) - /// - public var isRelative: Bool { - return !isAbsolute - } - - /// Concatenates relative paths to the current directory and derives the normalized path - /// - /// - Returns: the absolute path in the actual filesystem - /// - public func absolute() -> Path { - if isAbsolute { - return normalize() - } - - let expandedPath = Path(NSString(string: self.path).expandingTildeInPath) - if expandedPath.isAbsolute { - return expandedPath.normalize() - } - - return (Path.current + self).normalize() - } - - /// Normalizes the path, this cleans up redundant ".." and ".", double slashes - /// and resolves "~". - /// - /// - Returns: a new path made by removing extraneous path components from the underlying String - /// representation. - /// - public func normalize() -> Path { - return Path(NSString(string: self.path).standardizingPath) - } - - /// De-normalizes the path, by replacing the current user home directory with "~". - /// - /// - Returns: a new path made by removing extraneous path components from the underlying String - /// representation. - /// - public func abbreviate() -> Path { - let rangeOptions: String.CompareOptions = fileSystemInfo.isFSCaseSensitiveAt(path: self) ? - [.anchored] : [.anchored, .caseInsensitive] - let home = Path.home.string - guard let homeRange = self.path.range(of: home, options: rangeOptions) else { return self } - let withoutHome = Path(self.path.replacingCharacters(in: homeRange, with: "")) - - if withoutHome.path.isEmpty || withoutHome.path == Path.separator { - return Path("~") - } else if withoutHome.isAbsolute { - return Path("~" + withoutHome.path) - } else { - return Path("~") + withoutHome.path - } - } - - /// Returns the path of the item pointed to by a symbolic link. - /// - /// - Returns: the path of directory or file to which the symbolic link refers - /// - public func symlinkDestination() throws -> Path { - let symlinkDestination = try Path.fileManager.destinationOfSymbolicLink(atPath: path) - let symlinkPath = Path(symlinkDestination) - if symlinkPath.isRelative { - return self + ".." + symlinkPath - } else { - return symlinkPath +public extension Path { + /// Test whether a path is absolute. + /// + /// - Returns: `true` iff the path begins with a slash + /// + var isAbsolute: Bool { + return path.hasPrefix(Path.separator) + } + + /// Test whether a path is relative. + /// + /// - Returns: `true` iff a path is relative (not absolute) + /// + var isRelative: Bool { + return !isAbsolute + } + + /// Concatenates relative paths to the current directory and derives the normalized path + /// + /// - Returns: the absolute path in the actual filesystem + /// + func absolute() -> Path { + if isAbsolute { + return normalize() + } + + let expandedPath = Path(NSString(string: path).expandingTildeInPath) + if expandedPath.isAbsolute { + return expandedPath.normalize() + } + + return (Path.current + self).normalize() + } + + /// Normalizes the path, this cleans up redundant ".." and ".", double slashes + /// and resolves "~". + /// + /// - Returns: a new path made by removing extraneous path components from the underlying String + /// representation. + /// + func normalize() -> Path { + return Path(NSString(string: path).standardizingPath) + } + + /// De-normalizes the path, by replacing the current user home directory with "~". + /// + /// - Returns: a new path made by removing extraneous path components from the underlying String + /// representation. + /// + func abbreviate() -> Path { + let rangeOptions: String.CompareOptions = fileSystemInfo.isFSCaseSensitiveAt(path: self) ? + [.anchored] : [.anchored, .caseInsensitive] + let home = Path.home.string + guard let homeRange = path.range(of: home, options: rangeOptions) else { return self } + let withoutHome = Path(path.replacingCharacters(in: homeRange, with: "")) + + if withoutHome.path.isEmpty || withoutHome.path == Path.separator { + return Path("~") + } else if withoutHome.isAbsolute { + return Path("~" + withoutHome.path) + } else { + return Path("~") + withoutHome.path + } + } + + /// Returns the path of the item pointed to by a symbolic link. + /// + /// - Returns: the path of directory or file to which the symbolic link refers + /// + func symlinkDestination() throws -> Path { + let symlinkDestination = try Path.fileManager.destinationOfSymbolicLink(atPath: path) + let symlinkPath = Path(symlinkDestination) + if symlinkPath.isRelative { + return self + ".." + symlinkPath + } else { + return symlinkPath + } } - } } internal protocol FileSystemInfo { - func isFSCaseSensitiveAt(path: Path) -> Bool + func isFSCaseSensitiveAt(path: Path) -> Bool } internal struct DefaultFileSystemInfo: FileSystemInfo { - func isFSCaseSensitiveAt(path: Path) -> Bool { - #if os(Linux) - // URL resourceValues(forKeys:) is not supported on non-darwin platforms... - // But we can (fairly?) safely assume for now that the Linux FS is case sensitive. - // TODO: refactor when/if resourceValues is available, or look into using something - // like stat or pathconf to determine if the mountpoint is case sensitive. - return true - #else - var isCaseSensitive = false - // Calling resourceValues will fail if the path does not exist on the filesystem, which - // makes sense, but means we can only guarantee the return value is correct if the - // path actually exists. - if let resourceValues = try? path.url.resourceValues(forKeys: [.volumeSupportsCaseSensitiveNamesKey]) { - isCaseSensitive = resourceValues.volumeSupportsCaseSensitiveNames ?? isCaseSensitive - } - return isCaseSensitive - #endif - } + func isFSCaseSensitiveAt(path: Path) -> Bool { + #if os(Linux) + // URL resourceValues(forKeys:) is not supported on non-darwin platforms... + // But we can (fairly?) safely assume for now that the Linux FS is case sensitive. + // TODO: refactor when/if resourceValues is available, or look into using something + // like stat or pathconf to determine if the mountpoint is case sensitive. + return true + #else + var isCaseSensitive = false + // Calling resourceValues will fail if the path does not exist on the filesystem, which + // makes sense, but means we can only guarantee the return value is correct if the + // path actually exists. + if let resourceValues = try? path.url.resourceValues(forKeys: [.volumeSupportsCaseSensitiveNamesKey]) { + isCaseSensitive = resourceValues.volumeSupportsCaseSensitiveNames ?? isCaseSensitive + } + return isCaseSensitive + #endif + } } // MARK: Path Components -extension Path { - /// The last path component - /// - /// - Returns: the last path component - /// - public var lastComponent: String { - return NSString(string: path).lastPathComponent - } - - /// The last path component without file extension - /// - /// - Note: This returns "." for ".." on Linux, and ".." on Apple platforms. - /// - /// - Returns: the last path component without file extension - /// - public var lastComponentWithoutExtension: String { - return NSString(string: lastComponent).deletingPathExtension - } - - /// Splits the string representation on the directory separator. - /// Absolute paths remain the leading slash as first component. - /// - /// - Returns: all path components - /// - public var components: [String] { - return NSString(string: path).pathComponents - } - - /// The file extension behind the last dot of the last component. - /// - /// - Returns: the file extension - /// - public var `extension`: String? { - let pathExtension = NSString(string: path).pathExtension - if pathExtension.isEmpty { - return nil - } - - return pathExtension - } -} +public extension Path { + /// The last path component + /// + /// - Returns: the last path component + /// + var lastComponent: String { + return NSString(string: path).lastPathComponent + } + + /// The last path component without file extension + /// + /// - Note: This returns "." for ".." on Linux, and ".." on Apple platforms. + /// + /// - Returns: the last path component without file extension + /// + var lastComponentWithoutExtension: String { + return NSString(string: lastComponent).deletingPathExtension + } + /// Splits the string representation on the directory separator. + /// Absolute paths remain the leading slash as first component. + /// + /// - Returns: all path components + /// + var components: [String] { + return NSString(string: path).pathComponents + } -// MARK: File Info + /// The file extension behind the last dot of the last component. + /// + /// - Returns: the file extension + /// + var `extension`: String? { + let pathExtension = NSString(string: path).pathExtension + if pathExtension.isEmpty { + return nil + } -extension Path { - /// Test whether a file or directory exists at a specified path - /// - /// - Returns: `false` iff the path doesn't exist on disk or its existence could not be - /// determined - /// - public var exists: Bool { - return Path.fileManager.fileExists(atPath: self.path) - } - - /// Test whether a path is a directory. - /// - /// - Returns: `true` if the path is a directory or a symbolic link that points to a directory; - /// `false` if the path is not a directory or the path doesn't exist on disk or its existence - /// could not be determined - /// - public var isDirectory: Bool { - var directory = ObjCBool(false) - guard Path.fileManager.fileExists(atPath: normalize().path, isDirectory: &directory) else { - return false - } - return directory.boolValue - } - - /// Test whether a path is a regular file. - /// - /// - Returns: `true` if the path is neither a directory nor a symbolic link that points to a - /// directory; `false` if the path is a directory or a symbolic link that points to a - /// directory or the path doesn't exist on disk or its existence - /// could not be determined - /// - public var isFile: Bool { - var directory = ObjCBool(false) - guard Path.fileManager.fileExists(atPath: normalize().path, isDirectory: &directory) else { - return false - } - return !directory.boolValue - } - - /// Test whether a path is a symbolic link. - /// - /// - Returns: `true` if the path is a symbolic link; `false` if the path doesn't exist on disk - /// or its existence could not be determined - /// - public var isSymlink: Bool { - do { - let _ = try Path.fileManager.destinationOfSymbolicLink(atPath: path) - return true - } catch { - return false - } - } - - /// Test whether a path is readable - /// - /// - Returns: `true` if the current process has read privileges for the file at path; - /// otherwise `false` if the process does not have read privileges or the existence of the - /// file could not be determined. - /// - public var isReadable: Bool { - return Path.fileManager.isReadableFile(atPath: self.path) - } - - /// Test whether a path is writeable - /// - /// - Returns: `true` if the current process has write privileges for the file at path; - /// otherwise `false` if the process does not have write privileges or the existence of the - /// file could not be determined. - /// - public var isWritable: Bool { - return Path.fileManager.isWritableFile(atPath: self.path) - } - - /// Test whether a path is executable - /// - /// - Returns: `true` if the current process has execute privileges for the file at path; - /// otherwise `false` if the process does not have execute privileges or the existence of the - /// file could not be determined. - /// - public var isExecutable: Bool { - return Path.fileManager.isExecutableFile(atPath: self.path) - } - - /// Test whether a path is deletable - /// - /// - Returns: `true` if the current process has delete privileges for the file at path; - /// otherwise `false` if the process does not have delete privileges or the existence of the - /// file could not be determined. - /// - public var isDeletable: Bool { - return Path.fileManager.isDeletableFile(atPath: self.path) - } + return pathExtension + } } +// MARK: File Info -// MARK: File Manipulation +public extension Path { + /// Test whether a file or directory exists at a specified path + /// + /// - Returns: `false` iff the path doesn't exist on disk or its existence could not be + /// determined + /// + var exists: Bool { + return Path.fileManager.fileExists(atPath: path) + } -extension Path { - /// Create the directory. - /// - /// - Note: This method fails if any of the intermediate parent directories does not exist. - /// This method also fails if any of the intermediate path elements corresponds to a file and - /// not a directory. - /// - public func mkdir() throws -> () { - try Path.fileManager.createDirectory(atPath: self.path, withIntermediateDirectories: false, attributes: nil) - } - - /// Create the directory and any intermediate parent directories that do not exist. - /// - /// - Note: This method fails if any of the intermediate path elements corresponds to a file and - /// not a directory. - /// - public func mkpath() throws -> () { - try Path.fileManager.createDirectory(atPath: self.path, withIntermediateDirectories: true, attributes: nil) - } - - /// Delete the file or directory. - /// - /// - Note: If the path specifies a directory, the contents of that directory are recursively - /// removed. - /// - public func delete() throws -> () { - try Path.fileManager.removeItem(atPath: self.path) - } - - /// Move the file or directory to a new location synchronously. - /// - /// - Parameter destination: The new path. This path must include the name of the file or - /// directory in its new location. - /// - public func move(_ destination: Path) throws -> () { - try Path.fileManager.moveItem(atPath: self.path, toPath: destination.path) - } - - /// Copy the file or directory to a new location synchronously. - /// - /// - Parameter destination: The new path. This path must include the name of the file or - /// directory in its new location. - /// - public func copy(_ destination: Path) throws -> () { - try Path.fileManager.copyItem(atPath: self.path, toPath: destination.path) - } - - /// Creates a hard link at a new destination. - /// - /// - Parameter destination: The location where the link will be created. - /// - public func link(_ destination: Path) throws -> () { - try Path.fileManager.linkItem(atPath: self.path, toPath: destination.path) - } - - /// Creates a symbolic link at a new destination. - /// - /// - Parameter destintation: The location where the link will be created. - /// - public func symlink(_ destination: Path) throws -> () { - try Path.fileManager.createSymbolicLink(atPath: self.path, withDestinationPath: destination.path) - } -} + /// Test whether a path is a directory. + /// + /// - Returns: `true` if the path is a directory or a symbolic link that points to a directory; + /// `false` if the path is not a directory or the path doesn't exist on disk or its existence + /// could not be determined + /// + var isDirectory: Bool { + var directory = ObjCBool(false) + guard Path.fileManager.fileExists(atPath: normalize().path, isDirectory: &directory) else { + return false + } + return directory.boolValue + } + /// Test whether a path is a regular file. + /// + /// - Returns: `true` if the path is neither a directory nor a symbolic link that points to a + /// directory; `false` if the path is a directory or a symbolic link that points to a + /// directory or the path doesn't exist on disk or its existence + /// could not be determined + /// + var isFile: Bool { + var directory = ObjCBool(false) + guard Path.fileManager.fileExists(atPath: normalize().path, isDirectory: &directory) else { + return false + } + return !directory.boolValue + } -// MARK: Current Directory + /// Test whether a path is a symbolic link. + /// + /// - Returns: `true` if the path is a symbolic link; `false` if the path doesn't exist on disk + /// or its existence could not be determined + /// + var isSymlink: Bool { + do { + let _ = try Path.fileManager.destinationOfSymbolicLink(atPath: path) + return true + } catch { + return false + } + } + + /// Test whether a path is readable + /// + /// - Returns: `true` if the current process has read privileges for the file at path; + /// otherwise `false` if the process does not have read privileges or the existence of the + /// file could not be determined. + /// + var isReadable: Bool { + return Path.fileManager.isReadableFile(atPath: path) + } + + /// Test whether a path is writeable + /// + /// - Returns: `true` if the current process has write privileges for the file at path; + /// otherwise `false` if the process does not have write privileges or the existence of the + /// file could not be determined. + /// + var isWritable: Bool { + return Path.fileManager.isWritableFile(atPath: path) + } -extension Path { - /// The current working directory of the process - /// - /// - Returns: the current working directory of the process - /// - public static var current: Path { - get { - return self.init(Path.fileManager.currentDirectoryPath) - } - set { - _ = Path.fileManager.changeCurrentDirectoryPath(newValue.description) - } - } - - /// Changes the current working directory of the process to the path during the execution of the - /// given block. - /// - /// - Note: The original working directory is restored when the block returns or throws. - /// - Parameter closure: A closure to be executed while the current directory is configured to - /// the path. - /// - public func chdir(closure: () throws -> ()) rethrows { - let previous = Path.current - Path.current = self - defer { Path.current = previous } - try closure() - } + /// Test whether a path is executable + /// + /// - Returns: `true` if the current process has execute privileges for the file at path; + /// otherwise `false` if the process does not have execute privileges or the existence of the + /// file could not be determined. + /// + var isExecutable: Bool { + return Path.fileManager.isExecutableFile(atPath: path) + } + + /// Test whether a path is deletable + /// + /// - Returns: `true` if the current process has delete privileges for the file at path; + /// otherwise `false` if the process does not have delete privileges or the existence of the + /// file could not be determined. + /// + var isDeletable: Bool { + return Path.fileManager.isDeletableFile(atPath: path) + } } +// MARK: File Manipulation -// MARK: Temporary +public extension Path { + /// Create the directory. + /// + /// - Note: This method fails if any of the intermediate parent directories does not exist. + /// This method also fails if any of the intermediate path elements corresponds to a file and + /// not a directory. + /// + func mkdir() throws { + try Path.fileManager.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil) + } + + /// Create the directory and any intermediate parent directories that do not exist. + /// + /// - Note: This method fails if any of the intermediate path elements corresponds to a file and + /// not a directory. + /// + func mkpath() throws { + try Path.fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + } + + /// Delete the file or directory. + /// + /// - Note: If the path specifies a directory, the contents of that directory are recursively + /// removed. + /// + func delete() throws { + try Path.fileManager.removeItem(atPath: path) + } -extension Path { - /// - Returns: the path to either the user’s or application’s home directory, - /// depending on the platform. - /// - public static var home: Path { - return Path(NSHomeDirectory()) - } - - /// - Returns: the path of the temporary directory for the current user. - /// - public static var temporary: Path { - return Path(NSTemporaryDirectory()) - } - - /// - Returns: the path of a temporary directory unique for the process. - /// - Note: Based on `NSProcessInfo.globallyUniqueString`. - /// - public static func processUniqueTemporary() throws -> Path { - let path = temporary + ProcessInfo.processInfo.globallyUniqueString - if !path.exists { - try path.mkdir() - } - return path - } - - /// - Returns: the path of a temporary directory unique for each call. - /// - Note: Based on `NSUUID`. - /// - public static func uniqueTemporary() throws -> Path { - let path = try processUniqueTemporary() + UUID().uuidString - try path.mkdir() - return path - } + /// Move the file or directory to a new location synchronously. + /// + /// - Parameter destination: The new path. This path must include the name of the file or + /// directory in its new location. + /// + func move(_ destination: Path) throws { + try Path.fileManager.moveItem(atPath: path, toPath: destination.path) + } + + /// Copy the file or directory to a new location synchronously. + /// + /// - Parameter destination: The new path. This path must include the name of the file or + /// directory in its new location. + /// + func copy(_ destination: Path) throws { + try Path.fileManager.copyItem(atPath: path, toPath: destination.path) + } + + /// Creates a hard link at a new destination. + /// + /// - Parameter destination: The location where the link will be created. + /// + func link(_ destination: Path) throws { + try Path.fileManager.linkItem(atPath: path, toPath: destination.path) + } + + /// Creates a symbolic link at a new destination. + /// + /// - Parameter destintation: The location where the link will be created. + /// + func symlink(_ destination: Path) throws { + try Path.fileManager.createSymbolicLink(atPath: path, withDestinationPath: destination.path) + } } +// MARK: Current Directory -// MARK: Contents +public extension Path { + /// The current working directory of the process + /// + /// - Returns: the current working directory of the process + /// + static var current: Path { + get { + return self.init(Path.fileManager.currentDirectoryPath) + } + set { + _ = Path.fileManager.changeCurrentDirectoryPath(newValue.description) + } + } -extension Path { - /// Reads the file. - /// - /// - Returns: the contents of the file at the specified path. - /// - public func read() throws -> Data { - return try Data(contentsOf: self.url, options: NSData.ReadingOptions(rawValue: 0)) - } - - /// Reads the file contents and encoded its bytes to string applying the given encoding. - /// - /// - Parameter encoding: the encoding which should be used to decode the data. - /// (by default: `NSUTF8StringEncoding`) - /// - /// - Returns: the contents of the file at the specified path as string. - /// - public func read(_ encoding: String.Encoding = String.Encoding.utf8) throws -> String { - return try NSString(contentsOfFile: path, encoding: encoding.rawValue).substring(from: 0) as String - } - - /// Write a file. - /// - /// - Note: Works atomically: the data is written to a backup file, and then — assuming no - /// errors occur — the backup file is renamed to the name specified by path. - /// - /// - Parameter data: the contents to write to file. - /// - public func write(_ data: Data) throws { - try data.write(to: normalize().url, options: .atomic) - } - - /// Reads the file. - /// - /// - Note: Works atomically: the data is written to a backup file, and then — assuming no - /// errors occur — the backup file is renamed to the name specified by path. - /// - /// - Parameter string: the string to write to file. - /// - /// - Parameter encoding: the encoding which should be used to represent the string as bytes. - /// (by default: `NSUTF8StringEncoding`) - /// - /// - Returns: the contents of the file at the specified path as string. - /// - public func write(_ string: String, encoding: String.Encoding = String.Encoding.utf8) throws { - try string.write(toFile: normalize().path, atomically: true, encoding: encoding) - } + /// Changes the current working directory of the process to the path during the execution of the + /// given block. + /// + /// - Note: The original working directory is restored when the block returns or throws. + /// - Parameter closure: A closure to be executed while the current directory is configured to + /// the path. + /// + func chdir(closure: () throws -> Void) rethrows { + let previous = Path.current + Path.current = self + defer { Path.current = previous } + try closure() + } } +// MARK: Temporary -// MARK: Traversing +public extension Path { + /// - Returns: the path to either the user’s or application’s home directory, + /// depending on the platform. + /// + static var home: Path { + return Path(NSHomeDirectory()) + } + + /// - Returns: the path of the temporary directory for the current user. + /// + static var temporary: Path { + return Path(NSTemporaryDirectory()) + } + + /// - Returns: the path of a temporary directory unique for the process. + /// - Note: Based on `NSProcessInfo.globallyUniqueString`. + /// + static func processUniqueTemporary() throws -> Path { + let path = temporary + ProcessInfo.processInfo.globallyUniqueString + if !path.exists { + try path.mkdir() + } + return path + } -extension Path { - /// Get the parent directory - /// - /// - Returns: the normalized path of the parent directory - /// - public func parent() -> Path { - return self + ".." - } - - /// Performs a shallow enumeration in a directory - /// - /// - Returns: paths to all files, directories and symbolic links contained in the directory - /// - public func children() throws -> [Path] { - return try Path.fileManager.contentsOfDirectory(atPath: path).map { - self + Path($0) - } - } - - /// Performs a deep enumeration in a directory - /// - /// - Returns: paths to all files, directories and symbolic links contained in the directory or - /// any subdirectory. - /// - public func recursiveChildren() throws -> [Path] { - return try Path.fileManager.subpathsOfDirectory(atPath: path).map { - self + Path($0) - } - } + /// - Returns: the path of a temporary directory unique for each call. + /// - Note: Based on `NSUUID`. + /// + static func uniqueTemporary() throws -> Path { + let path = try processUniqueTemporary() + UUID().uuidString + try path.mkdir() + return path + } } +// MARK: Contents -// MARK: Globbing +public extension Path { + /// Reads the file. + /// + /// - Returns: the contents of the file at the specified path. + /// + func read() throws -> Data { + return try Data(contentsOf: url, options: NSData.ReadingOptions(rawValue: 0)) + } -extension Path { - public static func glob(_ pattern: String) -> [Path] { - var gt = glob_t() - guard let cPattern = strdup(pattern) else { - fatalError("strdup returned null: Likely out of memory") + /// Reads the file contents and encoded its bytes to string applying the given encoding. + /// + /// - Parameter encoding: the encoding which should be used to decode the data. + /// (by default: `NSUTF8StringEncoding`) + /// + /// - Returns: the contents of the file at the specified path as string. + /// + func read(_ encoding: String.Encoding = String.Encoding.utf8) throws -> String { + return try NSString(contentsOfFile: path, encoding: encoding.rawValue).substring(from: 0) as String } - defer { - globfree(>) - free(cPattern) + + /// Write a file. + /// + /// - Note: Works atomically: the data is written to a backup file, and then — assuming no + /// errors occur — the backup file is renamed to the name specified by path. + /// + /// - Parameter data: the contents to write to file. + /// + func write(_ data: Data) throws { + try data.write(to: normalize().url, options: .atomic) } - let flags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK - if system_glob(cPattern, flags, nil, >) == 0 { -#if os(Linux) - let matchc = gt.gl_pathc -#else - let matchc = gt.gl_matchc -#endif - return (0.. Path { + return self + ".." + } + + /// Performs a shallow enumeration in a directory + /// + /// - Returns: paths to all files, directories and symbolic links contained in the directory + /// + func children() throws -> [Path] { + return try Path.fileManager.contentsOfDirectory(atPath: path).map { + self + Path($0) } + } - return nil - } + /// Performs a deep enumeration in a directory + /// + /// - Returns: paths to all files, directories and symbolic links contained in the directory or + /// any subdirectory. + /// + func recursiveChildren() throws -> [Path] { + return try Path.fileManager.subpathsOfDirectory(atPath: path).map { + self + Path($0) + } } +} - // GLOB_NOMATCH - return [] - } +// MARK: Globbing - public func glob(_ pattern: String) -> [Path] { - return Path.glob((self + pattern).description) - } +public extension Path { + static func glob(_ pattern: String) -> [Path] { + var gt = glob_t() + guard let cPattern = strdup(pattern) else { + fatalError("strdup returned null: Likely out of memory") + } + defer { + globfree(>) + free(cPattern) + } + + let flags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK + if system_glob(cPattern, flags, nil, >) == 0 { + #if os(Linux) + let matchc = gt.gl_pathc + #else + let matchc = gt.gl_matchc + #endif + return (0 ..< Int(matchc)).compactMap { index in + if let path = String(validatingUTF8: gt.gl_pathv[index]!) { + return Path(path) + } + + return nil + } + } + + // GLOB_NOMATCH + return [] + } - public func match(_ pattern: String) -> Bool { - guard let cPattern = strdup(pattern), - let cPath = strdup(path) else { - fatalError("strdup returned null: Likely out of memory") + func glob(_ pattern: String) -> [Path] { + return Path.glob((self + pattern).description) } - defer { - free(cPattern) - free(cPath) + + func match(_ pattern: String) -> Bool { + guard let cPattern = strdup(pattern), + let cPath = strdup(path) + else { + fatalError("strdup returned null: Likely out of memory") + } + defer { + free(cPattern) + free(cPath) + } + return fnmatch(cPattern, cPath, 0) == 0 } - return fnmatch(cPattern, cPath, 0) == 0 - } } - // MARK: SequenceType -extension Path : Sequence { - public struct DirectoryEnumerationOptions : OptionSet { - public let rawValue: UInt - public init(rawValue: UInt) { - self.rawValue = rawValue +extension Path: Sequence { + public struct DirectoryEnumerationOptions: OptionSet { + public let rawValue: UInt + public init(rawValue: UInt) { + self.rawValue = rawValue + } + + public static var skipsSubdirectoryDescendants = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants.rawValue) + public static var skipsPackageDescendants = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsPackageDescendants.rawValue) + public static var skipsHiddenFiles = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles.rawValue) } - public static var skipsSubdirectoryDescendants = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants.rawValue) - public static var skipsPackageDescendants = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsPackageDescendants.rawValue) - public static var skipsHiddenFiles = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles.rawValue) - } + /// Represents a path sequence with specific enumeration options + public struct PathSequence: Sequence { + private var path: Path + private var options: DirectoryEnumerationOptions + init(path: Path, options: DirectoryEnumerationOptions) { + self.path = path + self.options = options + } - /// Represents a path sequence with specific enumeration options - public struct PathSequence : Sequence { - private var path: Path - private var options: DirectoryEnumerationOptions - init(path: Path, options: DirectoryEnumerationOptions) { - self.path = path - self.options = options + public func makeIterator() -> DirectoryEnumerator { + return DirectoryEnumerator(path: path, options: options) + } } + /// Enumerates the contents of a directory, returning the paths of all files and directories + /// contained within that directory. These paths are relative to the directory. + public struct DirectoryEnumerator: IteratorProtocol { + public typealias Element = Path + + let path: Path + let directoryEnumerator: FileManager.DirectoryEnumerator? + + init(path: Path, options mask: DirectoryEnumerationOptions = []) { + let options = FileManager.DirectoryEnumerationOptions(rawValue: mask.rawValue) + self.path = path + directoryEnumerator = Path.fileManager.enumerator(at: path.url, includingPropertiesForKeys: nil, options: options) + } + + public func next() -> Path? { + let next = directoryEnumerator?.nextObject() + + if let next = next as? URL { + return Path(next.path) + } + return nil + } + + /// Skip recursion into the most recently obtained subdirectory. + public func skipDescendants() { + directoryEnumerator?.skipDescendants() + } + } + + /// Perform a deep enumeration of a directory. + /// + /// - Returns: a directory enumerator that can be used to perform a deep enumeration of the + /// directory. + /// public func makeIterator() -> DirectoryEnumerator { - return DirectoryEnumerator(path: path, options: options) - } - } - - /// Enumerates the contents of a directory, returning the paths of all files and directories - /// contained within that directory. These paths are relative to the directory. - public struct DirectoryEnumerator : IteratorProtocol { - public typealias Element = Path - - let path: Path - let directoryEnumerator: FileManager.DirectoryEnumerator? - - init(path: Path, options mask: DirectoryEnumerationOptions = []) { - let options = FileManager.DirectoryEnumerationOptions(rawValue: mask.rawValue) - self.path = path - self.directoryEnumerator = Path.fileManager.enumerator(at: path.url, includingPropertiesForKeys: nil, options: options) - } - - public func next() -> Path? { - let next = directoryEnumerator?.nextObject() - - if let next = next as? URL { - return Path(next.path) - } - return nil - } - - /// Skip recursion into the most recently obtained subdirectory. - public func skipDescendants() { - directoryEnumerator?.skipDescendants() - } - } - - /// Perform a deep enumeration of a directory. - /// - /// - Returns: a directory enumerator that can be used to perform a deep enumeration of the - /// directory. - /// - public func makeIterator() -> DirectoryEnumerator { - return DirectoryEnumerator(path: self) - } - - /// Perform a deep enumeration of a directory. - /// - /// - Parameter options: FileManager directory enumerator options. - /// - /// - Returns: a path sequence that can be used to perform a deep enumeration of the - /// directory. - /// - public func iterateChildren(options: DirectoryEnumerationOptions = []) -> PathSequence { - return PathSequence(path: self, options: options) - } -} + return DirectoryEnumerator(path: self) + } + /// Perform a deep enumeration of a directory. + /// + /// - Parameter options: FileManager directory enumerator options. + /// + /// - Returns: a path sequence that can be used to perform a deep enumeration of the + /// directory. + /// + public func iterateChildren(options: DirectoryEnumerationOptions = []) -> PathSequence { + return PathSequence(path: self, options: options) + } +} // MARK: Equatable -extension Path : Equatable {} +extension Path: Equatable {} /// Determines if two paths are identical /// /// - Note: The comparison is string-based. Be aware that two different paths (foo.txt and /// ./foo.txt) can refer to the same file. /// -public func ==(lhs: Path, rhs: Path) -> Bool { - return lhs.path == rhs.path +public func == (lhs: Path, rhs: Path) -> Bool { + return lhs.path == rhs.path } - // MARK: Pattern Matching /// Implements pattern-matching for paths. @@ -735,78 +720,76 @@ public func ==(lhs: Path, rhs: Path) -> Bool { /// - the paths are equal (based on `Path`'s `Equatable` implementation) /// - the paths can be normalized to equal Paths. /// -public func ~=(lhs: Path, rhs: Path) -> Bool { - return lhs == rhs - || lhs.normalize() == rhs.normalize() +public func ~= (lhs: Path, rhs: Path) -> Bool { + return lhs == rhs + || lhs.normalize() == rhs.normalize() } - // MARK: Comparable -extension Path : Comparable {} +extension Path: Comparable {} /// Defines a strict total order over Paths based on their underlying string representation. -public func <(lhs: Path, rhs: Path) -> Bool { - return lhs.path < rhs.path +public func < (lhs: Path, rhs: Path) -> Bool { + return lhs.path < rhs.path } - // MARK: Operators /// Appends a Path fragment to another Path to produce a new Path -public func +(lhs: Path, rhs: Path) -> Path { - return lhs.path + rhs.path +public func + (lhs: Path, rhs: Path) -> Path { + return lhs.path + rhs.path } /// Appends a String fragment to another Path to produce a new Path -public func +(lhs: Path, rhs: String) -> Path { - return lhs.path + rhs +public func + (lhs: Path, rhs: String) -> Path { + return lhs.path + rhs } /// Appends a String fragment to another String to produce a new Path -internal func +(lhs: String, rhs: String) -> Path { - if rhs.hasPrefix(Path.separator) { - // Absolute paths replace relative paths - return Path(rhs) - } else { - var lSlice = NSString(string: lhs).pathComponents.fullSlice - var rSlice = NSString(string: rhs).pathComponents.fullSlice - - // Get rid of trailing "/" at the left side - if lSlice.count > 1 && lSlice.last == Path.separator { - lSlice.removeLast() - } - - // Advance after the first relevant "." - lSlice = lSlice.filter { $0 != "." }.fullSlice - rSlice = rSlice.filter { $0 != "." }.fullSlice - - // Eats up trailing components of the left and leading ".." of the right side - while lSlice.last != ".." && !lSlice.isEmpty && rSlice.first == ".." { - if lSlice.count > 1 || lSlice.first != Path.separator { - // A leading "/" is never popped - lSlice.removeLast() - } - if !rSlice.isEmpty { - rSlice.removeFirst() - } - - switch (lSlice.isEmpty, rSlice.isEmpty) { - case (true, _): - break - case (_, true): - break - default: - continue - } - } - - return Path(components: lSlice + rSlice) - } +internal func + (lhs: String, rhs: String) -> Path { + if rhs.hasPrefix(Path.separator) { + // Absolute paths replace relative paths + return Path(rhs) + } else { + var lSlice = NSString(string: lhs).pathComponents.fullSlice + var rSlice = NSString(string: rhs).pathComponents.fullSlice + + // Get rid of trailing "/" at the left side + if lSlice.count > 1, lSlice.last == Path.separator { + lSlice.removeLast() + } + + // Advance after the first relevant "." + lSlice = lSlice.filter { $0 != "." }.fullSlice + rSlice = rSlice.filter { $0 != "." }.fullSlice + + // Eats up trailing components of the left and leading ".." of the right side + while lSlice.last != "..", !lSlice.isEmpty, rSlice.first == ".." { + if lSlice.count > 1 || lSlice.first != Path.separator { + // A leading "/" is never popped + lSlice.removeLast() + } + if !rSlice.isEmpty { + rSlice.removeFirst() + } + + switch (lSlice.isEmpty, rSlice.isEmpty) { + case (true, _): + break + case (_, true): + break + default: + continue + } + } + + return Path(components: lSlice + rSlice) + } } extension Array { - var fullSlice: ArraySlice { - return self[self.indices.suffix(from: 0)] - } + var fullSlice: ArraySlice { + return self[indices.suffix(from: 0)] + } } diff --git a/GoMoney/Common/RealmConverter/RealmConverter.swift b/GoMoney/Common/RealmConverter/RealmConverter.swift index 7cf3067..221b6d2 100644 --- a/GoMoney/Common/RealmConverter/RealmConverter.swift +++ b/GoMoney/Common/RealmConverter/RealmConverter.swift @@ -3,34 +3,34 @@ import RealmSwift class RealmConverter { let realm = try! Realm() - + let delimiter = "," let escapeQuotes = "\"" - + func exportCSV() throws -> URL { let fileName = "Transactions-\(DateFormatter.ddmmyyyy.string(from: Date())).csv" - + let filePath = Path(getDocumentFilePath(fileName: fileName).relativePath) if filePath.exists { try filePath.delete() } - + let objectSchema = realm.schema.objectSchema.first( where: { $0.className == String(describing: Expense.self) })! - + // Build the initial row of property names and write to disk try filePath.write( objectSchema.properties.map { $0.name }.joined(separator: delimiter) + "\n" ) - + // Write the remaining objects let fileHandle = FileHandle(forWritingAtPath: String(describing: filePath)) fileHandle?.seekToEndOfFile() - + let objects = realm.objects(Expense.self) - + // Loop through each object in the table for object in objects { let row = objectSchema.properties.map { property in @@ -41,59 +41,59 @@ class RealmConverter { fileHandle?.write(rowString.data(using: .utf8)!) } fileHandle?.closeFile() - + return filePath.url } - + func exportTxt() throws -> URL { let fileName = "Transactions-\(DateFormatter.ddmmyyyy.string(from: Date())).txt" - + let filePath = Path(getDocumentFilePath(fileName: fileName).relativePath) if filePath.exists { try filePath.delete() } - + try filePath.write("") let fileHandle = FileHandle(forWritingAtPath: String(describing: filePath)) fileHandle?.seekToEndOfFile() - + let objects = realm.objects(Expense.self) - + for object in objects { let rowString = "\(object)\n" fileHandle?.write(rowString.data(using: .utf8)!) } fileHandle?.closeFile() - + return filePath.url } - + func exportRealm() throws -> URL { return Realm.Configuration.defaultConfiguration.fileURL! } - + func exportJSON() throws -> URL { var arrayJson: [Any] = [] - + let fileName = "Transactions-\(DateFormatter.ddmmyyyy.string(from: Date())).json" - + let filePath = Path(getDocumentFilePath(fileName: fileName).relativePath) if filePath.exists { try filePath.delete() } - + let objectSchema = realm.schema.objectSchema.first( where: { $0.className == String(describing: Expense.self) })! - + try filePath.write("") - + let fileHandle = FileHandle(forWritingAtPath: String(describing: filePath)) fileHandle?.seekToEndOfFile() - + let objects = realm.objects(Expense.self) - + for object in objects { var jsonObject = [AnyHashable: Any]() objectSchema.properties.forEach { property in @@ -102,26 +102,26 @@ class RealmConverter { } arrayJson.append(jsonObject) } - + let transactionJsonObject = ["Transactions": arrayJson] - + let data = try JSONSerialization.data(withJSONObject: transactionJsonObject, options: [.prettyPrinted]) let transactionJsonStr = String(data: data, encoding: .utf8) - + if let transactionJsonStr = transactionJsonStr { fileHandle?.write(transactionJsonStr.data(using: .utf8)!) } - + fileHandle?.closeFile() - + return filePath.url } - + private func sanitizedValue(_ value: String) -> String { func valueByEscapingQuotes(_ string: String) -> String { return escapeQuotes + string + escapeQuotes } - + // Value already contains quotes, replace with 2 sets of quotes if value.range(of: escapeQuotes) != nil { return valueByEscapingQuotes( diff --git a/GoMoney/Common/TabBar/RollingPitTabBar.swift b/GoMoney/Common/TabBar/RollingPitTabBar.swift index dfec169..cde0f82 100644 --- a/GoMoney/Common/TabBar/RollingPitTabBar.swift +++ b/GoMoney/Common/TabBar/RollingPitTabBar.swift @@ -22,119 +22,119 @@ public extension CGFloat { @IBInspectable public var barHeight: CGFloat = 65 @IBInspectable public var barTopRadius: CGFloat = 10 @IBInspectable public var barBottomRadius: CGFloat = 20 - + @IBInspectable public var circleBackColor: UIColor = .white @IBInspectable public var circleRadius: CGFloat = 40 @IBInspectable public var outerCircleRadius: CGFloat = 5 - + @IBInspectable var marginBottom: CGFloat = 5 @IBInspectable var marginTop: CGFloat = 0 - + @IBInspectable public var marginLeft: CGFloat = 15 @IBInspectable public var marginRight: CGFloat = 15 - + @IBInspectable public var pitCornerRad: CGFloat = 10 - + @IBInspectable public var pitCircleDistanceOffset: CGFloat = 7 @IBInspectable public var bottomInset: CGFloat = 4 - + @IBInspectable public var pathMoveDuration: CFTimeInterval = 1 @IBInspectable public var animateShowAndHideItemDuration: CFTimeInterval = 0.4 - + private var barRect: CGRect { let h = barHeight let w = bounds.width - (marginLeft + marginRight) let x = bounds.minX + marginLeft let y = marginTop + circleRadius - + let rect = CGRect(x: x, y: y, width: w, height: h) return rect } - + private func createCircleRect() -> CGRect { let backRect = barRect let radius = circleRadius let circleXCenter = getCircleCenter() - + let x: CGFloat = circleXCenter - radius let y = backRect.origin.y - radius + pitCircleDistanceOffset - + let pos = CGPoint(x: x, y: y) - + let result = CGRect(origin: pos, size: CGSize(width: radius * 2, height: radius * 2)) return result } - + private func createCirclePath() -> CGPath { let circleRect = createCircleRect() let result = UIBezierPath(roundedRect: circleRect, cornerRadius: circleRect.height / 2) - + return result.cgPath } - + private func getCircleCenter() -> CGFloat { let totalWidth = bounds.width var x = totalWidth / 2 if let v = getViewForItem(item: selectedItem) { x = v.frame.minX + (v.frame.width / 2) } - + return x } - + func createPitMaskPath(rect: CGRect) -> CGMutablePath { let circleXcenter = getCircleCenter() let backRect = barRect let x: CGFloat = circleXcenter + pitCornerRad let y = backRect.origin.y - + let center = CGPoint(x: x, y: y) - + let maskPath = CGMutablePath() maskPath.addRect(rect) - + let pit = createPitPath(center: center) maskPath.addPath(pit) - + return maskPath } - + func createPitPath(center: CGPoint) -> CGPath { let rad = circleRadius + outerCircleRadius let x = center.x - rad - pitCornerRad let y = center.y - + let result = UIBezierPath() result.lineWidth = 0 result.move(to: CGPoint(x: x - 0, y: y + 0)) - + result.addArc(withCenter: CGPoint(x: x - pitCornerRad, y: y + pitCornerRad), radius: pitCornerRad, startAngle: CGFloat(270).toRadians(), endAngle: CGFloat(0).toRadians(), clockwise: true) - + result.addArc(withCenter: CGPoint(x: x + rad, y: y + pitCornerRad), radius: rad, startAngle: CGFloat(180).toRadians(), endAngle: CGFloat(0).toRadians(), clockwise: false) - + result.addArc(withCenter: CGPoint(x: x + (rad * 2) + pitCornerRad, y: y + pitCornerRad), radius: pitCornerRad, startAngle: CGFloat(180).toRadians(), endAngle: CGFloat(270).toRadians(), clockwise: true) - + result.addLine(to: CGPoint(x: x + (pitCornerRad * 2) + (rad * 2), y: y)) // rounding errors correction lines result.addLine(to: CGPoint(x: 0, y: 0)) - + result.close() - + return result.cgPath } - + private func createBackgroundPath() -> CGPath { let rect = barRect let topLeftRadius = barTopRadius let topRightRadius = barTopRadius let bottomRigtRadius = barBottomRadius let bottomLeftRadius = barBottomRadius - + let path = UIBezierPath() - + path.move(to: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY)) path.addLine(to: CGPoint(x: rect.maxX - topLeftRadius, y: rect.minY)) - + path.addArc(withCenter: CGPoint(x: rect.maxX - topRightRadius, y: rect.minY + topRightRadius), radius: topRightRadius, startAngle: 3 * pi2, endAngle: 0, clockwise: true) path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRigtRadius)) path.addArc(withCenter: CGPoint(x: rect.maxX - bottomRigtRadius, y: rect.maxY - bottomRigtRadius), radius: bottomRigtRadius, startAngle: 0, endAngle: pi2, clockwise: true) @@ -143,26 +143,26 @@ public extension CGFloat { path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius)) path.addArc(withCenter: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY + topLeftRadius), radius: topLeftRadius, startAngle: pi, endAngle: 3 * pi2, clockwise: true) path.close() - + return path.cgPath } - + private lazy var background: CAShapeLayer = { let result = CAShapeLayer() result.fillColor = self.barBackColor.cgColor result.mask = self.backgroundMask - + return result }() - + private lazy var circle: CAShapeLayer = { let result = CAShapeLayer() - + result.fillColor = circleBackColor.cgColor - + return result }() - + private lazy var backgroundMask: CAShapeLayer = { let result = CAShapeLayer() result.fillRule = CAShapeLayerFillRule.evenOdd @@ -175,45 +175,45 @@ public extension CGFloat { sizeThatFits.height = barHeight + marginTop + marginBottom + circleRadius return sizeThatFits } - + private func getTabBarItemViews() -> [(item: UITabBarItem, view: UIView)] { guard let items = items else { return [] } - + var result: [(item: UITabBarItem, view: UIView)] = [] for item in items { if let v = getViewForItem(item: item) { result.append((item: item, view: v)) } } - + return result } - + private func getViewForItem(item: UITabBarItem?) -> UIView? { if let item = item { let v = item.value(forKey: "view") as? UIView return v } - + return nil } - - private func positionItem(barRect: CGRect, totalCount: Int, idx: Int, item: UITabBarItem, view: UIView) { + + private func positionItem(barRect: CGRect, totalCount _: Int, idx _: Int, item: UITabBarItem, view: UIView) { let x = view.frame.origin.x var y = barRect.origin.y + bottomInset let h = barHeight - (bottomInset * 2) let w = view.frame.width - + let itemImageHeight: CGFloat = (item.image?.size.height ?? 0) / 2 - + if selectedItem == item { y = barRect.origin.y - (circleRadius / 2) + pitCircleDistanceOffset - itemImageHeight } view.frame = CGRect(x: x, y: y, width: w, height: h) } - + private func animateHideAndShowItem(itemView: UIView) { itemView.alpha = 0 itemView.isHidden = false @@ -231,59 +231,58 @@ public extension CGFloat { // animation.beginTime = CACurrentMediaTime() + 2 animation.toValue = toVal animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) - + animation.isRemovedOnCompletion = false animation.fillMode = CAMediaTimingFillMode.forwards return animation } - + private func replaceAnimation(layer: CAShapeLayer, withNew: CABasicAnimation, forKey: String) { let existing = layer.animation(forKey: forKey) as? CABasicAnimation if existing != nil { withNew.fromValue = existing!.toValue } - + layer.removeAnimation(forKey: forKey) layer.add(withNew, forKey: forKey) } - + private func moveCircleAnimated() { let bgMaskNewPath = createPitMaskPath(rect: bounds) let circleNewPath = createCirclePath() - + let bgAni = createPathMoveAnimation(toVal: bgMaskNewPath) let circleAni = createPathMoveAnimation(toVal: circleNewPath) - + replaceAnimation(layer: backgroundMask, withNew: bgAni, forKey: "move_animation") replaceAnimation(layer: circle, withNew: circleAni, forKey: "move_animation") } - + private func layoutElements(selectedChanged: Bool) { background.path = createBackgroundPath() if backgroundMask.path == nil { backgroundMask.path = createPitMaskPath(rect: bounds) circle.path = createCirclePath() - } - else { + } else { moveCircleAnimated() } - + let items = getTabBarItemViews() if items.count <= 0 { return } - + let barR = barRect let total = items.count for (idx, item) in items.enumerated() { if selectedChanged { animateHideAndShowItem(itemView: item.view) } - + positionItem(barRect: barR, totalCount: total, idx: idx, item: item.item, view: item.view) } } - + override var selectedItem: UITabBarItem? { get { return super.selectedItem @@ -296,34 +295,34 @@ public extension CGFloat { } } } - + override func layoutSubviews() { super.layoutSubviews() background.fillColor = barBackColor.cgColor circle.fillColor = circleBackColor.cgColor - + layoutElements(selectedChanged: false) - + addDropShadow() } - + override func prepareForInterfaceBuilder() { isTranslucent = true backgroundColor = UIColor.clear backgroundImage = UIImage() shadowImage = UIImage() - + background.fillColor = barBackColor.cgColor circle.fillColor = circleBackColor.cgColor } - + private func setup() { isTranslucent = true backgroundColor = UIColor.clear backgroundImage = UIImage() shadowImage = UIImage() - + layer.insertSublayer(background, at: 0) layer.insertSublayer(circle, at: 0) } @@ -332,7 +331,7 @@ public extension CGFloat { super.init(frame: frame) setup() } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() diff --git a/GoMoney/Common/View/GMButton.swift b/GoMoney/Common/View/GMButton.swift index 5a3225b..543140e 100644 --- a/GoMoney/Common/View/GMButton.swift +++ b/GoMoney/Common/View/GMButton.swift @@ -1,10 +1,3 @@ -// -// GMButton.swift -// GoMoney -// -// Created by Golden Owl on 13/10/2022. -// - import UIKit class GMButton: UIButton { @@ -19,13 +12,13 @@ class GMButton: UIButton { builder: ((GMButton) -> Void)? = nil ) { self.init(frame: .zero) - self.translatesAutoresizingMaskIntoConstraints = false - self.setTitle(text, for: .normal) - self.titleLabel?.textAlignment = .center - self.setTitleColor(color, for: .normal) - self.titleLabel?.font = UIFont(name: font, size: size) + translatesAutoresizingMaskIntoConstraints = false + setTitle(text, for: .normal) + titleLabel?.textAlignment = .center + setTitleColor(color, for: .normal) + titleLabel?.font = UIFont(name: font, size: size) self.tapAction = tapAction - self.addTarget(self, action: #selector(self.didTapButton), for: .touchUpInside) + addTarget(self, action: #selector(didTapButton), for: .touchUpInside) builder?(self) } @@ -39,6 +32,6 @@ class GMButton: UIButton { @objc private func didTapButton() { - self.tapAction?() + tapAction?() } } diff --git a/GoMoney/Common/View/GMCircleImage.swift b/GoMoney/Common/View/GMCircleImage.swift index 41a8650..b6c75b7 100644 --- a/GoMoney/Common/View/GMCircleImage.swift +++ b/GoMoney/Common/View/GMCircleImage.swift @@ -5,29 +5,29 @@ class GMCircleImage: UIImageView { size: CGFloat, image: UIImage? = K.Image.user, border: CGFloat = 0, - builder: ((GMCircleImage) -> Void)? = nil) - { + builder: ((GMCircleImage) -> Void)? = nil + ) { super.init(frame: .zero) translatesAutoresizingMaskIntoConstraints = false backgroundColor = K.Color.contentBackground clipsToBounds = true - + anchor(width: size, height: size) - + layer.cornerRadius = size / 2 if border > 0 { layer.borderWidth = border layer.borderColor = K.Color.borderOnBg.cgColor } - + self.image = image contentMode = .scaleAspectFill - + builder?(self) } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Common/View/GMExpenseIcon.swift b/GoMoney/Common/View/GMExpenseIcon.swift index 824cb16..803e6d7 100644 --- a/GoMoney/Common/View/GMExpenseIcon.swift +++ b/GoMoney/Common/View/GMExpenseIcon.swift @@ -21,7 +21,7 @@ class GMExpenseIcon: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Common/View/GMFloatingButton.swift b/GoMoney/Common/View/GMFloatingButton.swift index 9af4989..d2c9694 100644 --- a/GoMoney/Common/View/GMFloatingButton.swift +++ b/GoMoney/Common/View/GMFloatingButton.swift @@ -3,62 +3,63 @@ import UIKit class GMFloatingButton: UIControl { private(set) var onTap: (() -> Void)? private let height: CGFloat = 54 - + lazy var buttonIcon: UIImageView = { let img = UIImageView() img.translatesAutoresizingMaskIntoConstraints = false img.contentMode = .scaleAspectFit return img }() - + lazy var buttonLabel = GMLabel(style: .regular, isCenter: true) { $0.textColor = .white } - + init(image: UIImage? = K.Image.edit, text: String = "Edit", textColor: UIColor? = .white, background: UIColor? = .action, onTap: (() -> Void)? = nil) { super.init(frame: CGRect(x: 0, y: 0, width: 120, height: 64)) - + backgroundColor = background buttonIcon.image = image buttonLabel.text = text buttonLabel.textColor = textColor - + self.onTap = onTap addTarget(self, action: #selector(didTapButton), for: .touchUpInside) - + setView() } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - + private func setView() { translatesAutoresizingMaskIntoConstraints = false sizeToFit() anchor(height: height) - + addSubviews(buttonIcon, buttonLabel) let padding = height / 2 - 10 buttonIcon.anchor(left: leftAnchor, paddingLeft: padding, width: 24, height: 30) buttonIcon.centerYToView(self) - + buttonLabel.anchor( left: buttonIcon.rightAnchor, right: rightAnchor, paddingLeft: 12, - paddingRight: padding) + paddingRight: padding + ) buttonLabel.centerYToView(self) } - + override func layoutSubviews() { super.layoutSubviews() layer.cornerRadius = frame.size.height / 2 setupShadow() } - + // MARK: - Touches var touchAlpha: TouchAlphaValues = .untouched { @@ -66,13 +67,13 @@ class GMFloatingButton: UIControl { updateTouchAlpha() } } - + var pressed: Bool = false { didSet { touchAlpha = pressed ? .touched : .untouched } } - + override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) pressed = true @@ -82,7 +83,7 @@ class GMFloatingButton: UIControl { super.touchesEnded(touches, with: event) pressed = false } - + func updateTouchAlpha() { if alpha != touchAlpha.rawValue { UIView.animate(withDuration: 0.3) { @@ -90,7 +91,7 @@ class GMFloatingButton: UIControl { } } } - + // Action @objc private func didTapButton() { diff --git a/GoMoney/Common/View/GMImageButton.swift b/GoMoney/Common/View/GMImageButton.swift index 8e5ece1..f6855a7 100644 --- a/GoMoney/Common/View/GMImageButton.swift +++ b/GoMoney/Common/View/GMImageButton.swift @@ -2,22 +2,22 @@ import UIKit class GMImageButton: UIView { lazy var imageView: UIImageView = .build { _ in } - + var didTapButton: (() -> Void)? - + var size: CGFloat! - + init(size: CGFloat = 36, image: UIImage?, tintColor: UIColor = .white, backgroundColor: UIColor = .action, padding: CGFloat = 12, didTapButton: (() -> Void)? = nil, builder: ((GMImageButton) -> Void)? = nil) { super.init(frame: .zero) - + self.backgroundColor = backgroundColor self.size = size self.didTapButton = didTapButton - self.isUserInteractionEnabled = true + isUserInteractionEnabled = true addGestureRecognizer( UITapGestureRecognizer(target: self, action: #selector(didTap)) ) - + imageView.image = image imageView.tintColor = tintColor imageView.anchor(width: size - padding, height: size - padding) @@ -25,23 +25,23 @@ class GMImageButton: UIView { setupView() builder?(self) } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func setupView() { translatesAutoresizingMaskIntoConstraints = false - + anchor(width: size, height: size) addSubview(imageView) - + imageView.centerInSuperview() layer.cornerRadius = size / 2 } - + @objc private func didTap() { showTapAnimation { diff --git a/GoMoney/Common/View/GMLabel.swift b/GoMoney/Common/View/GMLabel.swift index d8292c7..4d7c788 100644 --- a/GoMoney/Common/View/GMLabel.swift +++ b/GoMoney/Common/View/GMLabel.swift @@ -44,13 +44,13 @@ enum GMLabelStyle { class GMLabel: UILabel { var gmStyle: GMLabelStyle = .regular { didSet { - self.update() + update() } } private func update() { - self.font = UIFont(name: self.gmStyle.getFont(), size: self.gmStyle.getSize()) - self.textColor = self.gmStyle.getColor() + font = UIFont(name: gmStyle.getFont(), size: gmStyle.getSize()) + textColor = gmStyle.getColor() } required init?(coder: NSCoder) { @@ -60,18 +60,18 @@ class GMLabel: UILabel { init( text: String = "", style: GMLabelStyle = .regular, - numberOfLines: Int = 0, + numberOfLines _: Int = 0, isCenter: Bool = false, builder: ((GMLabel) -> Void)? = nil ) { super.init(frame: .zero) self.text = text - self.textAlignment = isCenter ? .center : .natural - self.gmStyle = style + textAlignment = isCenter ? .center : .natural + gmStyle = style - self.setup() - self.update() + setup() + update() builder?(self) } diff --git a/GoMoney/Common/View/GMLabelActionView.swift b/GoMoney/Common/View/GMLabelActionView.swift index b6c7c9e..1221d29 100644 --- a/GoMoney/Common/View/GMLabelActionView.swift +++ b/GoMoney/Common/View/GMLabelActionView.swift @@ -28,7 +28,7 @@ class GMLabelActionView: UIControl { text: String, icLeft: UIImage?, icRight: UIImage? = UIImage(systemName: "chevron.right"), - background: UIColor = .gray, + background _: UIColor = .gray, textColor: UIColor = K.Color.boxLabel, action: (() -> Void)? = nil ) { @@ -83,7 +83,7 @@ class GMLabelActionView: UIControl { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Common/View/GMLabelSpan.swift b/GoMoney/Common/View/GMLabelSpan.swift index 95eaed0..39b4a00 100644 --- a/GoMoney/Common/View/GMLabelSpan.swift +++ b/GoMoney/Common/View/GMLabelSpan.swift @@ -14,8 +14,8 @@ class GMLabelSpan: UILabel { span: String, style: GMLabelStyle, underline: Bool = false, - builder: ((GMLabelSpan) -> Void)? = nil) - { + builder: ((GMLabelSpan) -> Void)? = nil + ) { super.init(frame: .zero) let link = NSMutableAttributedString(string: text) @@ -24,11 +24,13 @@ class GMLabelSpan: UILabel { link.addAttribute( NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, - range: range) + range: range + ) } link.addAttribute(NSAttributedString.Key.font, value: UIFont( name: style.getFont(), - size: style.getSize()) ?? .nova(), + size: style.getSize() + ) ?? .nova(), range: NSRangeFromString(text)) link.addAttribute(NSAttributedString.Key.foregroundColor, value: K.Color.primaryColor, range: range) attributedText = link @@ -40,7 +42,7 @@ class GMLabelSpan: UILabel { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Common/View/GMLoadingView.swift b/GoMoney/Common/View/GMLoadingView.swift index 3ab68cf..be3c12d 100644 --- a/GoMoney/Common/View/GMLoadingView.swift +++ b/GoMoney/Common/View/GMLoadingView.swift @@ -16,7 +16,7 @@ class GMLoadingView { return indicatorView }() - var loadingLabel = GMLabel(style: .regular) { + var loadingLabel = GMLabel(style: .regular) { $0.textColor = .primary } @@ -42,6 +42,7 @@ class GMLoadingView { loadingLabel.centerX(inView: blurView) loadingLabel.anchor( top: indicatorView.bottomAnchor, - paddingTop: 16) + paddingTop: 16 + ) } } diff --git a/GoMoney/Constants/Constant.swift b/GoMoney/Constants/Constant.swift index 003eada..5b2d620 100644 --- a/GoMoney/Constants/Constant.swift +++ b/GoMoney/Constants/Constant.swift @@ -1,10 +1,3 @@ -// -// Constant.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import UIKit struct K { diff --git a/GoMoney/Constants/UserDefaultKey.swift b/GoMoney/Constants/UserDefaultKey.swift index 562b297..8ebc126 100644 --- a/GoMoney/Constants/UserDefaultKey.swift +++ b/GoMoney/Constants/UserDefaultKey.swift @@ -1,10 +1,3 @@ -// -// UserDefaultKey.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import Foundation struct UserDefaultKey { diff --git a/GoMoney/Extensions/Date+Extension.swift b/GoMoney/Extensions/Date+Extension.swift index a5474ee..b798700 100644 --- a/GoMoney/Extensions/Date+Extension.swift +++ b/GoMoney/Extensions/Date+Extension.swift @@ -4,36 +4,36 @@ extension Date { func getLast6Month() -> Date? { return Calendar.current.date(byAdding: .month, value: -6, to: self) } - + func getLast3Month() -> Date? { return Calendar.current.date(byAdding: .month, value: -3, to: self) } - + func getYesterday() -> Date? { return Calendar.current.date(byAdding: .day, value: -1, to: self) } - + func getLast7Day() -> Date? { return Calendar.current.date(byAdding: .day, value: -7, to: self) } - + func getLast30Day() -> Date? { return Calendar.current.date(byAdding: .day, value: -30, to: self) } - + func getLastYearDay() -> Date? { return Calendar.current.date(byAdding: .day, value: -365, to: self) } - + func getPreviousMonth() -> Date? { return Calendar.current.date(byAdding: .month, value: -1, to: self) } - + func getThisMonthStart() -> Date? { let components = Calendar.current.dateComponents([.year, .month], from: self) return Calendar.current.date(from: components) } - + func getThisMonthEnd() -> Date? { let components: NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents components.month += 1 @@ -41,7 +41,7 @@ extension Date { components.day -= 1 return Calendar.current.date(from: components as DateComponents) } - + func getLastMonthStart() -> Date? { let components: NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents components.month -= 1 @@ -54,22 +54,22 @@ extension Date { components.day -= 1 return Calendar.current.date(from: components as DateComponents) } - + func isEqual(to date: Date, toGranularity component: Calendar.Component, in calendar: Calendar = .current) -> Bool { calendar.isDate(self, equalTo: date, toGranularity: component) } - + func isSameDayAs(_ other: Date) -> Bool { return Calendar.current.isDate(self, inSameDayAs: other) } - + func isInSameYear(as date: Date) -> Bool { isEqual(to: date, toGranularity: .year) } func isInSameMonth(as date: Date) -> Bool { isEqual(to: date, toGranularity: .month) } func isInSameWeek(as date: Date) -> Bool { isEqual(to: date, toGranularity: .weekOfYear) } - + func timeAgoDisplay() -> String { let date = Date() - + let calendar = Calendar.current let minuteAgo = calendar.date(byAdding: .minute, value: -1, to: date) let hourAgo = calendar.date(byAdding: .hour, value: -1, to: date) @@ -84,7 +84,7 @@ extension Date { else { return "" } - + if minuteAgo < self { let diff = Calendar.current.dateComponents([.second], from: self, to: date).second ?? 0 return "\(diff) sec ago" diff --git a/GoMoney/Extensions/UIBarButtonItem+Extension.swift b/GoMoney/Extensions/UIBarButtonItem+Extension.swift index 68b72b5..e64fe66 100644 --- a/GoMoney/Extensions/UIBarButtonItem+Extension.swift +++ b/GoMoney/Extensions/UIBarButtonItem+Extension.swift @@ -2,7 +2,7 @@ import UIKit extension UIBarButtonItem { func setTextAttributes(font: UIFont? = K.Theme.titleFont, color: UIColor = .black, state: UIControl.State = .normal) { - self.setTitleTextAttributes([ + setTitleTextAttributes([ NSAttributedString.Key.font: font ?? UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: color, ], diff --git a/GoMoney/Extensions/UIView+Constraint.swift b/GoMoney/Extensions/UIView+Constraint.swift index 788c1a7..c352e7e 100644 --- a/GoMoney/Extensions/UIView+Constraint.swift +++ b/GoMoney/Extensions/UIView+Constraint.swift @@ -19,96 +19,96 @@ internal extension UIView { if let top = top { topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true } - + if let left = left { leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true } - + if let bottom = bottom { bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true } - + if let right = right { rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true } - + if let width = width { widthAnchor.constraint(equalToConstant: width).isActive = true } - + if let height = height { heightAnchor.constraint(equalToConstant: height).isActive = true } } - + func centerX(inView view: UIView) { centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true } - + func centerY(inView view: UIView, leftAnchor: NSLayoutXAxisAnchor? = nil, paddingLeft: CGFloat = 0, constant: CGFloat = 0) { centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true - + if let left = leftAnchor { - self.anchor(left: left, paddingLeft: paddingLeft) + anchor(left: left, paddingLeft: paddingLeft) } } - + func setDimensions(height: CGFloat, width: CGFloat) { heightAnchor.constraint(equalToConstant: height).isActive = true widthAnchor.constraint(equalToConstant: width).isActive = true } - + open func centerInSuperview(size: CGSize = .zero) { if let superviewCenterXAnchor = superview?.centerXAnchor { centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true } - + if let superviewCenterYAnchor = superview?.centerYAnchor { centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true } - + if size.width != 0 { widthAnchor.constraint(equalToConstant: size.width).isActive = true } - + if size.height != 0 { heightAnchor.constraint(equalToConstant: size.height).isActive = true } } - + open func centerInSuperview(size: CGSize = .zero, offset: CGPoint = .zero) { NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: superview, attribute: .centerX, multiplier: 1, constant: offset.x).isActive = true - + NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: superview, attribute: .centerY, multiplier: 1, constant: offset.y).isActive = true - + if size.width != 0 { widthAnchor.constraint(equalToConstant: size.width).isActive = true } - + if size.height != 0 { heightAnchor.constraint(equalToConstant: size.height).isActive = true } } - + open func centerXToSuperview() { if let superviewCenterXAnchor = superview?.centerXAnchor { centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true } } - + open func centerXToSuperview(offset: CGFloat) { let constraint = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: superview, attribute: .centerX, multiplier: 1, constant: offset) constraint.isActive = true } - + open func centerYToSuperview() { if let superviewCenterYAnchor = superview?.centerYAnchor { centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true } } - + open func centerYToSuperview(offset: CGFloat) { let constraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: superview, attribute: .centerY, multiplier: 1, constant: offset) constraint.isActive = true @@ -117,7 +117,7 @@ internal extension UIView { open func centerYToView(_ toView: UIView) { centerYAnchor.constraint(equalTo: toView.centerYAnchor).isActive = true } - + open func centerXToView(_ toView: UIView) { centerXAnchor.constraint(equalTo: toView.centerXAnchor).isActive = true } @@ -127,14 +127,14 @@ internal extension UIView { paddingBottom: CGFloat = 0, paddingRight: CGFloat = 0) { - self.anchor(top: superview?.safeAreaLayoutGuide.topAnchor, - left: superview?.leftAnchor, - bottom: superview?.safeAreaLayoutGuide.bottomAnchor, - right: superview?.rightAnchor, - paddingTop: paddingTop, - paddingLeft: paddingLeft, - paddingBottom: paddingBottom, - paddingRight: paddingRight) + anchor(top: superview?.safeAreaLayoutGuide.topAnchor, + left: superview?.leftAnchor, + bottom: superview?.safeAreaLayoutGuide.bottomAnchor, + right: superview?.rightAnchor, + paddingTop: paddingTop, + paddingLeft: paddingLeft, + paddingBottom: paddingBottom, + paddingRight: paddingRight) } open func setupShadow(opacity: Float = 0, radius: CGFloat = 0, offset: CGSize = .zero, color: UIColor = .black) { @@ -143,24 +143,24 @@ internal extension UIView { layer.shadowOffset = offset layer.shadowColor = color.cgColor } - + public convenience init(backgroundColor: UIColor? = .clear) { self.init(frame: .zero) self.backgroundColor = backgroundColor } - + func roundCorners(_ corners: UIRectCorner, radius: CGFloat) { - let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) + let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) let mask = CAShapeLayer() mask.path = path.cgPath - self.layer.mask = mask + layer.mask = mask } - + func clearConstraints() { - for subview in self.subviews { + for subview in subviews { subview.clearConstraints() } - self.removeConstraints(self.constraints) + removeConstraints(constraints) } } diff --git a/GoMoney/Extensions/UIView+Extension.swift b/GoMoney/Extensions/UIView+Extension.swift index a8d36af..cb5e55f 100644 --- a/GoMoney/Extensions/UIView+Extension.swift +++ b/GoMoney/Extensions/UIView+Extension.swift @@ -1,10 +1,3 @@ -// -// UIView+Extension.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import UIKit extension UIView { @@ -34,7 +27,7 @@ extension UIView { blurEffectView.leadingAnchor.constraint(equalTo: leadingAnchor), blurEffectView.trailingAnchor.constraint(equalTo: trailingAnchor), blurEffectView.topAnchor.constraint(equalTo: topAnchor), - blurEffectView.bottomAnchor.constraint(equalTo: bottomAnchor) + blurEffectView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) } @@ -52,8 +45,8 @@ extension UIView { shadowColor: CGColor = UIColor.darkGray.cgColor, shadowOffset: CGSize = CGSize(width: 3, height: 3), shadowOpacity: Float = 0.4, - shadowRadius: CGFloat = 3) - { + shadowRadius: CGFloat = 3 + ) { layer.masksToBounds = false layer.shadowColor = shadowColor layer.shadowOffset = shadowOffset diff --git a/GoMoney/Extensions/UIViewController+Extension.swift b/GoMoney/Extensions/UIViewController+Extension.swift index 45b2ea1..e543304 100644 --- a/GoMoney/Extensions/UIViewController+Extension.swift +++ b/GoMoney/Extensions/UIViewController+Extension.swift @@ -1,10 +1,3 @@ -// -// UIViewController+Extension.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import TTGSnackbar import UIKit diff --git a/GoMoney/Models/OnboardPage.swift b/GoMoney/Models/OnboardPage.swift index 97cf751..6f68ae8 100644 --- a/GoMoney/Models/OnboardPage.swift +++ b/GoMoney/Models/OnboardPage.swift @@ -1,10 +1,3 @@ -// -// OnboardPageModel.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - struct OnboardPageModel { let imageName: String? let topicText: String? diff --git a/GoMoney/Models/TransactionTag.swift b/GoMoney/Models/TransactionTag.swift index 95573e7..12b4dbd 100644 --- a/GoMoney/Models/TransactionTag.swift +++ b/GoMoney/Models/TransactionTag.swift @@ -48,7 +48,7 @@ extension TransactionTag { TransactionTag(name: "Salary", icon: "trans_type_salary", type: .income), TransactionTag(name: "Allowance", icon: "trans_type_allowance", type: .income), - TransactionTag(name: "Bonus", icon: "trans_type_bonus", type: .income) + TransactionTag(name: "Bonus", icon: "trans_type_bonus", type: .income), ] // for custom category with local icons diff --git a/GoMoney/Models/TransactionTracking.swift b/GoMoney/Models/TransactionTracking.swift index abf62da..3cf8056 100644 --- a/GoMoney/Models/TransactionTracking.swift +++ b/GoMoney/Models/TransactionTracking.swift @@ -9,7 +9,7 @@ class TransactionTracking: Object { convenience init(id: ObjectId, status: Status) { self.init() - self._id = id + _id = id self.status = status.rawValue } diff --git a/GoMoney/Navigation/GMTabBarViewController.swift b/GoMoney/Navigation/GMTabBarViewController.swift index 8389ab5..a111dc5 100644 --- a/GoMoney/Navigation/GMTabBarViewController.swift +++ b/GoMoney/Navigation/GMTabBarViewController.swift @@ -16,9 +16,9 @@ class GMTabBarViewController: UITabBarController { tabBar.marginBottom = 0 return tabBar }() - + // MARK: - TabBar Icon - + private let statBlackIcon = K.Image.statistic.black() private let statWhiteIcon = K.Image.statistic.white() private let homeBlackIcon = K.Image.dashboard.black() @@ -30,29 +30,32 @@ class GMTabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() - + let statisticVC = MainNavigationController(rootViewController: StatViewController()) statisticVC.tabBarItem = UITabBarItem( title: "", image: statBlackIcon, - selectedImage: statWhiteIcon) - + selectedImage: statWhiteIcon + ) + let homeVC = MainNavigationController(rootViewController: HomeViewController()) homeVC.tabBarItem = UITabBarItem( title: "", image: homeBlackIcon, - selectedImage: homeWhiteIcon) - + selectedImage: homeWhiteIcon + ) + let profileVC = MainNavigationController(rootViewController: ProfileViewController()) profileVC.tabBarItem = UITabBarItem( title: "", image: profileBlackIcon, - selectedImage: profileWhiteIcon) - + selectedImage: profileWhiteIcon + ) + setValue(customTabBar, forKey: "tabBar") - + viewControllers = [statisticVC, homeVC, profileVC] - + selectedIndex = 1 } } diff --git a/GoMoney/Scences/AddExpense/AddExpenseField.swift b/GoMoney/Scences/AddExpense/AddExpenseField.swift index 2057ebc..6581299 100644 --- a/GoMoney/Scences/AddExpense/AddExpenseField.swift +++ b/GoMoney/Scences/AddExpense/AddExpenseField.swift @@ -6,13 +6,13 @@ class AddExpenseField: UIView { inputField.placeholder = placeHolder } } - + var text: String? { didSet { inputField.text = text } } - + lazy var label = GMLabel { $0.textColor = .darkGray $0.font = .nova(20) @@ -28,45 +28,47 @@ class AddExpenseField: UIView { $0.attributedPlaceholder = NSAttributedString(string: self.placeHolder ?? "", attributes: attributes) } - + private(set) var name: String = "" private(set) var defaultValue: String = "" - + init(name: String, defaultValue: String = "", placeHolder: String = "", makeInputView: ((ExpenseTextField) -> Void)? = nil) { super.init(frame: .zero) - + self.name = name self.placeHolder = placeHolder self.defaultValue = defaultValue setView() makeInputView?(inputField) } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - + private func setView() { translatesAutoresizingMaskIntoConstraints = false - + addSubviews(label, inputField) label.text = name inputField.text = defaultValue - + heightAnchor.constraint(equalToConstant: 56).isActive = true - + label.anchor( top: topAnchor, - left: leftAnchor) + left: leftAnchor + ) label.centerYToView(self) label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.3).isActive = true - + inputField.anchor( top: label.topAnchor, left: label.rightAnchor, right: rightAnchor, - paddingLeft: 8) + paddingLeft: 8 + ) inputField.centerYToView(self) } } diff --git a/GoMoney/Scences/AddExpense/AddExpenseForm.swift b/GoMoney/Scences/AddExpense/AddExpenseForm.swift index f3c4088..5fbce8d 100644 --- a/GoMoney/Scences/AddExpense/AddExpenseForm.swift +++ b/GoMoney/Scences/AddExpense/AddExpenseForm.swift @@ -116,7 +116,7 @@ class AddExpenseForm: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Scences/AddExpense/AddExpenseViewController.swift b/GoMoney/Scences/AddExpense/AddExpenseViewController.swift index 297cc92..bd1c895 100644 --- a/GoMoney/Scences/AddExpense/AddExpenseViewController.swift +++ b/GoMoney/Scences/AddExpense/AddExpenseViewController.swift @@ -19,7 +19,8 @@ class AddExpenseViewController: GMMainViewController { transType: type, textFieldOnChange: { [weak self] in self?.errorLabel.text = nil - }) + } + ) lazy var errorLabel: GMLabel = .init(style: .smallBold, isCenter: true) { $0.textColor = K.Color.error @@ -28,8 +29,8 @@ class AddExpenseViewController: GMMainViewController { lazy var saveButton = GMFloatingButton( image: UIImage(systemName: "plus.circle")?.color(.white), - text: "Save") - { [weak self] in + text: "Save" + ) { [weak self] in self?.saveExpense() } @@ -81,7 +82,8 @@ class AddExpenseViewController: GMMainViewController { view.addSubviews( addExpenseForm, errorLabel, - saveButton) + saveButton + ) addExpenseForm.anchor( top: view.safeAreaLayoutGuide.topAnchor, @@ -89,21 +91,25 @@ class AddExpenseViewController: GMMainViewController { right: view.rightAnchor, paddingTop: 32, paddingLeft: Constant.padding, - paddingRight: Constant.padding) + paddingRight: Constant.padding + ) errorLabel.anchor( top: addExpenseForm.bottomAnchor, left: addExpenseForm.leftAnchor, right: addExpenseForm.rightAnchor, - paddingTop: 32) + paddingTop: 32 + ) saveButton.anchor( right: view.rightAnchor, - paddingRight: 16) + paddingRight: 16 + ) saveButtonAnchor = saveButton.bottomAnchor.constraint( equalTo: view.bottomAnchor, - constant: -bottomInset) + constant: -bottomInset + ) saveButtonAnchor?.isActive = true } @@ -145,7 +151,8 @@ class AddExpenseViewController: GMMainViewController { kTextFont: .nova(14), kButtonFont: .novaBold(14), showCloseButton: false, - circleBackgroundColor: .action) + circleBackgroundColor: .action + ) let btnColor = UIColor.action.withAlphaComponent(0.8) @@ -158,8 +165,8 @@ class AddExpenseViewController: GMMainViewController { let doneBtn = alert.addButton( "Done", backgroundColor: .white, - textColor: .action) - { + textColor: .action + ) { self.didTapBack() } @@ -171,7 +178,8 @@ class AddExpenseViewController: GMMainViewController { subTitle: "Transaction saved.", style: .success, colorStyle: 0xEFF3F6, - colorTextButton: 0xFFFFFF) + colorTextButton: 0xFFFFFF + ) } } @@ -182,7 +190,7 @@ extension AddExpenseViewController { } extension AddExpenseViewController: UITextFieldDelegate { - func textFieldDidBeginEditing(_ textField: UITextField) { + func textFieldDidBeginEditing(_: UITextField) { // TODO: Editting animation } } diff --git a/GoMoney/Scences/Auth/GMAuthViewController.swift b/GoMoney/Scences/Auth/GMAuthViewController.swift index 25828a7..3ceeb16 100644 --- a/GoMoney/Scences/Auth/GMAuthViewController.swift +++ b/GoMoney/Scences/Auth/GMAuthViewController.swift @@ -36,7 +36,8 @@ class GMAuthViewController: GMViewController { duration: duration, options: options, animations: {}, - completion: { _ in }) + completion: { _ in } + ) } } } @@ -62,9 +63,9 @@ class GMAuthViewController: GMViewController { func checkIfNewUser(completion: @escaping (Bool) -> Void) { RemoteService.shared.checkIfUserExist { result in switch result { - case .success(let exist): + case let .success(exist): completion(!exist) - case .failure(let err): + case let .failure(err): self.errorAlert(message: err.localizedDescription) } } diff --git a/GoMoney/Scences/Auth/SignIn/SignInGoogleVC.swift b/GoMoney/Scences/Auth/SignIn/SignInGoogleVC.swift index 6bfb935..1db5470 100644 --- a/GoMoney/Scences/Auth/SignIn/SignInGoogleVC.swift +++ b/GoMoney/Scences/Auth/SignIn/SignInGoogleVC.swift @@ -1,3 +1 @@ import UIKit - - diff --git a/GoMoney/Scences/Auth/SignIn/SignInPasswordVC.swift b/GoMoney/Scences/Auth/SignIn/SignInPasswordVC.swift index 6079ee6..0b00e7c 100644 --- a/GoMoney/Scences/Auth/SignIn/SignInPasswordVC.swift +++ b/GoMoney/Scences/Auth/SignIn/SignInPasswordVC.swift @@ -9,40 +9,40 @@ class SignInPasswordVC: GMAuthViewController { static let password = "Enter Your Password" static let login = "Login" } - + private enum Constant { static let padding: CGFloat = 16 } - + // MARK: - private properties - + let viewModel = SignInViewModel() private lazy var fieldEmail: TextFieldSignUp = .init( title: Content.email, type: .emailAddress, - delegate: self) - { + delegate: self + ) { $0.inputField.addTarget(self, action: #selector(self.textFieldDidChanged), for: .editingChanged) } - + private lazy var fieldPassword: TextFieldSignUp = .init( title: Content.password, secure: true, - delegate: self) - { + delegate: self + ) { $0.inputField.addTarget(self, action: #selector(self.textFieldDidChanged), for: .editingChanged) } - + private lazy var errorLabel: GMLabel = .init(style: .smallBold, isCenter: true) { $0.textColor = K.Color.error $0.numberOfLines = 0 } - + private lazy var inputFields: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical @@ -51,39 +51,41 @@ class SignInPasswordVC: GMAuthViewController { stackView.translatesAutoresizingMaskIntoConstraints = false return stackView }() - + private lazy var logginButton: GMButton = .init(text: Content.login, color: .white) { $0.layer.cornerRadius = 8 $0.backgroundColor = .primary $0.addTarget(self, action: #selector(self.didTapLogin), for: .touchUpInside) } - + func onSuccessLogin() { navigateToMainVC() } - + // MARK: - Setup Layout - + override func setupLayout() { super.setupLayout() - + view.backgroundColor = .white view.addSubviews(inputFields, errorLabel, logginButton) - + inputFields.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 50, paddingLeft: Constant.padding, - paddingRight: Constant.padding) - + paddingRight: Constant.padding + ) + errorLabel.anchor( top: inputFields.bottomAnchor, left: inputFields.leftAnchor, right: inputFields.rightAnchor, - paddingTop: 24) - + paddingTop: 24 + ) + logginButton.anchor( left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, @@ -91,16 +93,17 @@ class SignInPasswordVC: GMAuthViewController { paddingLeft: Constant.padding, paddingBottom: Constant.padding, paddingRight: Constant.padding, - height: 40) + height: 40 + ) } - + override func configureNavigation() { super.configureNavigation() title = Content.title } - + // MARK: - Method - + @objc func textFieldDidChanged() { errorLabel.text = "" } @@ -110,7 +113,7 @@ class SignInPasswordVC: GMAuthViewController { fieldEmail.inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let password: String = fieldPassword.inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - + viewModel.validateTextField(email: email, password: password) { error in if error != nil { errorLabel.text = error diff --git a/GoMoney/Scences/Auth/SignIn/SignInViewController.swift b/GoMoney/Scences/Auth/SignIn/SignInViewController.swift index fcd66ae..dc32b0d 100644 --- a/GoMoney/Scences/Auth/SignIn/SignInViewController.swift +++ b/GoMoney/Scences/Auth/SignIn/SignInViewController.swift @@ -1,17 +1,10 @@ -// -// SignInViewController.swift -// GoMoney -// -// Created by Golden Owl on 12/10/2022. -// - import UIKit class SignInViewController: GMAuthViewController { private lazy var img: UIImageView = .build { view in view.image = UIImage(named: "onboard_1") } - + private lazy var mailButton: ButtonAuth = .init( icon: "ic_email", text: "Sign in with password", @@ -20,7 +13,7 @@ class SignInViewController: GMAuthViewController { self?.didTapSignInEmail() } ) - + private lazy var googleButton: ButtonAuth = .init( icon: "ic_google", text: "Sign in with gmail", @@ -41,36 +34,36 @@ class SignInViewController: GMAuthViewController { action: #selector(self.didTapSignUp) )) } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - + img.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) mailButton.transform = CGAffineTransform(translationX: view.frame.origin.x + view.frame.width / 2, y: 0) googleButton.transform = CGAffineTransform(translationX: view.frame.origin.x + view.frame.width / 2, y: 0) - + UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: { self.img.transform = .identity self.mailButton.transform = .identity self.googleButton.transform = .identity }) } - + override func setupLayout() { title = "Login" view.backgroundColor = .white view.addSubviews(img, mailButton, googleButton, signUpText) - + img.anchor(width: 200, height: 200) img.centerXToSuperview() img.centerYToSuperview(offset: -(windowSafeAreaInsets?.top ?? 0)) - + signUpText.centerX(inView: view) signUpText.anchor( bottom: view.safeAreaLayoutGuide.bottomAnchor, paddingBottom: 24 ) - + googleButton.anchor( left: view.leftAnchor, bottom: signUpText.topAnchor, @@ -80,7 +73,7 @@ class SignInViewController: GMAuthViewController { paddingRight: 16, height: 40 ) - + mailButton.anchor( left: view.leftAnchor, bottom: googleButton.topAnchor, @@ -91,17 +84,17 @@ class SignInViewController: GMAuthViewController { height: 40 ) } - + @objc private func didTapSignUp() { let vc = SignUpPasswordVC() navigationController?.pushViewController(vc, animated: true) } - + private func didTapSignInEmail() { let vc = SignInPasswordVC() navigationController?.pushViewController(vc, animated: true) } - + private func didTapSignInGoogle() { GMLoadingView.shared.startLoadingAnimation(with: "Logging in ...") AuthService.shared.signInGoogle( diff --git a/GoMoney/Scences/Auth/SignUp/SignUpDetailViewController.swift b/GoMoney/Scences/Auth/SignUp/SignUpDetailViewController.swift index 35a7e75..5cddab7 100644 --- a/GoMoney/Scences/Auth/SignUp/SignUpDetailViewController.swift +++ b/GoMoney/Scences/Auth/SignUp/SignUpDetailViewController.swift @@ -4,7 +4,7 @@ class SignUpDetailViewController: GMViewController { private enum Constant { static let padding: CGFloat = 16 } - + private enum Content { static let title = "Enter your info" static let name = "What's your name?" @@ -15,17 +15,20 @@ class SignUpDetailViewController: GMViewController { private lazy var fieldName: TextFieldSignUp = .init( title: Content.name, - delegate: self) - + delegate: self + ) + private lazy var fieldDob: TextFieldSignUp = .init( title: Content.dob, type: .numberPad, - delegate: self) - + delegate: self + ) + private lazy var fieldIncome: TextFieldSignUp = .init( title: Content.income, type: .numberPad, - delegate: self) + delegate: self + ) private lazy var inputFields: UIStackView = { let stackView = UIStackView() @@ -33,16 +36,16 @@ class SignUpDetailViewController: GMViewController { stackView.spacing = Constant.padding stackView.addArrangedSubviews(fieldName, fieldDob, fieldIncome) stackView.translatesAutoresizingMaskIntoConstraints = false - + return stackView }() - + private lazy var nextButton: GMButton = .init(text: Content.next, color: .white) { $0.layer.cornerRadius = 8 $0.backgroundColor = .primary $0.addTarget(self, action: #selector(self.didTapNext), for: .touchUpInside) } - + override func configureNavigation() { super.configureNavigation() configureBackButton() @@ -51,19 +54,20 @@ class SignUpDetailViewController: GMViewController { override func setupLayout() { super.setupLayout() - + view.backgroundColor = K.Color.background - + view.addSubviews(inputFields, nextButton) - + inputFields.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 50, paddingLeft: 16, - paddingRight: 16) - + paddingRight: 16 + ) + nextButton.anchor( left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, @@ -71,9 +75,10 @@ class SignUpDetailViewController: GMViewController { paddingLeft: Constant.padding, paddingBottom: Constant.padding, paddingRight: Constant.padding, - height: 40) + height: 40 + ) } - + @objc func didTapNext() { print("save info to firebase") } diff --git a/GoMoney/Scences/Auth/SignUp/SignUpPasswordVC.swift b/GoMoney/Scences/Auth/SignUp/SignUpPasswordVC.swift index 66a4da8..3376542 100644 --- a/GoMoney/Scences/Auth/SignUp/SignUpPasswordVC.swift +++ b/GoMoney/Scences/Auth/SignUp/SignUpPasswordVC.swift @@ -4,7 +4,7 @@ class SignUpPasswordVC: GMAuthViewController { private enum Constant { static let padding: CGFloat = 16 } - + private enum Content { static let title = "Create a new account" static let email = "What's your mail?" @@ -14,23 +14,26 @@ class SignUpPasswordVC: GMAuthViewController { } // MARK: - private properties - + private let viewModel = SignUpViewModel() - + private lazy var fieldEmail: TextFieldSignUp = .init( title: Content.email, type: .emailAddress, - delegate: self) + delegate: self + ) private lazy var fieldPassword: TextFieldSignUp = .init( title: Content.password, secure: true, - delegate: self) + delegate: self + ) private lazy var fieldRePassword: TextFieldSignUp = .init( title: Content.repassword, secure: true, - delegate: self) - + delegate: self + ) + private lazy var errorLabel: GMLabel = .init(style: .smallBold, isCenter: true) { $0.textColor = K.Color.error $0.numberOfLines = 0 @@ -42,10 +45,10 @@ class SignUpPasswordVC: GMAuthViewController { stackView.spacing = Constant.padding stackView.addArrangedSubviews(fieldEmail, fieldPassword, fieldRePassword) stackView.translatesAutoresizingMaskIntoConstraints = false - + return stackView }() - + private lazy var nextButton: GMButton = .init( text: Content.next, color: .white, @@ -53,8 +56,9 @@ class SignUpPasswordVC: GMAuthViewController { $0.layer.cornerRadius = 8 $0.backgroundColor = .primary $0.addTarget(self, action: #selector(self.didTapNext), for: .touchUpInside) - }) - + } + ) + override func configureNavigation() { super.configureNavigation() configureBackButton() @@ -63,19 +67,20 @@ class SignUpPasswordVC: GMAuthViewController { override func setupLayout() { super.setupLayout() - + view.backgroundColor = K.Color.background - + view.addSubviews(inputFields, errorLabel, nextButton) - + inputFields.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 50, paddingLeft: 16, - paddingRight: 16) - + paddingRight: 16 + ) + nextButton.anchor( left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, @@ -83,37 +88,40 @@ class SignUpPasswordVC: GMAuthViewController { paddingLeft: Constant.padding, paddingBottom: Constant.padding, paddingRight: Constant.padding, - height: 40) - + height: 40 + ) + errorLabel.anchor( top: inputFields.bottomAnchor, left: inputFields.leftAnchor, right: inputFields.rightAnchor, - paddingTop: 24) - + paddingTop: 24 + ) + [fieldEmail, fieldPassword, fieldRePassword].forEach { $0.inputField.addTarget( self, action: #selector(self.textFieldDidChanged), - for: .editingChanged) + for: .editingChanged + ) } } - + @objc func didTapNext() { let email: String = fieldEmail.inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let password: String = fieldPassword.inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - + let repassword: String = fieldRePassword.inputField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - + viewModel.validateTextField(email: email, password: password, repassword: repassword) { error in if error != nil { errorLabel.text = error } else { errorLabel.text = "" - + GMLoadingView.shared.startLoadingAnimation(with: "Signing up ...") viewModel.signUpWithEmailAndPassword(email: email, password: password) { [weak self] error in GMLoadingView.shared.endLoadingAnimation() @@ -127,12 +135,12 @@ class SignUpPasswordVC: GMAuthViewController { } } } - + @objc private func textFieldDidChanged() { errorLabel.text = "" } - + private func navigateToDetailVC() { let vc = SignUpDetailViewController() navigationController?.pushViewController(vc, animated: true) diff --git a/GoMoney/Scences/Auth/View/ButtonAuth.swift b/GoMoney/Scences/Auth/View/ButtonAuth.swift index d85110d..c9764d8 100644 --- a/GoMoney/Scences/Auth/View/ButtonAuth.swift +++ b/GoMoney/Scences/Auth/View/ButtonAuth.swift @@ -11,58 +11,58 @@ class ButtonAuth: UIButton { private lazy var btnImg: UIImageView = .build { icon in icon.contentMode = .scaleAspectFill } - + private lazy var btnText: GMLabel = .init(style: .regular, isCenter: true) { $0.textColor = .white } - + private lazy var stackView: UIStackView = .build { stackView in stackView.axis = .horizontal stackView.spacing = Constants.spacing stackView.addArrangedSubviews(self.btnImg, self.btnText) stackView.isUserInteractionEnabled = false } - + private var tapAction: (() -> Void)? - + init(icon: String, text: String, background: UIColor, textColor: UIColor = .white, tapAction: @escaping () -> Void, builder: ((ButtonAuth) -> Void)? = nil) { super.init(frame: .zero) setup() - + if background == .white { layer.borderColor = UIColor.action.cgColor } - + backgroundColor = background - + addSubviews(stackView) - + stackView.centerXToSuperview() - + btnImg.image = UIImage(named: icon) btnImg.anchor(width: Constants.buttonAuthSize, height: Constants.buttonAuthSize) - + btnText.text = text btnText.textColor = textColor - + self.tapAction = tapAction addTarget(self, action: #selector(didTapButton), for: .touchUpInside) - + builder?(self) } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - + private func setup() { layer.cornerRadius = Constants.cornerRadius layer.borderWidth = Constants.borderWidth translatesAutoresizingMaskIntoConstraints = false isUserInteractionEnabled = true } - + @objc private func didTapButton() { tapAction?() diff --git a/GoMoney/Scences/Auth/View/TextFieldSignUp.swift b/GoMoney/Scences/Auth/View/TextFieldSignUp.swift index 14817a0..a077f57 100644 --- a/GoMoney/Scences/Auth/View/TextFieldSignUp.swift +++ b/GoMoney/Scences/Auth/View/TextFieldSignUp.swift @@ -4,19 +4,19 @@ class TextFieldSignUp: UIView { private lazy var inputTitle: GMLabel = .init(style: .regular) { $0.textColor = K.Color.subTitle } - + lazy var inputField: GMTextField = { let textField = GMTextField() textField.font = .nova(18) return textField }() - + private lazy var stackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 10 stackView.addArrangedSubviews(inputTitle, inputField) - + inputTitle.anchor( left: stackView.leftAnchor, right: stackView.rightAnchor @@ -25,11 +25,11 @@ class TextFieldSignUp: UIView { left: inputTitle.leftAnchor, right: inputTitle.rightAnchor ) - + stackView.translatesAutoresizingMaskIntoConstraints = false return stackView }() - + init( title: String = "", placeHolder: String = "", @@ -40,27 +40,27 @@ class TextFieldSignUp: UIView { ) { super.init(frame: .zero) translatesAutoresizingMaskIntoConstraints = false - + inputTitle.text = title inputField.keyboardType = type inputField.placeholder = placeHolder inputField.delegate = delegate inputField.isSecureTextEntry = secure - + addSubview(stackView) - + stackView.anchor( top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor ) - + builder?(self) } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Scences/BioAuth/BioLockViewController.swift b/GoMoney/Scences/BioAuth/BioLockViewController.swift index 1e5da6d..32f1f1c 100644 --- a/GoMoney/Scences/BioAuth/BioLockViewController.swift +++ b/GoMoney/Scences/BioAuth/BioLockViewController.swift @@ -60,14 +60,14 @@ class BioLockViewController: UIViewController { BiometricService.shared.authenticate { [weak self] error in if let error = error { switch error { - case .biometryNotEnrolled: - self?.showGotoSettingsAlert(message: error.message()) - case .canceledBySystem, .canceledByUser: - break - default: - print(error) - print(error.message()) - self?.errorAlert(message: error.message()) + case .biometryNotEnrolled: + self?.showGotoSettingsAlert(message: error.message()) + case .canceledBySystem, .canceledByUser: + break + default: + print(error) + print(error.message()) + self?.errorAlert(message: error.message()) } } else { let tabBarVC = GMTabBarViewController() diff --git a/GoMoney/Scences/Calculator/CalculatorViewController.swift b/GoMoney/Scences/Calculator/CalculatorViewController.swift index b3e0524..8b94529 100644 --- a/GoMoney/Scences/Calculator/CalculatorViewController.swift +++ b/GoMoney/Scences/Calculator/CalculatorViewController.swift @@ -21,7 +21,7 @@ class CalculatorViewController: UIViewController { override var preferredStatusBarStyle: UIStatusBarStyle { return UIStatusBarStyle.lightContent } - + @IBOutlet var displayLabel: UILabel! var firstTyping = false var firstOperand: Double = 0 @@ -48,7 +48,7 @@ class CalculatorViewController: UIViewController { firstTyping = false } } - + @IBAction func numberPressed(_ sender: UIButton) { let number = sender.currentTitle! if firstTyping { @@ -60,19 +60,19 @@ class CalculatorViewController: UIViewController { firstTyping = true } } - + @IBAction func numberActionPressed(_ sender: UIButton) { firstOperand = currentInput actionSign = sender.currentTitle ?? "" firstTyping = false dotIsHere = false } - + func operandsAction(operation: (Double, Double) -> Double) { currentInput = operation(firstOperand, secondOperand) firstTyping = false } - + @IBAction func equality(_ sender: UIButton) { if firstTyping { secondOperand = currentInput @@ -96,14 +96,14 @@ class CalculatorViewController: UIViewController { break } } - - @IBAction func plusMinus(_ sender: UIButton) { + + @IBAction func plusMinus(_: UIButton) { if displayLabel.text != "0" { currentInput = -currentInput } } - - @IBAction func clear(_ sender: UIButton) { + + @IBAction func clear(_: UIButton) { firstOperand = 0 secondOperand = 0 currentInput = 0 @@ -112,8 +112,8 @@ class CalculatorViewController: UIViewController { actionSign = "" dotIsHere = false } - - @IBAction func procent(_ sender: UIButton) { + + @IBAction func procent(_: UIButton) { if firstOperand == 0 { currentInput = currentInput / 100 } else { @@ -121,8 +121,8 @@ class CalculatorViewController: UIViewController { firstTyping = false } } - - @IBAction func dot(_ sender: UIButton) { + + @IBAction func dot(_: UIButton) { if firstTyping, !dotIsHere { displayLabel.text = displayLabel.text! + "." dotIsHere = true diff --git a/GoMoney/Scences/Detail/DetailView.swift b/GoMoney/Scences/Detail/DetailView.swift index ae2163b..d3c1526 100644 --- a/GoMoney/Scences/Detail/DetailView.swift +++ b/GoMoney/Scences/Detail/DetailView.swift @@ -29,7 +29,7 @@ private class DetailCell: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -40,7 +40,8 @@ private class DetailCell: UIView { top: topAnchor, left: leftAnchor, bottom: bottomAnchor, - right: rightAnchor) + right: rightAnchor + ) } func setDetail(key: String, value: String) { @@ -77,7 +78,7 @@ class DetailView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -113,7 +114,8 @@ class DetailView: UIView { DetailCell(key: "Tag", value: tag.name), DetailCell(key: "Amount", value: amount), DetailCell(key: "When", value: DateFormatter.dmy().string(from: transaction.occuredOn)), - DetailCell(key: "Note", value: transaction.note)) + DetailCell(key: "Note", value: transaction.note) + ) if let createdAt = transaction.createdAt { stackView.addArrangedSubview( diff --git a/GoMoney/Scences/Detail/DetailViewController.swift b/GoMoney/Scences/Detail/DetailViewController.swift index e737d94..ee60999 100644 --- a/GoMoney/Scences/Detail/DetailViewController.swift +++ b/GoMoney/Scences/Detail/DetailViewController.swift @@ -5,16 +5,16 @@ class DetailViewController: GMMainViewController { // MARK: - Properties let viewModel = DetailViewModel() - + lazy var detailView = DetailView(transaction: transaction) - + lazy var editButton = GMFloatingButton( image: UIImage(systemName: "pencil")?.color(.white), - text: "Edit") - { [weak self] in + text: "Edit" + ) { [weak self] in self?.editTransaction() } - + private lazy var dropDown: DropDown = .build { [weak self] dropDown in dropDown.dataSource = ["Share as Text", "Share as Image"] dropDown.anchorView = self?.navigationItem.rightBarButtonItems![0] @@ -27,7 +27,7 @@ class DetailViewController: GMMainViewController { } } } - + var transaction: Expense? { didSet { guard let transaction = transaction else { @@ -37,11 +37,11 @@ class DetailViewController: GMMainViewController { detailView.transaction = transaction } } - + private var newTransaction: Expense? - + // MARK: - LifeCircle - + override func configureBackButton() { super.configureBackButton() @@ -50,20 +50,20 @@ class DetailViewController: GMMainViewController { style: .done, target: self, action: #selector(dropDownMenu)), - + UIBarButtonItem(image: UIImage(systemName: "trash")?.color(.white), style: .done, target: self, - action: #selector(deleteTransaction)) + action: #selector(deleteTransaction)), ] } override func setupLayout() { title = "Detail" super.setupLayout() - + view.addSubviews(detailView, editButton) - + detailView.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, @@ -71,35 +71,38 @@ class DetailViewController: GMMainViewController { right: view.rightAnchor, paddingTop: 32, paddingLeft: 16, - paddingRight: 16) - + paddingRight: 16 + ) + editButton.anchor( bottom: view.safeAreaLayoutGuide.bottomAnchor, right: view.rightAnchor, - paddingRight: 16) + paddingRight: 16 + ) } - + // MARK: Functions - + private func editTransaction() { guard transaction != nil else { alert( title: "Error", message: "Can't edit transaction", - actionTitle: "Cancel") + actionTitle: "Cancel" + ) return } - + let vc = EditViewController() vc.transaction = transaction vc.onApply = { newTrans in self.newTransaction = newTrans self.applyNewTransaction(newTrans) } - + present(vc, animated: true) } - + private func applyNewTransaction(_ newTrans: Expense) { detailView.transaction = newTrans viewModel.applyTransaction(newTrans: newTrans) { [weak self] err in @@ -108,17 +111,19 @@ class DetailViewController: GMMainViewController { self?.alert( title: "Error", message: err.localizedDescription, - actionTitle: "Cancel") + actionTitle: "Cancel" + ) } else { self?.notifyDataDidChange() self?.snackBar( message: "Transaction updated successfully!", - actionText: "OK") + actionText: "OK" + ) } } } } - + @objc func deleteTransaction() { guard let transaction = transaction else { alert(title: "Error", message: DataError.noTransactions.localizedDescription) @@ -136,26 +141,26 @@ class DetailViewController: GMMainViewController { } } } - - private func shareAsText(completion: ((Error?) -> Void)? = nil) { + + private func shareAsText(completion _: ((Error?) -> Void)? = nil) { let transaction = newTransaction ?? transaction guard let transaction = transaction else { return } - + let shareText = transaction.createShareText() - + let activityVC = UIActivityViewController(activityItems: [shareText], applicationActivities: nil) present(activityVC, animated: true) } private func shareAsImage() { let image = detailView.asImage() - + let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil) present(activityVC, animated: true) } - + @objc private func dropDownMenu() { dropDown.show() diff --git a/GoMoney/Scences/Edit/EditViewController.swift b/GoMoney/Scences/Edit/EditViewController.swift index d30c35d..834f12c 100644 --- a/GoMoney/Scences/Edit/EditViewController.swift +++ b/GoMoney/Scences/Edit/EditViewController.swift @@ -26,7 +26,8 @@ class EditViewController: GMMainViewController { color: K.Color.primaryColor, tapAction: { [weak self] in self?.dismiss(animated: true) - }) + } + ) private lazy var applyBtn = GMButton( text: "Apply", @@ -49,7 +50,8 @@ class EditViewController: GMMainViewController { self?.onApply?(updated) self?.dismiss(animated: true) } - }) + } + ) override func setupLayout() { super.setupLayout() @@ -65,25 +67,29 @@ class EditViewController: GMMainViewController { right: view.rightAnchor, paddingTop: 32, paddingLeft: 16, - paddingRight: 16) + paddingRight: 16 + ) errorLabel.anchor( top: form.bottomAnchor, left: form.leftAnchor, right: form.rightAnchor, - paddingTop: 24) + paddingTop: 24 + ) cancelBtn.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, paddingTop: 16, - paddingLeft: 16) + paddingLeft: 16 + ) applyBtn.anchor( top: view.safeAreaLayoutGuide.topAnchor, right: view.rightAnchor, paddingTop: 16, - paddingRight: 16) + paddingRight: 16 + ) } private func setView(with transaction: Expense) { @@ -93,7 +99,8 @@ class EditViewController: GMMainViewController { transType: type, textFieldOnChange: { [weak self] in self?.errorLabel.text = nil - }) + } + ) form?.fillTransaction(transaction) } } diff --git a/GoMoney/Scences/Exchange/CountryPickerViewController.swift b/GoMoney/Scences/Exchange/CountryPickerViewController.swift index 5f274de..eb52820 100644 --- a/GoMoney/Scences/Exchange/CountryPickerViewController.swift +++ b/GoMoney/Scences/Exchange/CountryPickerViewController.swift @@ -25,7 +25,7 @@ class CountryPickerViewController: GMMainViewController { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -39,7 +39,7 @@ class CountryPickerViewController: GMMainViewController { } extension CountryPickerViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let country = countries[indexPath.row] let cell = UITableViewCell() cell.textLabel?.text = country.country @@ -48,7 +48,7 @@ extension CountryPickerViewController: UITableViewDelegate, UITableViewDataSourc return cell } - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { return countries.count } @@ -58,4 +58,4 @@ extension CountryPickerViewController: UITableViewDelegate, UITableViewDataSourc onSelectCountry?(country) dismiss(animated: true) } -} \ No newline at end of file +} diff --git a/GoMoney/Scences/Exchange/ExchangeCell.swift b/GoMoney/Scences/Exchange/ExchangeCell.swift index 249d8af..7c50c82 100644 --- a/GoMoney/Scences/Exchange/ExchangeCell.swift +++ b/GoMoney/Scences/Exchange/ExchangeCell.swift @@ -31,7 +31,7 @@ class ExchangeCell: UITableViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Scences/Exchange/ExchangeViewController.swift b/GoMoney/Scences/Exchange/ExchangeViewController.swift index d23123c..5fc882a 100644 --- a/GoMoney/Scences/Exchange/ExchangeViewController.swift +++ b/GoMoney/Scences/Exchange/ExchangeViewController.swift @@ -142,7 +142,7 @@ class ExchangeViewController: GMMainViewController { } extension ExchangeViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { return viewModel.exchanges.count } @@ -157,7 +157,7 @@ extension ExchangeViewController: UITableViewDelegate, UITableViewDataSource { return UITableViewCell() } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat { return 72 } diff --git a/GoMoney/Scences/Exchange/ExchangeViewmodel.swift b/GoMoney/Scences/Exchange/ExchangeViewmodel.swift index cf0c649..f7aa656 100644 --- a/GoMoney/Scences/Exchange/ExchangeViewmodel.swift +++ b/GoMoney/Scences/Exchange/ExchangeViewmodel.swift @@ -53,9 +53,9 @@ class ExchangeViewModel { func getLastestRate(from: CurrencyItem, completion: @escaping (Error?) -> Void) { service.getExchangeRate(from: from.code, to: [.dollar], completion: { result in switch result { - case .failure(let error): + case let .failure(error): completion(error) - case .success(let exchange): + case let .success(exchange): let rates = exchange.rates self.rates = rates diff --git a/GoMoney/Scences/Export/ExportViewController.swift b/GoMoney/Scences/Export/ExportViewController.swift index 7fa13a6..313b02d 100644 --- a/GoMoney/Scences/Export/ExportViewController.swift +++ b/GoMoney/Scences/Export/ExportViewController.swift @@ -5,39 +5,43 @@ class ExportViewController: GMMainViewController { // MARK: Properties private let viewModel = ExportViewModel() - + private let gradientLoadingBar = GradientLoadingBar(isRelativeToSafeArea: false) - + // MARK: Views - + private lazy var csvActionView: GMLabelActionView = .init( text: "Export to CSV", icLeft: UIImage(named: "ic_export_csv"), action: { [weak self] in self?.export(to: .csv) - }) - + } + ) + private lazy var jsonActionView: GMLabelActionView = .init( text: "Export to JSON", icLeft: UIImage(named: "ic_export_json"), action: { [weak self] in self?.export(to: .json) - }) - + } + ) + private lazy var txtActionView: GMLabelActionView = .init( text: "Export to TXT", icLeft: UIImage(named: "ic_text"), action: { [weak self] in self?.export(to: .txt) - }) - + } + ) + private lazy var realmActionView: GMLabelActionView = .init( text: "Export Realm file", icLeft: UIImage(named: "ic_export"), action: { [weak self] in self?.export(to: .realm) - }) - + } + ) + private lazy var stackActionViews: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical @@ -47,28 +51,30 @@ class ExportViewController: GMMainViewController { csvActionView, jsonActionView, txtActionView, - realmActionView) + realmActionView + ) return stackView }() - + override func getTitle() -> String? { return "Export Transactions" } - + override func setupLayout() { super.setupLayout() - + view.addSubviews(stackActionViews) - + stackActionViews.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 32, paddingLeft: 16, - paddingRight: 16) + paddingRight: 16 + ) } - + private func chooseSaveDir(from url: URL) { let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) activityViewController.completionWithItemsHandler = { [weak self] _, completed, _, error in @@ -76,35 +82,39 @@ class ExportViewController: GMMainViewController { self?.alert( title: "Error", message: error.localizedDescription, - actionTitle: "Cancel") + actionTitle: "Cancel" + ) } else { if completed { self?.snackBar( message: "Export Successfully", actionText: "OK", - block: { self?.openFile(url) }) + block: { self?.openFile(url) } + ) } else { self?.snackBar( message: "Aborted", - actionText: "Cancel") + actionText: "Cancel" + ) } } } present(activityViewController, animated: true, completion: nil) } - + private func completion(result: Result) { DispatchQueue.main.async { switch result { - case .failure(let err): + case let .failure(err): self.alert( title: "Export Fail", message: err.localizedDescription, - actionTitle: "Cancel") - case .success(let url): + actionTitle: "Cancel" + ) + case let .success(url): self.chooseSaveDir(from: url) } - + self.gradientLoadingBar.fadeOut() } } @@ -117,7 +127,7 @@ class ExportViewController: GMMainViewController { } } - private func openFile(_ url: URL) { + private func openFile(_: URL) { // TODO: Display file. } } diff --git a/GoMoney/Scences/Home/HomeViewController.swift b/GoMoney/Scences/Home/HomeViewController.swift index c09ed61..1423727 100644 --- a/GoMoney/Scences/Home/HomeViewController.swift +++ b/GoMoney/Scences/Home/HomeViewController.swift @@ -81,7 +81,8 @@ class HomeViewController: GMMainViewController { configureRootTitle( leftImage: K.Image.note, leftTitle: Content.myExpense, - rightImage: K.Image.bell) + rightImage: K.Image.bell + ) } // MARK: - Setup layout @@ -97,33 +98,38 @@ class HomeViewController: GMMainViewController { top: view.safeAreaLayoutGuide.topAnchor, paddingTop: Constant.chartPaddingTop, width: chartSize, - height: chartSize * 2 / 3) + height: chartSize * 2 / 3 + ) chartView.centerX(inView: view) backImage.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, right: view.rightAnchor, - height: (chartSize * 2 / 3 + Constant.chartPaddingTop) / 2) + height: (chartSize * 2 / 3 + Constant.chartPaddingTop) / 2 + ) recentExpenseLabel.anchor( top: chartView.bottomAnchor, left: chartView.leftAnchor, - paddingTop: 16) + paddingTop: 16 + ) tableView.anchor( top: recentExpenseLabel.topAnchor, left: chartView.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, right: chartView.rightAnchor, - paddingTop: 32) + paddingTop: 32 + ) floatingButton.anchor( bottom: view.safeAreaLayoutGuide.bottomAnchor, right: view.rightAnchor, paddingRight: Constant.padding, width: Constant.buttonSize, - height: Constant.buttonSize) + height: Constant.buttonSize + ) } // MARK: Actions @@ -142,7 +148,8 @@ class HomeViewController: GMMainViewController { chartView.setData( viewModel.groupedExpenses, incomeSum: viewModel.incomeSum, - expenseSum: viewModel.expenseSum) + expenseSum: viewModel.expenseSum + ) } // MARK: Methods @@ -159,7 +166,7 @@ class HomeViewController: GMMainViewController { // MARK: - Table Delegate extension HomeViewController: UITableViewDataSource, UITableViewDelegate { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { return viewModel.transactions?.count ?? 0 } @@ -184,14 +191,15 @@ extension HomeViewController: UITableViewDataSource, UITableViewDelegate { } } - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + func tableView(_: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let delete = UIContextualAction(style: .normal, title: "Delete") { _, _, completionHandler in completionHandler(true) if let transaction = self.viewModel.transactions?[indexPath.row] { self.alertDeleteTransaction( transaction: transaction, indexPath: indexPath, - handler: completionHandler) + handler: completionHandler + ) } } @@ -203,7 +211,7 @@ extension HomeViewController: UITableViewDataSource, UITableViewDelegate { return swipe } - func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + func tableView(_: UITableView, leadingSwipeActionsConfigurationForRowAt _: IndexPath) -> UISwipeActionsConfiguration? { let edit = UIContextualAction(style: .normal, title: "Edit") { _, _, completionHandler in // TODO: Go to edit let vc = GMMainViewController() @@ -242,11 +250,12 @@ extension HomeViewController: UITableViewDataSource, UITableViewDelegate { // MARK: - Alert extension HomeViewController { - func alertDeleteTransaction(transaction: Expense, indexPath: IndexPath, handler: @escaping (Bool) -> Void) { + func alertDeleteTransaction(transaction: Expense, indexPath: IndexPath, handler _: @escaping (Bool) -> Void) { let alert = UIAlertController( title: "Delete Transaction", message: "Are you sure to delete \(transaction.tag?.name ?? "transaction")?", - preferredStyle: .alert) + preferredStyle: .alert + ) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) diff --git a/GoMoney/Scences/Home/View/ChartView.swift b/GoMoney/Scences/Home/View/ChartView.swift index 8d292ae..c15bf12 100644 --- a/GoMoney/Scences/Home/View/ChartView.swift +++ b/GoMoney/Scences/Home/View/ChartView.swift @@ -33,7 +33,7 @@ class ChartView: UIView { pieChartView.topAnchor.constraint(equalTo: topAnchor), pieChartView.leftAnchor.constraint(equalTo: leftAnchor), pieChartView.widthAnchor.constraint(equalTo: heightAnchor), - pieChartView.heightAnchor.constraint(equalTo: pieChartView.widthAnchor) + pieChartView.heightAnchor.constraint(equalTo: pieChartView.widthAnchor), ]) monthlyStackView.anchor( @@ -111,7 +111,7 @@ class ChartView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Scences/Home/View/EmptyTransactionView.swift b/GoMoney/Scences/Home/View/EmptyTransactionView.swift index a45ef76..4a4bc75 100644 --- a/GoMoney/Scences/Home/View/EmptyTransactionView.swift +++ b/GoMoney/Scences/Home/View/EmptyTransactionView.swift @@ -36,13 +36,14 @@ class EmptyTransactionView: UIView { private lazy var noTransLabel = GMLabel( text: label, style: .largeBold, - isCenter: true) + isCenter: true + ) private lazy var noTransDetailLabel = GMLabel( text: detailLabel, style: .regular, - isCenter: true) - { + isCenter: true + ) { $0.numberOfLines = 0 } @@ -51,7 +52,8 @@ class EmptyTransactionView: UIView { text: "Add", onTap: { [weak self] in self?.didTapAddTransaction() - }) + } + ) init(viewController: UIViewController) { super.init(frame: .zero) @@ -62,7 +64,7 @@ class EmptyTransactionView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -79,18 +81,21 @@ class EmptyTransactionView: UIView { left: leftAnchor, right: rightAnchor, paddingLeft: 24, - paddingRight: 24) + paddingRight: 24 + ) noTransDetailLabel.anchor( top: noTransLabel.bottomAnchor, left: noTransLabel.leftAnchor, right: noTransLabel.rightAnchor, - paddingTop: 24) + paddingTop: 24 + ) addButton.centerX(inView: self) addButton.anchor( bottom: bottomAnchor, - paddingBottom: 24) + paddingBottom: 24 + ) } private func didTapAddTransaction() { @@ -114,6 +119,7 @@ class EmptyTransactionView: UIView { type: .actionSheet, with: "GoMoney", message: "Add transaction", - actions: [expenseBtn, incomeBtn]) + actions: [expenseBtn, incomeBtn] + ) } } diff --git a/GoMoney/Scences/Home/View/MonthlyExpenseView.swift b/GoMoney/Scences/Home/View/MonthlyExpenseView.swift index f426ff9..60da7cf 100644 --- a/GoMoney/Scences/Home/View/MonthlyExpenseView.swift +++ b/GoMoney/Scences/Home/View/MonthlyExpenseView.swift @@ -21,7 +21,7 @@ class MonthlyExpenseView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Scences/Home/View/RecentExpenseCell.swift b/GoMoney/Scences/Home/View/RecentExpenseCell.swift index 9d7ca25..0aa58b3 100644 --- a/GoMoney/Scences/Home/View/RecentExpenseCell.swift +++ b/GoMoney/Scences/Home/View/RecentExpenseCell.swift @@ -32,7 +32,7 @@ class RecentExpenseCell: UITableViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -44,7 +44,7 @@ class RecentExpenseCell: UITableViewCell { icon.centerYToView(self) NSLayoutConstraint.activate([ icon.widthAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), - icon.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75) + icon.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), ]) stackInfo.centerYToView(self) @@ -66,7 +66,7 @@ class RecentExpenseCell: UITableViewCell { switch transaction.type { case ExpenseType.income.rawValue: labelPrice.textColor = K.Color.saving - case ExpenseType.expense.rawValue: + case ExpenseType.expense.rawValue: labelPrice.textColor = K.Color.debt default: break diff --git a/GoMoney/Scences/Onboard/OnboardCell.swift b/GoMoney/Scences/Onboard/OnboardCell.swift index 5bf7337..214a897 100644 --- a/GoMoney/Scences/Onboard/OnboardCell.swift +++ b/GoMoney/Scences/Onboard/OnboardCell.swift @@ -3,7 +3,7 @@ import UIKit class OnboardCell: UICollectionViewCell { static let identifier = "onboard_cell" - + // MARK: - private properties private lazy var topicImage: UIImageView = .build { topicImage in @@ -12,11 +12,11 @@ class OnboardCell: UICollectionViewCell { topicImage.layer.masksToBounds = true topicImage.anchor(width: 200, height: 200) } - + private lazy var topicLabel: GMLabel = .init(style: .largeBold, isCenter: true) - + private lazy var descriptionLabel: GMLabel = .init(style: .regular, isCenter: true) - + private lazy var centerView: UIStackView = { let view = UIStackView(arrangedSubviews: [topicImage, topicLabel]) view.axis = .vertical @@ -25,7 +25,7 @@ class OnboardCell: UICollectionViewCell { view.spacing = 16 return view }() - + var page: OnboardPageModel? { didSet { topicImage.image = UIImage(named: page?.imageName ?? "") @@ -33,23 +33,23 @@ class OnboardCell: UICollectionViewCell { descriptionLabel.text = page?.descriptionText } } - + override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .white setupLayout() } - + @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - Setup layout private func setupLayout() { addSubviews(centerView, descriptionLabel) - + centerView.centerXToSuperview() centerView.centerYToSuperview(offset: -(windowSafeAreaInsets?.top ?? 0)) @@ -60,9 +60,10 @@ class OnboardCell: UICollectionViewCell { right: rightAnchor, paddingTop: 24, paddingLeft: 8, - paddingRight: 8) + paddingRight: 8 + ) } - + // MARK: public methods public func onWillAppear(_ view: UIView) { diff --git a/GoMoney/Scences/Onboard/OnboardViewController.swift b/GoMoney/Scences/Onboard/OnboardViewController.swift index a70f718..fbdf489 100644 --- a/GoMoney/Scences/Onboard/OnboardViewController.swift +++ b/GoMoney/Scences/Onboard/OnboardViewController.swift @@ -10,9 +10,9 @@ class OnboardViewController: GMViewController { OnboardPageModel(imageName: "onboard_3", topicText: "Analysis", descriptionText: "Easily find the status of your top expenses."), OnboardPageModel(imageName: "start", topicText: "Start Go Money!", descriptionText: "Start Go Money!"), ] - + // MARK: - Private properties - + private lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal @@ -26,11 +26,11 @@ class OnboardViewController: GMViewController { collectionView.translatesAutoresizingMaskIntoConstraints = false return collectionView }() - + private lazy var skipButton: GMButton = .init(text: "Skip", size: 14) { $0.addTarget(self, action: #selector(self.didTapSkipButton), for: .touchUpInside) } - + private lazy var startButton: GMButton = .init(text: "Start", size: 30) { $0.isHidden = true $0.addTarget(self, action: #selector(self.didTapStartButton), for: .touchUpInside) @@ -43,7 +43,7 @@ class OnboardViewController: GMViewController { pageControl.pageIndicatorTintColor = K.Color.contentBackground pageControl.isUserInteractionEnabled = false } - + // MARK: - Life circle override func viewWillAppear(_ animated: Bool) { @@ -55,57 +55,60 @@ class OnboardViewController: GMViewController { super.viewWillDisappear(animated) navigationController?.setNavigationBarHidden(false, animated: animated) } - + // MARK: - Setup layout override func setupLayout() { super.setupLayout() - + view.backgroundColor = .white - + view.addSubviews(collectionView, skipButton, pageControl, startButton) - + collectionView.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, - right: view.rightAnchor) - + right: view.rightAnchor + ) + skipButton.anchor( top: view.safeAreaLayoutGuide.topAnchor, right: view.rightAnchor, - paddingRight: 32) + paddingRight: 32 + ) pageControl.centerXToSuperview() pageControl.anchor( bottom: view.safeAreaLayoutGuide.bottomAnchor, paddingBottom: 8, - height: 30) - + height: 30 + ) + startButton.centerXToSuperview() startButton.anchor(bottom: pageControl.topAnchor, paddingBottom: 8) } - + // MARK: Actions - + @objc private func didTapSkipButton() { let indexPath = IndexPath(item: onboardPages.count - 1, section: 0) collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) pageControl.currentPage = onboardPages.count - 1 skip() } - + @objc private func didTapStartButton() { skip() } - + private func skip() { UserDefaults.standard.set(true, forKey: UserDefaultKey.firstLaunch) - + let signInVC = SignInViewController() let navVC = UINavigationController(rootViewController: signInVC) navVC.modalPresentationStyle = .fullScreen - + if let delegate = view.window?.windowScene?.delegate as? SceneDelegate { delegate.window?.rootViewController = navVC } @@ -115,7 +118,7 @@ class OnboardViewController: GMViewController { // MARK: - CollectionView delegate extension OnboardViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { - func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + func scrollViewWillEndDragging(_: UIScrollView, withVelocity _: CGPoint, targetContentOffset: UnsafeMutablePointer) { let xValue = targetContentOffset.pointee.x let pageNum = Int(xValue / view.frame.width) pageControl.currentPage = pageNum @@ -127,15 +130,15 @@ extension OnboardViewController: UICollectionViewDelegate, UICollectionViewDataS startButton.isHidden = false } } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + + func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, minimumLineSpacingForSectionAt _: Int) -> CGFloat { return 0 } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + + func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { return onboardPages.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OnboardCell.identifier, for: indexPath) as? OnboardCell { let onboardPage = onboardPages[indexPath.row] @@ -144,12 +147,12 @@ extension OnboardViewController: UICollectionViewDelegate, UICollectionViewDataS } return UICollectionViewCell() } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, sizeForItemAt _: IndexPath) -> CGSize { return CGSize(width: collectionView.frame.width, height: collectionView.frame.height) } - - func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + + func collectionView(_: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt _: IndexPath) { let onboardCell = cell as! OnboardCell onboardCell.onWillAppear(view) } diff --git a/GoMoney/Scences/Profile/ProfileView.swift b/GoMoney/Scences/Profile/ProfileView.swift index be50b1f..983f063 100644 --- a/GoMoney/Scences/Profile/ProfileView.swift +++ b/GoMoney/Scences/Profile/ProfileView.swift @@ -45,7 +45,7 @@ class ProfileView: UIView { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Scences/Profile/ProfileViewController.swift b/GoMoney/Scences/Profile/ProfileViewController.swift index adcae68..7bb45ee 100644 --- a/GoMoney/Scences/Profile/ProfileViewController.swift +++ b/GoMoney/Scences/Profile/ProfileViewController.swift @@ -36,7 +36,8 @@ class ProfileViewController: GMMainViewController { private lazy var notificationActionView: GMLabelActionView = .init( text: Content.notification, - icLeft: UIImage(systemName: "bell")) + icLeft: UIImage(systemName: "bell") + ) private lazy var settingsActionView: GMLabelActionView = .init( text: Content.settings, @@ -44,7 +45,8 @@ class ProfileViewController: GMMainViewController { action: { [weak self] in let toolVC = SettingsViewController() self?.navigationController?.pushViewController(toolVC, animated: true) - }) + } + ) private lazy var toolsActionView: GMLabelActionView = .init( text: Content.tool, @@ -52,16 +54,19 @@ class ProfileViewController: GMMainViewController { action: { [weak self] in let toolVC = ToolsViewController() self?.navigationController?.pushViewController(toolVC, animated: true) - }) + } + ) private lazy var aboutActionView: GMLabelActionView = .init( text: Content.aboutUs, - icLeft: UIImage(systemName: "info.circle")) + icLeft: UIImage(systemName: "info.circle") + ) private lazy var helpActionView: GMLabelActionView = .init( text: Content.help, icLeft: UIImage(systemName: "questionmark.circle"), - icRight: nil) + icRight: nil + ) private lazy var stackActions: UIStackView = { let stackView = UIStackView() @@ -73,7 +78,8 @@ class ProfileViewController: GMMainViewController { settingsActionView, toolsActionView, aboutActionView, - helpActionView) + helpActionView + ) return stackView }() @@ -95,7 +101,8 @@ class ProfileViewController: GMMainViewController { override func configureBackButton() { configureRootTitle( leftImage: K.Image.profile, - leftTitle: Content.title) + leftTitle: Content.title + ) } // MARK: - LifeCircle @@ -116,37 +123,43 @@ class ProfileViewController: GMMainViewController { editButton, stackActions, logoutLabel, - logoutButton) + logoutButton + ) profileView.anchor( top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, paddingTop: 32, - paddingLeft: Constant.padding) + paddingLeft: Constant.padding + ) editButton.anchor( right: view.rightAnchor, paddingRight: Constant.padding, width: 30, - height: 30) + height: 30 + ) editButton.centerYToView(profileView) stackActions.anchor( top: profileView.bottomAnchor, left: profileView.leftAnchor, right: editButton.rightAnchor, - paddingTop: 32) + paddingTop: 32 + ) logoutLabel.anchor( top: stackActions.bottomAnchor, left: stackActions.leftAnchor, - paddingTop: 32) + paddingTop: 32 + ) logoutButton.anchor( top: logoutLabel.topAnchor, right: stackActions.rightAnchor, width: 30, - height: 30) + height: 30 + ) logoutButton.centerYToView(logoutLabel) } @@ -179,7 +192,8 @@ class ProfileViewController: GMMainViewController { duration: duration, options: options, animations: {}, - completion: { _ in }) + completion: { _ in } + ) } } } diff --git a/GoMoney/Scences/Profile/Tools/ToolsViewController.swift b/GoMoney/Scences/Profile/Tools/ToolsViewController.swift index dffef41..cfd2b18 100644 --- a/GoMoney/Scences/Profile/Tools/ToolsViewController.swift +++ b/GoMoney/Scences/Profile/Tools/ToolsViewController.swift @@ -12,7 +12,8 @@ class ToolsViewController: GMMainViewController { let storyBoard: UIStoryboard = .init(name: "Calculator", bundle: nil) let calculatorVC = storyBoard.instantiateViewController(withIdentifier: "calculatorStoryboardID") self?.present(calculatorVC, animated: true) - }) + } + ) private lazy var currencyActionViews: GMLabelActionView = .init( text: "Currency Exchanger", @@ -20,7 +21,8 @@ class ToolsViewController: GMMainViewController { action: { [weak self] in let exchangerVC = ExchangeViewController() self?.present(exchangerVC, animated: true) - }) + } + ) private lazy var exportActionViews: GMLabelActionView = .init( text: "Export Data", @@ -28,7 +30,8 @@ class ToolsViewController: GMMainViewController { action: { [weak self] in let exportVC = ExportViewController() self?.navigationController?.pushViewController(exportVC, animated: true) - }) + } + ) private lazy var stackActionViews: UIStackView = { let stackView = UIStackView() @@ -38,7 +41,8 @@ class ToolsViewController: GMMainViewController { stackView.addArrangedSubviews( calculatorActionViews, currencyActionViews, - exportActionViews) + exportActionViews + ) return stackView }() @@ -67,13 +71,15 @@ class ToolsViewController: GMMainViewController { right: view.rightAnchor, paddingTop: Constant.padding, paddingLeft: Constant.padding, - paddingRight: Constant.padding) + paddingRight: Constant.padding + ) animationView.anchor( top: view.safeAreaLayoutGuide.topAnchor, paddingTop: Constant.padding, width: 150, - height: 150) + height: 150 + ) animationView.centerXToView(view) } } diff --git a/GoMoney/Scences/Setting/Cells/SettingFooterView.swift b/GoMoney/Scences/Setting/Cells/SettingFooterView.swift index abda0b2..7f99e33 100644 --- a/GoMoney/Scences/Setting/Cells/SettingFooterView.swift +++ b/GoMoney/Scences/Setting/Cells/SettingFooterView.swift @@ -15,11 +15,12 @@ class SettingsFooterView: UIView { paddingTop: 8, paddingLeft: 16, paddingBottom: 16, - paddingRight: 16) + paddingRight: 16 + ) } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Scences/Setting/Cells/SettingsTableViewAccessoryCell.swift b/GoMoney/Scences/Setting/Cells/SettingsTableViewAccessoryCell.swift index b179783..ac64469 100644 --- a/GoMoney/Scences/Setting/Cells/SettingsTableViewAccessoryCell.swift +++ b/GoMoney/Scences/Setting/Cells/SettingsTableViewAccessoryCell.swift @@ -26,7 +26,7 @@ class SettingsTableViewAccessoryCell: SettingsTableViewCell { } @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Scences/Setting/Cells/SettingsTableViewCell.swift b/GoMoney/Scences/Setting/Cells/SettingsTableViewCell.swift index 50d5c44..9360b6e 100644 --- a/GoMoney/Scences/Setting/Cells/SettingsTableViewCell.swift +++ b/GoMoney/Scences/Setting/Cells/SettingsTableViewCell.swift @@ -11,7 +11,7 @@ class SettingsTableViewCell: UITableViewCell { } @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } diff --git a/GoMoney/Scences/Setting/Cells/SettingsTableViewToggleCell.swift b/GoMoney/Scences/Setting/Cells/SettingsTableViewToggleCell.swift index 9a11f49..9e56087 100644 --- a/GoMoney/Scences/Setting/Cells/SettingsTableViewToggleCell.swift +++ b/GoMoney/Scences/Setting/Cells/SettingsTableViewToggleCell.swift @@ -14,7 +14,7 @@ class SettingsTableViewToggleCell: SettingsTableViewCell { } @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } @@ -29,7 +29,8 @@ private class PaddedSwitch: UIView { frame.size = CGSize( width: switchView.frame.width + PaddedSwitch.Padding, - height: switchView.frame.height) + height: switchView.frame.height + ) switchView.frame.origin = CGPoint(x: PaddedSwitch.Padding, y: 0) } } diff --git a/GoMoney/Scences/Setting/SettingsViewController.swift b/GoMoney/Scences/Setting/SettingsViewController.swift index a339af0..08308dc 100644 --- a/GoMoney/Scences/Setting/SettingsViewController.swift +++ b/GoMoney/Scences/Setting/SettingsViewController.swift @@ -51,7 +51,8 @@ class SettingsViewController: GMMainViewController { title: "Done", style: .plain, target: self, - action: #selector(dismissSettings)) + action: #selector(dismissSettings) + ) doneButton.setTextAttributes(font: .nova(), color: .white) return doneButton @@ -122,7 +123,8 @@ class SettingsViewController: GMMainViewController { toggle.addTarget( self, action: #selector(toggleSwitched(_:)), - for: .valueChanged) + for: .valueChanged + ) toggle.isOn = settings.getValue(for: blockerToggle.setting) as? Bool ?? false toggles[sectionIndex]?[cellIndex] = blockerToggle } @@ -197,7 +199,8 @@ class SettingsViewController: GMMainViewController { type: .actionSheet, with: nil, message: "Please Select a Currency Unit", - actions: actions) + actions: actions + ) } private func selectDateFormat(at indexPath: IndexPath) { @@ -223,7 +226,8 @@ class SettingsViewController: GMMainViewController { type: .actionSheet, with: nil, message: "Please Select a Day Format", - actions: actions) + actions: actions + ) } @objc private func dismissSettings() { @@ -234,11 +238,11 @@ class SettingsViewController: GMMainViewController { // MARK: TableView extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { + func numberOfSections(in _: UITableView) -> Int { return sections.count } - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { numberOfRows(for: sections[section]) } @@ -251,7 +255,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { } } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell: UITableViewCell switch sections[indexPath.section] { @@ -314,7 +318,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { return cell } - func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + func tableView(_: UITableView, viewForFooterInSection section: Int) -> UIView? { if sections[section] == .database { let lastSync = getLastSync() @@ -344,11 +348,11 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { return toggle } - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { return sections[section].headerText } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat { switch sections[section] { case .display: return 70 @@ -414,6 +418,7 @@ extension SettingsViewController { with: nil, message: "Choose sync interval time", actions: actions, - showCancel: true) + showCancel: true + ) } } diff --git a/GoMoney/Scences/Stat/StatBarChartCell.swift b/GoMoney/Scences/Stat/StatBarChartCell.swift index e3b250e..6f3b330 100644 --- a/GoMoney/Scences/Stat/StatBarChartCell.swift +++ b/GoMoney/Scences/Stat/StatBarChartCell.swift @@ -44,7 +44,7 @@ class StatBarChartCell: UITableViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -56,7 +56,8 @@ class StatBarChartCell: UITableViewCell { top: topAnchor, left: leftAnchor, bottom: bottomAnchor, - right: rightAnchor) + right: rightAnchor + ) } private func setBarChart() { @@ -81,7 +82,7 @@ class StatBarChartCell: UITableViewCell { } extension StatBarChartCell: ChartViewDelegate { - func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) { + func chartValueSelected(_ chartView: ChartViewBase, entry _: ChartDataEntry, highlight _: Highlight) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { chartView.highlightValue(nil) } diff --git a/GoMoney/Scences/Stat/StatLineChartCell.swift b/GoMoney/Scences/Stat/StatLineChartCell.swift index 7dffe61..be23eec 100644 --- a/GoMoney/Scences/Stat/StatLineChartCell.swift +++ b/GoMoney/Scences/Stat/StatLineChartCell.swift @@ -42,7 +42,7 @@ class StatLineChartCell: UITableViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -54,7 +54,8 @@ class StatLineChartCell: UITableViewCell { top: topAnchor, left: leftAnchor, bottom: bottomAnchor, - right: rightAnchor) + right: rightAnchor + ) } private func setLineChart() { diff --git a/GoMoney/Scences/Stat/StatTableCell.swift b/GoMoney/Scences/Stat/StatTableCell.swift index 53bfcd6..3afd771 100644 --- a/GoMoney/Scences/Stat/StatTableCell.swift +++ b/GoMoney/Scences/Stat/StatTableCell.swift @@ -19,7 +19,7 @@ class StatTableCell: UITableViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -29,12 +29,13 @@ class StatTableCell: UITableViewCell { top: topAnchor, left: leftAnchor, bottom: bottomAnchor, - right: rightAnchor) + right: rightAnchor + ) } } extension StatTableCell: UITableViewDataSource, UITableViewDelegate { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { return expenses.count } diff --git a/GoMoney/Scences/Stat/StatViewController.swift b/GoMoney/Scences/Stat/StatViewController.swift index da2327c..1d2b82b 100644 --- a/GoMoney/Scences/Stat/StatViewController.swift +++ b/GoMoney/Scences/Stat/StatViewController.swift @@ -44,9 +44,10 @@ class StatViewController: GMMainViewController { text: ExpenseFilter.week.getName(), tapAction: { [weak self] in self?.dropDown.show() - }) { - $0.backgroundColor = .clear } + ) { + $0.backgroundColor = .clear + } private var emptyView: EmptyTransactionView? @@ -55,7 +56,8 @@ class StatViewController: GMMainViewController { override func configureBackButton() { configureRootTitle( leftImage: K.Image.statistic, - leftTitle: "Stats") + leftTitle: "Stats" + ) } // MARK: LifeCircle @@ -97,13 +99,15 @@ class StatViewController: GMMainViewController { bottom: view.bottomAnchor, right: view.rightAnchor, paddingLeft: Constant.padding, - paddingRight: Constant.padding) + paddingRight: Constant.padding + ) filterBtn.anchor( top: tableView.topAnchor, right: tableView.rightAnchor, paddingTop: 28, - width: 90) + width: 90 + ) } // MARK: Methods @@ -137,8 +141,7 @@ class StatViewController: GMMainViewController { view.addSubview(emptyView) emptyView.fillSuperview() } - } - else { + } else { contents.forEach { $0.isHidden = false } emptyView?.removeFromSuperview() emptyView = nil @@ -149,11 +152,11 @@ class StatViewController: GMMainViewController { // MARK: TableView DataSource extension StatViewController: UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { + func numberOfSections(in _: UITableView) -> Int { return sections.count } - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 2 { return viewModel.topExpenses?.count ?? 0 } @@ -184,7 +187,7 @@ extension StatViewController: UITableViewDataSource { return UITableViewCell() } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { switch indexPath.section { case 0: return Constant.chartHeight @@ -195,7 +198,7 @@ extension StatViewController: UITableViewDataSource { } } - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { return sections[section] } } diff --git a/GoMoney/Scences/Tags/IconCollectionVC.swift b/GoMoney/Scences/Tags/IconCollectionVC.swift index 2e17a08..1576efa 100644 --- a/GoMoney/Scences/Tags/IconCollectionVC.swift +++ b/GoMoney/Scences/Tags/IconCollectionVC.swift @@ -84,7 +84,7 @@ class IconCollectionVC: GMMainViewController { } extension IconCollectionVC: UICollectionViewDelegate, UICollectionViewDataSource { - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + public func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { return viewModel.defaultIcons.count } @@ -98,7 +98,7 @@ extension IconCollectionVC: UICollectionViewDelegate, UICollectionViewDataSource return UICollectionViewCell() } - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + public func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { let src = viewModel.defaultIcons[indexPath.row] didSelect?(src) dismiss(animated: true) @@ -106,7 +106,7 @@ extension IconCollectionVC: UICollectionViewDelegate, UICollectionViewDataSource } extension IconCollectionVC: UISearchBarDelegate { - func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + func searchBar(_: UISearchBar, textDidChange searchText: String) { viewModel.filterTags(by: searchText) { [weak self] in self?.collectionView.reloadData() } diff --git a/GoMoney/Scences/Tags/IconPickerCell.swift b/GoMoney/Scences/Tags/IconPickerCell.swift index bccbf48..b177e8e 100644 --- a/GoMoney/Scences/Tags/IconPickerCell.swift +++ b/GoMoney/Scences/Tags/IconPickerCell.swift @@ -11,7 +11,7 @@ class IconPickerCell: UICollectionViewCell { } @available(*, unavailable) - required init?(coder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/GoMoney/Scences/Tags/NewTagViewController.swift b/GoMoney/Scences/Tags/NewTagViewController.swift index c3327da..89e32b8 100644 --- a/GoMoney/Scences/Tags/NewTagViewController.swift +++ b/GoMoney/Scences/Tags/NewTagViewController.swift @@ -81,7 +81,8 @@ class NewTagViewController: GMMainViewController { self?.dismiss(animated: true) } } - }) + } + ) override func setupLayout() { super.setupLayout() @@ -92,17 +93,20 @@ class NewTagViewController: GMMainViewController { saveBtn, tagImage, tagName, - radioGroup) + radioGroup + ) label.anchor( top: view.safeAreaLayoutGuide.topAnchor, - paddingTop: 16) + paddingTop: 16 + ) label.centerX(inView: view) saveBtn.anchor( top: label.topAnchor, right: view.rightAnchor, - paddingRight: 24) + paddingRight: 24 + ) tagImage.anchor( top: label.bottomAnchor, @@ -110,20 +114,23 @@ class NewTagViewController: GMMainViewController { paddingTop: 48, paddingLeft: 24, width: 65, - height: 65) + height: 65 + ) tagName.anchor( top: tagImage.topAnchor, left: tagImage.rightAnchor, right: saveBtn.rightAnchor, paddingLeft: 24, - height: 65) + height: 65 + ) radioGroup.anchor( top: tagName.bottomAnchor, left: tagImage.leftAnchor, right: tagName.rightAnchor, - paddingTop: 24) + paddingTop: 24 + ) } @objc diff --git a/GoMoney/Service/AuthService.swift b/GoMoney/Service/AuthService.swift index 68e5bf2..c290a96 100644 --- a/GoMoney/Service/AuthService.swift +++ b/GoMoney/Service/AuthService.swift @@ -15,12 +15,10 @@ class AuthService { if let error = error { completion(.failure(error)) - } - else if let authResult = authResult { + } else if let authResult = authResult { self.saveUserInfo() completion(.success(authResult)) - } - else { + } else { print("Unknown Error") } } @@ -33,13 +31,11 @@ class AuthService { if let error = error { completion(.failure(error)) - } - else + } else if let authResult = authResult { self.saveUserInfo() completion(.success(authResult)) - } - else { + } else { print("Unknown Error") } } @@ -91,8 +87,7 @@ class AuthService { clearData() completion(.success(true)) - } - catch let signOutError as NSError { + } catch let signOutError as NSError { completion(.failure(signOutError)) } } @@ -124,23 +119,22 @@ class AuthService { func restoreUserData(completion: @escaping (Error?) -> Void) { RemoteService.shared.getAllTags { result in switch result { - case .failure(let err): + case let .failure(err): completion(err) - case .success(let tags): + case let .success(tags): TagService.shared.setTags(tags: tags) { err in if let err = err { completion(err) - } - else { + } else { print("[restore] \(tags.count) tags") RemoteService.shared.getAllTransactions { result in switch result { - case .success(let transactions): + case let .success(transactions): print("[restore] \(transactions.count) transactions") DataService.shared.addTransactions(transactions) { err in completion(err) } - case .failure(let err): + case let .failure(err): completion(err) } } diff --git a/GoMoney/Service/BiometricService.swift b/GoMoney/Service/BiometricService.swift index 7a350e2..1e0212f 100644 --- a/GoMoney/Service/BiometricService.swift +++ b/GoMoney/Service/BiometricService.swift @@ -13,7 +13,7 @@ class BiometricService { case .success: completion(nil) - case .failure(let error): + case let .failure(error): if error == .biometryLockedout { self?.showPasscodeAuthentication(message: error.message(), completion: completion) } else { @@ -29,7 +29,7 @@ class BiometricService { switch result { case .success: completion(nil) - case .failure(let error): + case let .failure(error): completion(error) } } diff --git a/GoMoney/Service/DataService.swift b/GoMoney/Service/DataService.swift index 37beb0d..394841e 100644 --- a/GoMoney/Service/DataService.swift +++ b/GoMoney/Service/DataService.swift @@ -17,7 +17,7 @@ enum DataError: Error { var localizedDescription: String { switch self { - case .transactionNotFound(let transaction): + case let .transactionNotFound(transaction): return "Transaction \(transaction._id) not found!" case .noTransactions: return "There is no transactions!" @@ -44,7 +44,7 @@ class DataService { let expenses = realm.objects(Expense.self) itemsToken = expenses.observe { changes in switch changes { - case .update(_, let deletions, let insertions, let updates): + case let .update(_, deletions, insertions, updates): print("deletions:\(deletions)") print("insertions:\(insertions)") print("updates:\(updates)") @@ -85,13 +85,15 @@ class DataService { .where { $0.type == type.rawValue && $0.occuredOn >= startDate && $0.occuredOn <= endDate } .sorted( byKeyPath: sortBy.rawValue, - ascending: ascending) + ascending: ascending + ) } else { expenses = realm.objects(Expense.self) .where { $0.occuredOn >= startDate && $0.occuredOn <= endDate } .sorted( byKeyPath: sortBy.rawValue, - ascending: ascending) + ascending: ascending + ) } if let expense = expenses { diff --git a/GoMoney/Service/ExchangeService.swift b/GoMoney/Service/ExchangeService.swift index c9f618d..9cee91f 100644 --- a/GoMoney/Service/ExchangeService.swift +++ b/GoMoney/Service/ExchangeService.swift @@ -22,7 +22,7 @@ typealias ExchangeCompletion = (Result) -> Void class ExchangeService { let cache = URLCache.shared - func getExchangeRate(from base: String, to: [CurrencyUnit], completion: @escaping ExchangeCompletion) { + func getExchangeRate(from base: String, to _: [CurrencyUnit], completion: @escaping ExchangeCompletion) { guard let url = URL(string: String(format: "%@?base=%@", ServiceResources.exchangeUri, base)) else { return } var request = URLRequest(url: url) @@ -34,7 +34,7 @@ class ExchangeService { print(reachable) if !reachable { - let data = self.cache.cachedResponse(for: request)?.data + let data = cache.cachedResponse(for: request)?.data if let data = data { do { diff --git a/GoMoney/Service/RemoteService.swift b/GoMoney/Service/RemoteService.swift index 061ff80..6cfdcc5 100644 --- a/GoMoney/Service/RemoteService.swift +++ b/GoMoney/Service/RemoteService.swift @@ -26,7 +26,7 @@ class RemoteService { print("userId=\(userId)") - self.db.collection("transactions") + db.collection("transactions") .document(userId) .collection("transactions") .getDocuments { snapshot, err in @@ -54,7 +54,7 @@ class RemoteService { return } - self.local.getTransaction(by: id) { transaction in + local.getTransaction(by: id) { transaction in guard let transaction = transaction else { print("Transaction \(id) not found at local.") return @@ -80,7 +80,7 @@ class RemoteService { return } - self.db.collection("transactions") + db.collection("transactions") .document(userId) .collection("transactions") .document(id) @@ -113,7 +113,7 @@ extension RemoteService { } print("userId=\(userId)") - let doc = self.db + let doc = db .collection("tags") .document(userId) @@ -134,7 +134,7 @@ extension RemoteService { return } - let doc = self.db + let doc = db .collection("tags") .document(userId) @@ -166,14 +166,14 @@ extension RemoteService { return } - self.db.collection("info") + db.collection("info") .document(userId) .getDocument(as: GMUser.self) { result in completion(result) switch result { - case .success(let user): + case let .success(user): print("City: \(user)") - case .failure(let error): + case let .failure(error): print("Error decoding city: \(error)") } } @@ -184,7 +184,7 @@ extension RemoteService { completion(.failure(DataError.userNotFound)) return } - let ref = self.db.collection("tags") + let ref = db.collection("tags") .document(userId) ref.getDocument { snapShot, err in diff --git a/GoMoney/Service/SyncManager.swift b/GoMoney/Service/SyncManager.swift index 2b6d4a9..b89469a 100644 --- a/GoMoney/Service/SyncManager.swift +++ b/GoMoney/Service/SyncManager.swift @@ -99,6 +99,7 @@ class SyncManager { private func setSyncTime() { SettingsManager.shared.setValue( Date().timeIntervalSince1970, - for: .lastSync) + for: .lastSync + ) } } diff --git a/GoMoney/Service/TrackingService.swift b/GoMoney/Service/TrackingService.swift index aec4a50..f7920d5 100644 --- a/GoMoney/Service/TrackingService.swift +++ b/GoMoney/Service/TrackingService.swift @@ -5,7 +5,7 @@ enum TrackingError: Error { var localizedDescription: String { switch self { - case .transactionNotFound(let id): + case let .transactionNotFound(id): return "Transaction \(id.stringValue) not found." } } diff --git a/GoMoney/ViewModel/Auth/SignInViewModel.swift b/GoMoney/ViewModel/Auth/SignInViewModel.swift index 3947bdb..5bdd656 100644 --- a/GoMoney/ViewModel/Auth/SignInViewModel.swift +++ b/GoMoney/ViewModel/Auth/SignInViewModel.swift @@ -10,8 +10,8 @@ class SignInViewModel { func signInWithEmailAndPassword( email: String, password: String, - completion: @escaping (Error?) -> Void) - { + completion: @escaping (Error?) -> Void + ) { AuthService.shared.signIn(with: email, and: password) { [weak self] authResult in switch authResult { case .success: diff --git a/GoMoney/ViewModel/Auth/SignUpViewModel.swift b/GoMoney/ViewModel/Auth/SignUpViewModel.swift index 1f03d00..960b148 100644 --- a/GoMoney/ViewModel/Auth/SignUpViewModel.swift +++ b/GoMoney/ViewModel/Auth/SignUpViewModel.swift @@ -5,8 +5,8 @@ class SignUpViewModel { func signUpWithEmailAndPassword( email: String, password: String, - completion: @escaping (Error?) -> Void) - { + completion: @escaping (Error?) -> Void + ) { AuthService.shared.signUp(with: email, and: password) { authResult in switch authResult { case .success: diff --git a/GoMoney/ViewModel/Detail/DetailViewModel.swift b/GoMoney/ViewModel/Detail/DetailViewModel.swift index f4430ac..39e194a 100644 --- a/GoMoney/ViewModel/Detail/DetailViewModel.swift +++ b/GoMoney/ViewModel/Detail/DetailViewModel.swift @@ -15,7 +15,8 @@ class DetailViewModel { newTrans: newTrans, completion: { err in completion?(err) - }) + } + ) } func deleteTransaction(_ expense: Expense, completion: ((Error?) -> Void)? = nil) { diff --git a/GoMoney/ViewModel/Home/HomeViewModel.swift b/GoMoney/ViewModel/Home/HomeViewModel.swift index 4692202..338d9e6 100644 --- a/GoMoney/ViewModel/Home/HomeViewModel.swift +++ b/GoMoney/ViewModel/Home/HomeViewModel.swift @@ -3,22 +3,22 @@ import UIKit class HomeViewModel { let realm = try! Realm() - + private let service = DataService.shared - + private let widgetService = WidgetService() - + weak var delegate: DataServiceDelegate? - + var transactions: [Expense]? = [] { didSet { guard let transactions = transactions else { return } - + expenses = [] incomes = [] - + transactions.forEach { if $0.isExpense() { expenses.append($0) @@ -28,36 +28,36 @@ class HomeViewModel { } incomeSum = incomes.reduce(0) { $0 + $1.amount } expenseSum = expenses.reduce(0) { $0 + $1.amount } - + widgetService.updateIncomeWidget(income: incomeSum ?? 0, expense: expenseSum ?? 0) - + groupedExpenses = expenses.groupExpensesByTag() } } - + var incomeSum: Double? var expenseSum: Double? var expenses: [Expense] = [] var incomes: [Expense] = [] var groupedExpenses: [TagAmount] = [] - + func loadExpenses() { delegate?.dataWillLoad() - + service.getExpenses(type: nil) { result in self.transactions = result self.delegate?.dataDidLoad() } } - + func deleteTransaction(_ expense: Expense, completion: ((Error?) -> Void)? = nil) { let index = transactions?.firstIndex(of: expense) - + guard let index = index else { completion?(DataError.transactionNotFound(expense)) return } - + transactions?.remove(at: index) service.deleteExpense(expense: expense) { err in diff --git a/Podfile.lock b/Podfile.lock index 845ef3c..41b3ffd 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -614,24 +614,20 @@ PODS: - Charts/Core (4.1.0): - SwiftAlgorithms (~> 1.0) - DropDown (2.3.13) - - FirebaseAuth (9.6.0): - - FirebaseCore (~> 9.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.7) - - GoogleUtilities/Environment (~> 7.7) - - GTMSessionFetcher/Core (< 3.0, >= 1.7) - - FirebaseCore (9.6.0): - - FirebaseCoreDiagnostics (~> 9.0) - - FirebaseCoreInternal (~> 9.0) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/Logger (~> 7.7) - - FirebaseCoreDiagnostics (9.6.0): - - GoogleDataTransport (< 10.0.0, >= 9.1.4) - - GoogleUtilities/Environment (~> 7.7) - - GoogleUtilities/Logger (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCoreInternal (9.6.0): - - "GoogleUtilities/NSData+zlib (~> 7.7)" - - FirebaseFirestore (9.6.0): + - FirebaseAuth (10.0.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/Environment (~> 7.8) + - GTMSessionFetcher/Core (~> 2.1) + - FirebaseCore (10.0.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreExtension (10.1.0): + - FirebaseCore (~> 10.0) + - FirebaseCoreInternal (10.0.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseFirestore (10.0.0): - abseil/algorithm (~> 1.20211102.0) - abseil/base (~> 1.20211102.0) - abseil/container/flat_hash_map (~> 1.20211102.0) @@ -640,17 +636,17 @@ PODS: - abseil/strings/strings (~> 1.20211102.0) - abseil/time (~> 1.20211102.0) - abseil/types (~> 1.20211102.0) - - FirebaseCore (~> 9.0) + - FirebaseCore (~> 10.0) - "gRPC-C++ (~> 1.44.0)" - leveldb-library (~> 1.22) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseFirestoreSwift (9.6.0): - - FirebaseFirestore (~> 9.0) + - FirebaseFirestoreSwift (10.1.0): + - FirebaseCore (~> 10.0) + - FirebaseCoreExtension (~> 10.0) + - FirebaseFirestore (~> 10.0) + - FirebaseSharedSwift (~> 10.0) + - FirebaseSharedSwift (10.1.0) - Floaty (4.2.0) - - GoogleDataTransport (9.2.0): - - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) - GoogleSignIn (6.2.4): - AppAuth (~> 1.5) - GTMAppAuth (~> 1.3) @@ -780,12 +776,12 @@ SPEC REPOS: - DropDown - FirebaseAuth - FirebaseCore - - FirebaseCoreDiagnostics + - FirebaseCoreExtension - FirebaseCoreInternal - FirebaseFirestore - FirebaseFirestoreSwift + - FirebaseSharedSwift - Floaty - - GoogleDataTransport - GoogleSignIn - GoogleUtilities - GradientLoadingBar @@ -824,14 +820,14 @@ SPEC CHECKSUMS: BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33 Charts: 354f86803d11d9c35de280587fef50d1af063978 DropDown: 8a2116376c1981888557f72ec2ffc9a5e0e456ec - FirebaseAuth: e4a5d3c36e778e41141b91cc861103a441d80bcc - FirebaseCore: 2082fffcd855f95f883c0a1641133eb9bbe76d40 - FirebaseCoreDiagnostics: 99a495094b10a57eeb3ae8efa1665700ad0bdaa6 - FirebaseCoreInternal: bca76517fe1ed381e989f5e7d8abb0da8d85bed3 - FirebaseFirestore: c09ce6d050745a45fb75ebd901005ed3279c3caf - FirebaseFirestoreSwift: d86a0fef1225c6c1ddfae15b3250700867d63013 + FirebaseAuth: 493382cf533cc45e2862b00e9aa4cfe4c98daf71 + FirebaseCore: 97f48a3a567a72b8d4daa0f03c3aadb78df4e995 + FirebaseCoreExtension: 69b966c399abc4ca6dc75006ab87160f81512725 + FirebaseCoreInternal: 5eb3960335da5ea30115d57d39db6988c4ad06f3 + FirebaseFirestore: 5007583f3db2129de8e87f18ee63f4c86f07e7a3 + FirebaseFirestoreSwift: ba0e8ae1dca6cd395c646671a7fe2dcc077db225 + FirebaseSharedSwift: 6966c4de41fba13a4270de1e421e6eee2cf90113 Floaty: e2bd5a0f6f7f70899e26ff2da31081c8bee8d1b0 - GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7 GradientLoadingBar: 3c4246535efdedaf9b471c05caabfdb4b87b40a2 diff --git a/WidgetExtension/IncomeWidget/IncomeWidget.swift b/WidgetExtension/IncomeWidget/IncomeWidget.swift index ba58acc..586c49f 100644 --- a/WidgetExtension/IncomeWidget/IncomeWidget.swift +++ b/WidgetExtension/IncomeWidget/IncomeWidget.swift @@ -2,16 +2,16 @@ import SwiftUI import WidgetKit private struct Provider: TimelineProvider { - func placeholder(in context: Context) -> SimpleEntry { + func placeholder(in _: Context) -> SimpleEntry { SimpleEntry(date: Date()) } - func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) { + func getSnapshot(in _: Context, completion: @escaping (SimpleEntry) -> Void) { let entry = SimpleEntry(date: Date()) completion(entry) } - func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + func getTimeline(in _: Context, completion: @escaping (Timeline) -> Void) { let currentDate = Date() let nextDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)