├── .gitignore ├── AppSeed.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── AppSeed-develop.xcscheme │ └── AppSeed.xcscheme ├── AppSeed ├── Application │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── SceneDelegate.swift ├── Base │ ├── Constants │ │ └── Typealias.swift │ ├── Controllers │ │ ├── BaseNavigationController.swift │ │ └── BaseTabbarController.swift │ ├── Localizable │ │ ├── Localizable.swift │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ └── tr.lproj │ │ │ └── Localizable.strings │ ├── MVVM-R │ │ └── Scene │ │ │ ├── BaseBuilder.swift │ │ │ ├── BaseRouter.swift │ │ │ ├── BaseViewController.swift │ │ │ └── BaseViewModel.swift │ └── UI │ │ ├── Button │ │ └── BaseButton.swift │ │ ├── CollectionView │ │ ├── BaseCollectionView.swift │ │ └── BaseCollectionViewCell.swift │ │ ├── ImageView │ │ ├── BaseImageView.swift │ │ ├── CircleImageView.swift │ │ └── CorneredImageView.swift │ │ ├── ReuseableView.swift │ │ └── TableView │ │ ├── BaseTableView.swift │ │ └── BaseTableViewCell.swift ├── Configuration │ ├── Configuration.swift │ ├── Develop.xcconfig │ └── Release.xcconfig ├── Extensions │ ├── Data+Extension.swift │ ├── UIApplication+Extension.swift │ ├── UICollectionView+Extension.swift │ ├── UIColor+Extension.swift │ ├── UIDevice+Extension.swift │ ├── UIImageView+Extension.swift │ ├── UIStackView+Extension.swift │ ├── UITableView+Extension.swift │ ├── UIView+Extension.swift │ ├── UIViewController+Extension.swift │ └── URL+Extension.swift ├── Helpers │ ├── KeychainHelper.swift │ ├── LoadingHelper.swift │ └── UserDefaultsWrapper.swift ├── Network │ ├── Protocols │ │ ├── QueryParameters.swift │ │ ├── RequestArrayProtocol.swift │ │ ├── RequestObjectProtocol.swift │ │ └── RequestProtocols.swift │ ├── RequestsManager │ │ ├── BaseUrl.swift │ │ └── RequestManager.swift │ ├── Responses │ │ ├── ResponseError.swift │ │ └── ResponseErrorGPT.swift │ └── Services │ │ ├── Gpt │ │ ├── GPTModels.swift │ │ ├── GptService.swift │ │ └── Response │ │ │ ├── ResponseDALLE.swift │ │ │ └── ResponseGPT.swift │ │ ├── RequestDALLE.swift │ │ └── RequestGPT.swift ├── Resources │ ├── Animations │ │ └── lottie-loading.json │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 1x 1024pt 1.png │ │ │ ├── 1x 1024pt.png │ │ │ ├── 1x 29pt.png │ │ │ ├── 1x 40pt.png │ │ │ ├── 1x 76pt.png │ │ │ ├── 2x 20pt.png │ │ │ ├── 2x 29pt 2.png │ │ │ ├── 2x 29pt.png │ │ │ ├── 2x 40pt 2.png │ │ │ ├── 2x 40pt.png │ │ │ ├── 2x 60pt.png │ │ │ ├── 2x 64pt.png │ │ │ ├── 2x 68pt.png │ │ │ ├── 2x 76pt.png │ │ │ ├── 2x 83,5pt.png │ │ │ ├── 3x 20pt 1.png │ │ │ ├── 3x 20pt.png │ │ │ ├── 3x 29pt 1.png │ │ │ ├── 3x 29pt.png │ │ │ ├── 3x 38pt.png │ │ │ ├── 3x 40pt 1.png │ │ │ ├── 3x 40pt 2.png │ │ │ ├── 3x 40pt.png │ │ │ ├── 3x 60pt 1.png │ │ │ ├── 3x 60pt.png │ │ │ ├── 3x 64pt.png │ │ │ └── Contents.json │ │ ├── Background │ │ │ ├── Contents.json │ │ │ └── bg_launch.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── bg_launch.svg │ │ ├── Contents.json │ │ ├── Logo │ │ │ ├── Contents.json │ │ │ ├── Logo_1024.imageset │ │ │ │ ├── 1x 1024pt 1.png │ │ │ │ ├── 1x 1024pt.png │ │ │ │ └── Contents.json │ │ │ └── logo_40.imageset │ │ │ │ ├── 1x 40pt 1.png │ │ │ │ ├── 1x 40pt.png │ │ │ │ └── Contents.json │ │ └── Tutorial │ │ │ ├── Contents.json │ │ │ ├── tutorial_1.imageset │ │ │ ├── Contents.json │ │ │ └── tutorial_1.svg │ │ │ ├── tutorial_2.imageset │ │ │ ├── Contents.json │ │ │ └── tutorial_2.svg │ │ │ └── tutorial_3.imageset │ │ │ ├── Contents.json │ │ │ └── tutorial_3.svg │ ├── Colors.xcassets │ │ ├── AppPalette │ │ │ ├── Contents.json │ │ │ ├── palette1.colorset │ │ │ │ └── Contents.json │ │ │ └── palette2.colorset │ │ │ │ └── Contents.json │ │ ├── Background │ │ │ ├── Contents.json │ │ │ └── backgroundPrimary.colorset │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Shadow │ │ │ ├── Contents.json │ │ │ └── shadowPrimary.colorset │ │ │ │ └── Contents.json │ │ └── Text │ │ │ ├── Contents.json │ │ │ ├── textPrimary.colorset │ │ │ └── Contents.json │ │ │ └── textSecondary.colorset │ │ │ └── Contents.json │ ├── Colors │ │ ├── ColorBackground.swift │ │ ├── ColorText.swift │ │ ├── Colorable.swift │ │ └── Palette.swift │ ├── Images │ │ ├── BackgroundImages.swift │ │ ├── Imageable+Symbolable.swift │ │ ├── Logo.swift │ │ ├── SFSymbols.swift │ │ └── TutorialImage.swift │ └── Info.plist ├── Scenes │ ├── ScrollTest │ │ ├── ScrollTestBuilder.swift │ │ ├── ScrollTestRouter.swift │ │ ├── ScrollTestViewController.swift │ │ └── ScrollTestViewModel.swift │ ├── Splash │ │ ├── Localizable │ │ │ ├── SplashLocalizable.swift │ │ │ ├── en.lproj │ │ │ │ └── SplashLocalizable.strings │ │ │ └── tr.lproj │ │ │ │ └── SplashLocalizable.strings │ │ ├── SplashBuilder.swift │ │ ├── SplashLocalizable.swift │ │ ├── SplashRouter.swift │ │ ├── SplashViewController.swift │ │ ├── SplashViewModel.swift │ │ ├── en.lproj │ │ │ └── SplashLocalizable.strings │ │ └── tr.lproj │ │ │ └── SplashLocalizable.strings │ └── Tutorial │ │ ├── Cells │ │ └── TutorialCell.swift │ │ ├── Localizable │ │ ├── TutorialLocalizable.swift │ │ ├── en.lproj │ │ │ └── TutorialLocalizable.strings │ │ └── tr.lproj │ │ │ └── TutorialLocalizable.strings │ │ ├── TutorialBuilder.swift │ │ ├── TutorialRouter.swift │ │ ├── TutorialViewController.swift │ │ ├── TutorialViewModel.swift │ │ └── UIModel │ │ └── TutorialUIModel.swift └── UIComponents │ └── View │ └── HudView.swift ├── README.md ├── Renamer └── Renamer.swift ├── Templates ├── AppSeedScene.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── TemplateInfo.plist │ ├── ___VARIABLE_name___Builder.swift │ ├── ___VARIABLE_name___Router.swift │ ├── ___VARIABLE_name___ViewController.swift │ └── ___VARIABLE_name___ViewModel.swift └── install.swift └── txts ├── privacy_policy_dreamcatcher.txt └── terms_of_use_dreamcatcher.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | Packages/ 41 | Package.pins 42 | Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store 92 | -------------------------------------------------------------------------------- /AppSeed.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppSeed.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppSeed.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "fc12012499b96bab028d705701fb98e701cf555f5118a812f096dd7812098a46", 3 | "pins" : [ 4 | { 5 | "identity" : "alamofire", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/Alamofire/Alamofire", 8 | "state" : { 9 | "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", 10 | "version" : "5.10.2" 11 | } 12 | }, 13 | { 14 | "identity" : "jsonfellow", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/tunayyalverr/JsonFellow", 17 | "state" : { 18 | "revision" : "78313037364aaf373eeda366f1a8b7569b59d3ac", 19 | "version" : "1.0.2" 20 | } 21 | }, 22 | { 23 | "identity" : "kingfisher", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/onevcat/Kingfisher.git", 26 | "state" : { 27 | "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", 28 | "version" : "7.12.0" 29 | } 30 | }, 31 | { 32 | "identity" : "lottie-spm", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/airbnb/lottie-spm.git", 35 | "state" : { 36 | "revision" : "8c6edf4f0fa84fe9c058600a4295eb0c01661c69", 37 | "version" : "4.5.1" 38 | } 39 | }, 40 | { 41 | "identity" : "snapkit", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/SnapKit/SnapKit", 44 | "state" : { 45 | "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", 46 | "version" : "5.7.1" 47 | } 48 | } 49 | ], 50 | "version" : 3 51 | } 52 | -------------------------------------------------------------------------------- /AppSeed.xcodeproj/xcshareddata/xcschemes/AppSeed-develop.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 51 | 53 | 59 | 60 | 61 | 62 | 68 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /AppSeed.xcodeproj/xcshareddata/xcschemes/AppSeed.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /AppSeed/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 5.01.2025. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 13 | return true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AppSeed/Application/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /AppSeed/Application/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | var window: UIWindow? 12 | 13 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 14 | guard let windowScene = scene as? UIWindowScene else { return } 15 | setInitialScene(with: windowScene) 16 | } 17 | 18 | // MARK: - Initial Scene 19 | private func setInitialScene(with windowScene: UIWindowScene) { 20 | let splashViewController = SplashBuilder().build() 21 | window = UIWindow(windowScene: windowScene) 22 | window?.rootViewController = splashViewController 23 | window?.makeKeyAndVisible() 24 | } 25 | 26 | // MARK: - Set to Window 27 | static func setToWindow(_ viewController: UIViewController) { 28 | guard let sceneDelegate = UIApplication.shared.sceneDelegate, 29 | let window = sceneDelegate.window else { 30 | debugPrint("Error: Unable to access SceneDelegate's window.") 31 | return 32 | } 33 | 34 | UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve) { 35 | window.rootViewController = viewController 36 | } 37 | window.makeKeyAndVisible() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AppSeed/Base/Constants/Typealias.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Typealias.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 9.08.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | public typealias EmptyClosure = () -> Void 11 | public typealias AnyClosure = (T) -> Void 12 | public typealias OptinalAnyClosure = (T?) -> Void 13 | 14 | public typealias CodableAnyClosure = ((T?) -> Void) 15 | public typealias CodableArrayClosure = (([T?]?) -> Void) 16 | public typealias ResponseErrorClosure = ((ResponseError?) -> Void) 17 | public typealias ResponseErrorGPTClosure = ((ResponseErrorGPT?) -> Void) 18 | -------------------------------------------------------------------------------- /AppSeed/Base/Controllers/BaseNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNavigationController.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseNavigationController: UINavigationController { 11 | 12 | // MARK: - Life Cycle 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | prepare() 16 | } 17 | 18 | //MARK: - Prepare 19 | private func prepare() { 20 | setBarApperance() 21 | } 22 | 23 | //MARK: - Private 24 | private func setBarApperance() { 25 | navigationBar.shadowImage = UIImage() 26 | navigationBar.isTranslucent = true 27 | 28 | let appearance = UINavigationBarAppearance() 29 | appearance.backgroundColor = ColorBackground.backgroundSecondary.color 30 | appearance.titleTextAttributes = [.foregroundColor: ColorText.textPrimary.color] 31 | appearance.largeTitleTextAttributes = [.foregroundColor: ColorText.textPrimary.color] 32 | appearance.backButtonAppearance = UIBarButtonItemAppearance() 33 | appearance.backButtonAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.clear] 34 | appearance.shadowColor = nil 35 | 36 | UINavigationBar.appearance().tintColor = Palette.palette2.color 37 | UINavigationBar.appearance().standardAppearance = appearance 38 | UINavigationBar.appearance().compactAppearance = appearance 39 | UINavigationBar.appearance().scrollEdgeAppearance = appearance 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AppSeed/Base/Controllers/BaseTabbarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTabbarController.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 10.08.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | // TODO: - Update when needed 11 | class BaseTabbarController: UITabBarController, UITabBarControllerDelegate { 12 | // MARK: - Life Cycle 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | prepare() 16 | } 17 | 18 | //MARK: - Prepare 19 | func prepare() { 20 | tabBar.shadowImage = UIImage() 21 | tabBar.backgroundImage = UIImage() 22 | tabBar.clipsToBounds = false 23 | tabBar.backgroundColor = ColorBackground.backgroundPrimary.color 24 | tabBar.tintColor = ColorText.textPrimary.color 25 | tabBar.unselectedItemTintColor = ColorText.textPrimary.color 26 | definesPresentationContext = true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AppSeed/Base/Localizable/Localizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Localizable.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Localizable { 11 | private static func localized(_ key: String) -> String { 12 | NSLocalizedString(key, tableName: "Localizable", bundle: .main, value: "", comment: "") 13 | } 14 | 15 | static var ok: String { 16 | localized("ok") 17 | } 18 | static var cancel: String { 19 | localized("cancel") 20 | } 21 | static var save: String { 22 | localized("save") 23 | } 24 | static var delete: String { 25 | localized("delete") 26 | } 27 | static var edit: String { 28 | localized("edit") 29 | } 30 | static var yes: String { 31 | localized("yes") 32 | } 33 | static var no: String { 34 | localized("no") 35 | } 36 | static var retry: String { 37 | localized("retry") 38 | } 39 | static var close: String { 40 | localized("close") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AppSeed/Base/Localizable/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "ok" = "Ok"; 10 | "cancel" = "Cancel"; 11 | "save" = "Save"; 12 | "delete" = "Delete"; 13 | "edit" = "Edit"; 14 | "yes" = "Yes"; 15 | "no" = "No"; 16 | "retry" = "Retry"; 17 | "close" = "Close"; 18 | -------------------------------------------------------------------------------- /AppSeed/Base/Localizable/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "ok" = "Tamam"; 10 | "cancel" = "Vazgeç"; 11 | "save" = "Kaydet"; 12 | "delete" = "Sil"; 13 | "edit" = "Düzenle"; 14 | "yes" = "Evet"; 15 | "no" = "Hayır"; 16 | "retry" = "Tekrar Dene"; 17 | "close" = "Kapat"; 18 | -------------------------------------------------------------------------------- /AppSeed/Base/MVVM-R/Scene/BaseBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseBuilder.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol BaseBuilder { 11 | func build() -> UIViewController 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Base/MVVM-R/Scene/BaseRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseRouter.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol BaseRouterProtocol: AnyObject { 11 | var viewController: UIViewController? { get set } 12 | } 13 | 14 | class BaseRouter: BaseRouterProtocol { 15 | weak var viewController: UIViewController? 16 | 17 | init() { } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Base/MVVM-R/Scene/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseViewController: UIViewController { 11 | // MARK: - UI 12 | private lazy var scrollView: UIScrollView = { 13 | let view = UIScrollView() 14 | view.backgroundColor = ColorBackground.backgroundPrimary.color 15 | view.showsVerticalScrollIndicator = false 16 | view.showsHorizontalScrollIndicator = false 17 | return view 18 | }() 19 | private lazy var stackView: UIStackView = { 20 | let view = UIStackView() 21 | view.axis = .vertical 22 | return view 23 | }() 24 | 25 | // MARK: - Properties 26 | var isScrollable: Bool { false } 27 | 28 | // MARK: - Dependency 29 | var viewModel: V? 30 | var router: R? 31 | 32 | // MARK: - Init 33 | required init(viewModel: V, router: R) { 34 | self.viewModel = viewModel 35 | self.router = router 36 | super.init(nibName: nil, bundle: nil) 37 | self.router?.viewController = self 38 | bindViewModel() 39 | } 40 | 41 | required init?(coder: NSCoder) { 42 | fatalError("init(coder:) has not been implemented") 43 | } 44 | 45 | deinit { 46 | debugPrint("🔴 deinit: ", self.description) 47 | } 48 | 49 | // MARK: - Life Cycle 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | prepare() 53 | } 54 | 55 | // MARK: - Prapare 56 | func prepare() { 57 | debugPrint("🟢 didload: ", self.description) 58 | view.backgroundColor = ColorBackground.backgroundPrimary.color 59 | draw() 60 | } 61 | 62 | // MARK: - Bind 63 | func bindViewModel() { 64 | // handle viewModel closures here 65 | } 66 | } 67 | 68 | // MARK: - Draw 69 | extension BaseViewController { 70 | private func draw() { 71 | if isScrollable { 72 | view.addSubview(scrollView) 73 | scrollView.snp.makeConstraints { 74 | $0.edges.equalToSuperview() 75 | } 76 | scrollView.addSubview(stackView) 77 | stackView.snp.makeConstraints { 78 | $0.edges.equalToSuperview() 79 | $0.width.equalToSuperview() 80 | } 81 | } 82 | } 83 | } 84 | 85 | // MARK: - ScrollView 86 | extension BaseViewController { 87 | func addToScrollView(_ view: UIView) { 88 | guard isScrollable else { return } 89 | stackView.addArrangedSubview(view) 90 | } 91 | 92 | func addToScrollView(_ views: [UIView]) { 93 | guard isScrollable else { return } 94 | views.forEach { stackView.addArrangedSubview($0) } 95 | } 96 | 97 | func setScrollViewInsets(top: CGFloat, bottom: CGFloat) { 98 | guard isScrollable else { return } 99 | scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0) 100 | } 101 | 102 | func setScrollViewStackInsets(left: CGFloat, right: CGFloat) { 103 | guard isScrollable else { return } 104 | stackView.snp.updateConstraints { 105 | $0.left.equalToSuperview().offset(left) 106 | $0.right.equalToSuperview().offset(-right) 107 | $0.width.equalToSuperview().inset((left + right) / 2) 108 | } 109 | } 110 | 111 | func setScrollViewStackSpacing(_ spacing: CGFloat) { 112 | guard isScrollable else { return } 113 | stackView.spacing = spacing 114 | } 115 | 116 | func setSpacingAfter(spacing: CGFloat, after view: UIView) { 117 | guard isScrollable else { return } 118 | stackView.setCustomSpacing(spacing, after: view) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /AppSeed/Base/MVVM-R/Scene/BaseViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewModel.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit.UIViewController 9 | 10 | // MARK: - Source 11 | protocol BaseViewModelDataSource { } 12 | 13 | // MARK: - Closure 14 | protocol BaseViewModelClosureSource { } 15 | 16 | // MARK: - Function 17 | protocol BaseViewModelFunctionSource { } 18 | 19 | // MARK: - Protocol 20 | protocol BaseViewModelProtocol: AnyObject, BaseViewModelDataSource, BaseViewModelClosureSource, BaseViewModelFunctionSource { } 21 | 22 | class BaseViewModel: BaseViewModelProtocol { 23 | // MARK: - Closures 24 | 25 | deinit { 26 | // TODO: - create a custom logger 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/Button/BaseButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseButton.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 10.08.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseButton: UIButton { 11 | // MARK: - Properties 12 | override var isHighlighted: Bool { 13 | didSet { 14 | alpha = isHighlighted ? 0.5 : 1.0 15 | } 16 | } 17 | 18 | override var isEnabled: Bool { 19 | didSet { 20 | alpha = isEnabled ? 1.0 : 0.5 21 | } 22 | } 23 | 24 | var style: Style? 25 | 26 | // MARK: - Init 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | prepare() 30 | } 31 | 32 | required init?(coder: NSCoder) { 33 | super.init(coder: coder) 34 | prepare() 35 | } 36 | 37 | init(style: Style) { 38 | self.style = style 39 | super.init(frame: .zero) 40 | prepare() 41 | } 42 | 43 | // MARK: - Life Cycle 44 | override func layoutSubviews() { 45 | super.layoutSubviews() 46 | roundCorners(radius: 12) 47 | } 48 | 49 | // MARK: - Prepare 50 | private func prepare() { 51 | titleLabel?.font = style?.font 52 | setTitleColor(style?.textColor, for: .normal) 53 | backgroundColor = style?.backgroundColor 54 | 55 | } 56 | } 57 | 58 | // MARK: - Style 59 | extension BaseButton { 60 | enum Style { 61 | case primary 62 | 63 | var font: UIFont { 64 | switch self { 65 | case .primary: 66 | return .systemFont(ofSize: 16, weight: .bold) 67 | } 68 | } 69 | 70 | var textColor: UIColor { 71 | switch self { 72 | case .primary: 73 | return ColorText.textPrimary.color 74 | } 75 | } 76 | 77 | var backgroundColor: UIColor { 78 | switch self { 79 | case .primary: 80 | return Palette.palette1.color 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/CollectionView/BaseCollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseCollectionView: UICollectionView { 11 | // MARK: - Init 12 | override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { 13 | super.init(frame: frame, collectionViewLayout: layout) 14 | 15 | prepare() 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | 21 | prepare() 22 | } 23 | 24 | // MARK: - Prepare 25 | func prepare() { 26 | showsVerticalScrollIndicator = false 27 | showsHorizontalScrollIndicator = false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/CollectionView/BaseCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionViewCell.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseCollectionViewCell: UICollectionViewCell, ReusableView { 11 | // MARK: - Init 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | prepare() 15 | } 16 | 17 | required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | prepare() 20 | } 21 | 22 | // MARK: - Prepare 23 | private func prepare() {} 24 | } 25 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/ImageView/BaseImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseImageView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseImageView: UIImageView { 11 | // MARK: - Init 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | prepare() 15 | } 16 | 17 | required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | prepare() 20 | } 21 | 22 | // MARK: - Prepare 23 | private func prepare() { 24 | backgroundColor = .clear 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/ImageView/CircleImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircleImageView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class CircleImageView: UIImageView { 11 | // MARK: - Init 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | prepare() 15 | } 16 | 17 | required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | prepare() 20 | } 21 | 22 | // MARK: - Life Cycle 23 | override func layoutSubviews() { 24 | super.layoutSubviews() 25 | roundCorners(radius: frame.height / 2) 26 | } 27 | 28 | // MARK: - Prepare 29 | private func prepare() {} 30 | } 31 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/ImageView/CorneredImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorneredImageView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | class CorneredImageView: BaseImageView { 13 | 14 | @IBInspectable 15 | var cornerRadius: CGFloat = 16 16 | 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | prepare() 21 | } 22 | 23 | override func prepareForInterfaceBuilder() { 24 | super.prepareForInterfaceBuilder() 25 | awakeFromNib() 26 | } 27 | 28 | // MARK: - Prepare 29 | private func prepare() { 30 | layer.masksToBounds = true 31 | layer.cornerRadius = cornerRadius 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/ReuseableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReuseableView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 15.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol ReusableView: AnyObject { 11 | static var identifier: String { get } 12 | } 13 | 14 | extension ReusableView where Self: UIView { 15 | static var identifier: String { 16 | return String(describing: self) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/TableView/BaseTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseTableView: UITableView { 11 | // MARK: - Init 12 | override init(frame: CGRect, style: UITableView.Style) { 13 | super.init(frame: frame, style: style) 14 | 15 | prepare() 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | 21 | prepare() 22 | } 23 | 24 | // MARK: - Prepare 25 | func prepare() { 26 | showsVerticalScrollIndicator = false 27 | showsHorizontalScrollIndicator = false 28 | separatorStyle = .none 29 | if #available(iOS 15.0, *) { 30 | sectionHeaderTopPadding = 0 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AppSeed/Base/UI/TableView/BaseTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewCell.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseTVCell: UITableViewCell, ReusableView { 11 | // MARK: - Init 12 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: style, reuseIdentifier: reuseIdentifier) 14 | 15 | prepare() 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | 21 | prepare() 22 | } 23 | 24 | // MARK: - Prepare 25 | private func prepare() { 26 | selectionStyle = .none 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AppSeed/Configuration/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | final class Configuration { 11 | enum Error: Swift.Error { 12 | case missingKey, invalidValue 13 | } 14 | 15 | static var isRelease: Bool { 16 | #if Release 17 | return true 18 | #else 19 | return false 20 | #endif 21 | } 22 | 23 | static var isDevelop: Bool { 24 | #if Develop 25 | return true 26 | #else 27 | return false 28 | #endif 29 | } 30 | 31 | static func value(for key: String) throws -> T where T: LosslessStringConvertible { 32 | guard let object = Bundle.main.object(forInfoDictionaryKey: key) else { 33 | throw Error.missingKey 34 | } 35 | switch object { 36 | case let value as T: 37 | return value 38 | case let string as String: 39 | guard let value = T(string) else { fallthrough } 40 | return value 41 | default: 42 | throw Error.invalidValue 43 | } 44 | } 45 | } 46 | 47 | extension Configuration { 48 | static var isIpad: Bool { 49 | UIDevice.isIpad 50 | } 51 | 52 | static var appVersion: String { 53 | UIApplication.appVersion 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /AppSeed/Configuration/Develop.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Develop.xcconfig 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | 11 | PRODUCT_BUNDLE_IDENTIFIER = com.devno39.AppSeed.develop 12 | PRODUCT_APP_NAME = AppSeed-develop 13 | API_BASE_PATH = https:/$()/ 14 | -------------------------------------------------------------------------------- /AppSeed/Configuration/Release.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Release.xcconfig 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | 11 | PRODUCT_BUNDLE_IDENTIFIER = com.devno39.AppSeed.release 12 | PRODUCT_APP_NAME = AppSeed 13 | API_BASE_PATH = https:/$()/ 14 | -------------------------------------------------------------------------------- /AppSeed/Extensions/Data+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Data { 11 | var prettyString: NSString? { 12 | return NSString(data: self, encoding: String.Encoding.utf8.rawValue) ?? nil 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIApplication+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIApplication { 11 | var appDelegate: AppDelegate? { 12 | return UIApplication.shared.delegate as? AppDelegate 13 | } 14 | 15 | var sceneDelegate: SceneDelegate? { 16 | connectedScenes 17 | .first { $0.activationState == .foregroundActive } 18 | .flatMap { $0.delegate as? SceneDelegate } 19 | } 20 | 21 | static let appVersion: String = { 22 | if let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String { 23 | return appVersion 24 | } else { 25 | return "" 26 | } 27 | }() 28 | 29 | static let appBuild: String = { 30 | if let appBuild = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String { 31 | return appBuild 32 | } else { 33 | return "" 34 | } 35 | }() 36 | 37 | static func appVersionAndBuild() -> String { 38 | let releaseVersion = "v. " + appVersion 39 | let buildVersion = " b. " + appBuild 40 | return Configuration.isRelease ? releaseVersion : releaseVersion + buildVersion 41 | } 42 | 43 | func topMostViewController() -> UIViewController? { 44 | return rootWindow()?.rootViewController?.topMostViewController() 45 | } 46 | 47 | func rootWindow() -> UIWindow? { 48 | return connectedScenes 49 | .compactMap { $0 as? UIWindowScene } 50 | .flatMap { $0.windows } 51 | .first { $0.rootViewController is UITabBarController } 52 | } 53 | 54 | func keyWindow() -> UIWindow? { 55 | return connectedScenes 56 | .compactMap { $0 as? UIWindowScene } 57 | .flatMap { $0.windows } 58 | .first { $0.isKeyWindow } 59 | } 60 | 61 | func openApplicationSettings() { 62 | if let url = URL(string: UIApplication.openSettingsURLString), 63 | UIApplication.shared.canOpenURL((url)) { 64 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UICollectionView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 15.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UICollectionView { 11 | func register(_: T.Type) where T: BaseCollectionViewCell { 12 | register(T.self, forCellWithReuseIdentifier: T.identifier) 13 | } 14 | 15 | func dequeueReusableCell(for indexPath: IndexPath) -> T where T: ReusableView { 16 | guard let cell = dequeueReusableCell(withReuseIdentifier: T.identifier, for: indexPath) as? T else { 17 | fatalError("Could not dequeue cell with identifier: \(T.identifier)") 18 | } 19 | return cell 20 | } 21 | 22 | func dequeueReusableCell(ofKind: String, for indexPath: IndexPath) -> T where T: ReusableView { 23 | guard let cell = dequeueReusableSupplementaryView(ofKind: ofKind, withReuseIdentifier: T.identifier, for: indexPath) as? T else { 24 | fatalError("Could not dequeue cell with identifier: \(T.identifier)") 25 | } 26 | return cell 27 | } 28 | 29 | func registerHeader(_: T.Type) where T: ReusableView { 30 | register(T.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: T.identifier) 31 | } 32 | 33 | func registerFooter(_: T.Type) where T: ReusableView { 34 | register(T.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: T.identifier) 35 | } 36 | 37 | func isValid(indexPath: IndexPath) -> Bool { 38 | guard indexPath.section < numberOfSections, 39 | indexPath.row < numberOfItems(inSection: indexPath.section) 40 | else { return false } 41 | return true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIColor+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIColor { 11 | // MARK: - Hex 12 | convenience init(red: Int, green: Int, blue: Int) { 13 | assert(red >= 0 && red <= 255, "Invalid red component") 14 | assert(green >= 0 && green <= 255, "Invalid green component") 15 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 16 | 17 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 18 | } 19 | 20 | convenience init(rgb: Int) { 21 | self.init(red: (rgb >> 16) & 0xFF, green: (rgb >> 8) & 0xFF, blue: rgb & 0xFF) 22 | } 23 | 24 | // MARK: - 1 point Image 25 | func as1ptImage() -> UIImage { 26 | UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) 27 | setFill() 28 | UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1)) 29 | let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage() 30 | UIGraphicsEndImageContext() 31 | return image 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIDevice+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIDevice { 11 | static let isIpad = current.userInterfaceIdiom == .pad 12 | 13 | static let osVersion: String = { 14 | UIDevice.current.systemVersion 15 | }() 16 | 17 | static let osName: String = { 18 | UIDevice.current.systemName 19 | }() 20 | 21 | static let deviceUuid: String = { 22 | if let uuid = UIDevice.current.identifierForVendor?.uuidString { 23 | return uuid 24 | } else { 25 | return "" 26 | } 27 | }() 28 | 29 | static let hasNotch: Bool = { 30 | if #available(iOS 11.0, *) { 31 | if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { 32 | let top = windowScene.windows.first?.safeAreaInsets.top ?? 0 33 | return top > 20 34 | } 35 | } 36 | return false 37 | }() 38 | 39 | static let modelName: String = { 40 | var systemInfo = utsname() 41 | uname(&systemInfo) 42 | let machineMirror = Mirror(reflecting: systemInfo.machine) 43 | let identifier = machineMirror.children.reduce("") { identifier, element in 44 | guard let value = element.value as? Int8, value != 0 else { return identifier } 45 | return identifier + String(UnicodeScalar(UInt8(value))) 46 | } 47 | 48 | // swiftlint:disable:this cyclomatic_complexity 49 | func mapToDevice(identifier: String) -> String { 50 | #if os(iOS) 51 | switch identifier { 52 | case "iPod5,1": 53 | return "iPod touch (5th generation)" 54 | case "iPod7,1": 55 | return "iPod touch (6th generation)" 56 | case "iPod9,1": 57 | return "iPod touch (7th generation)" 58 | case "iPhone3,1", "iPhone3,2", "iPhone3,3": 59 | return "iPhone 4" 60 | case "iPhone4,1": 61 | return "iPhone 4s" 62 | case "iPhone5,1", "iPhone5,2": 63 | return "iPhone 5" 64 | case "iPhone5,3", "iPhone5,4": 65 | return "iPhone 5c" 66 | case "iPhone6,1", "iPhone6,2": 67 | return "iPhone 5s" 68 | case "iPhone7,2": 69 | return "iPhone 6" 70 | case "iPhone7,1": 71 | return "iPhone 6 Plus" 72 | case "iPhone8,1": 73 | return "iPhone 6s" 74 | case "iPhone8,2": 75 | return "iPhone 6s Plus" 76 | case "iPhone9,1", "iPhone9,3": 77 | return "iPhone 7" 78 | case "iPhone9,2", "iPhone9,4": 79 | return "iPhone 7 Plus" 80 | case "iPhone10,1", "iPhone10,4": 81 | return "iPhone 8" 82 | case "iPhone10,2", "iPhone10,5": 83 | return "iPhone 8 Plus" 84 | case "iPhone10,3", "iPhone10,6": 85 | return "iPhone X" 86 | case "iPhone11,2": 87 | return "iPhone XS" 88 | case "iPhone11,4", "iPhone11,6": 89 | return "iPhone XS Max" 90 | case "iPhone11,8": 91 | return "iPhone XR" 92 | case "iPhone12,1": 93 | return "iPhone 11" 94 | case "iPhone12,3": 95 | return "iPhone 11 Pro" 96 | case "iPhone12,5": 97 | return "iPhone 11 Pro Max" 98 | case "iPhone13,1": 99 | return "iPhone 12 mini" 100 | case "iPhone13,2": 101 | return "iPhone 12" 102 | case "iPhone13,3": 103 | return "iPhone 12 Pro" 104 | case "iPhone13,4": 105 | return "iPhone 12 Pro Max" 106 | case "iPhone14,4": 107 | return "iPhone 13 mini" 108 | case "iPhone14,5": 109 | return "iPhone 13" 110 | case "iPhone14,2": 111 | return "iPhone 13 Pro" 112 | case "iPhone14,3": 113 | return "iPhone 13 Pro Max" 114 | case "iPhone14,7": 115 | return "iPhone 14" 116 | case "iPhone14,8": 117 | return "iPhone 14 Plus" 118 | case "iPhone15,2": 119 | return "iPhone 14 Pro" 120 | case "iPhone15,3": 121 | return "iPhone 14 Pro Max" 122 | case "iPhone15,4": 123 | return "iPhone 15" 124 | case "iPhone15,5": 125 | return "iPhone 15 Plus" 126 | case "iPhone16,1": 127 | return "iPhone 15 Pro" 128 | case "iPhone16,2": 129 | return "iPhone 15 Pro Max" 130 | case "iPhone8,4": 131 | return "iPhone SE" 132 | case "iPhone12,8": 133 | return "iPhone SE (2nd generation)" 134 | case "iPhone14,6": 135 | return "iPhone SE (3rd generation)" 136 | case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": 137 | return "iPad 2" 138 | case "iPad3,1", "iPad3,2", "iPad3,3": 139 | return "iPad (3rd generation)" 140 | case "iPad3,4", "iPad3,5", "iPad3,6": 141 | return "iPad (4th generation)" 142 | case "iPad6,11", "iPad6,12": 143 | return "iPad (5th generation)" 144 | case "iPad7,5", "iPad7,6": 145 | return "iPad (6th generation)" 146 | case "iPad7,11", "iPad7,12": 147 | return "iPad (7th generation)" 148 | case "iPad11,6", "iPad11,7": 149 | return "iPad (8th generation)" 150 | case "iPad12,1", "iPad12,2": 151 | return "iPad (9th generation)" 152 | case "iPad13,18", "iPad13,19": 153 | return "iPad (10th generation)" 154 | case "iPad4,1", "iPad4,2", "iPad4,3": 155 | return "iPad Air" 156 | case "iPad5,3", "iPad5,4": 157 | return "iPad Air 2" 158 | case "iPad11,3", "iPad11,4": 159 | return "iPad Air (3rd generation)" 160 | case "iPad13,1", "iPad13,2": 161 | return "iPad Air (4th generation)" 162 | case "iPad13,16", "iPad13,17": 163 | return "iPad Air (5th generation)" 164 | case "iPad2,5", "iPad2,6", "iPad2,7": 165 | return "iPad mini" 166 | case "iPad4,4", "iPad4,5", "iPad4,6": 167 | return "iPad mini 2" 168 | case "iPad4,7", "iPad4,8", "iPad4,9": 169 | return "iPad mini 3" 170 | case "iPad5,1", "iPad5,2": 171 | return "iPad mini 4" 172 | case "iPad11,1", "iPad11,2": 173 | return "iPad mini (5th generation)" 174 | case "iPad14,1", "iPad14,2": 175 | return "iPad mini (6th generation)" 176 | case "iPad6,3", "iPad6,4": 177 | return "iPad Pro (9.7-inch)" 178 | case "iPad7,3", "iPad7,4": 179 | return "iPad Pro (10.5-inch)" 180 | case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": 181 | return "iPad Pro (11-inch) (1st generation)" 182 | case "iPad8,9", "iPad8,10": 183 | return "iPad Pro (11-inch) (2nd generation)" 184 | case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": 185 | return "iPad Pro (11-inch) (3rd generation)" 186 | case "iPad14,3", "iPad14,4": 187 | return "iPad Pro (11-inch) (4th generation)" 188 | case "iPad6,7", "iPad6,8": 189 | return "iPad Pro (12.9-inch) (1st generation)" 190 | case "iPad7,1", "iPad7,2": 191 | return "iPad Pro (12.9-inch) (2nd generation)" 192 | case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": 193 | return "iPad Pro (12.9-inch) (3rd generation)" 194 | case "iPad8,11", "iPad8,12": 195 | return "iPad Pro (12.9-inch) (4th generation)" 196 | case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11": 197 | return "iPad Pro (12.9-inch) (5th generation)" 198 | case "iPad14,5", "iPad14,6": 199 | return "iPad Pro (12.9-inch) (6th generation)" 200 | case "AppleTV5,3": 201 | return "Apple TV" 202 | case "AppleTV6,2": 203 | return "Apple TV 4K" 204 | case "AudioAccessory1,1": 205 | return "HomePod" 206 | case "AudioAccessory5,1": 207 | return "HomePod mini" 208 | case "i386", "x86_64", "arm64": 209 | return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" 210 | default: 211 | return identifier 212 | } 213 | #elseif os(tvOS) 214 | switch identifier { 215 | case "AppleTV5,3": 216 | return "Apple TV 4" 217 | case "AppleTV6,2": 218 | return "Apple TV 4K" 219 | case "i386", "x86_64": 220 | return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" 221 | default: 222 | return identifier 223 | } 224 | #endif 225 | } 226 | 227 | return mapToDevice(identifier: identifier) 228 | }() 229 | } 230 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIImageView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by Osman Emre Ömürlü on 31.01.2024. 6 | // 7 | 8 | import UIKit 9 | import Kingfisher 10 | 11 | extension UIImageView { 12 | // MARK: - Properties 13 | static let imageCache = ImageCache.default 14 | 15 | // MARK: - Functions 16 | func setImage(with string: String?, placeholder: UIImage? = nil) { 17 | guard let string, let url = URL(string: string) else { return } 18 | 19 | let cacheOptions: KingfisherOptionsInfo = [ 20 | .scaleFactor(UIScreen.main.scale), 21 | .cacheOriginalImage, 22 | .processor(DefaultImageProcessor.default) 23 | ] 24 | 25 | kf.setImage(with: url, placeholder: placeholder) 26 | } 27 | 28 | // MARK: - Static functions 29 | static func limitMemoryCacheSize(_ MB: Int) { 30 | imageCache.memoryStorage.config.totalCostLimit = MB * 1024 * 1024 31 | } 32 | 33 | static func limitMemoryCacheCount(_ count: Int) { 34 | imageCache.memoryStorage.config.totalCostLimit = count 35 | } 36 | 37 | static func setMaxCachePeriodInSeconds(_ seconds: Int) { 38 | imageCache.memoryStorage.config.expiration = .seconds(TimeInterval(seconds)) 39 | } 40 | 41 | static func clearMemoryCache() { 42 | imageCache.clearMemoryCache() 43 | } 44 | 45 | static func clearDiskCache() { 46 | imageCache.clearDiskCache() 47 | } 48 | 49 | static func cleanExpiredDiskCache() { 50 | imageCache.cleanExpiredDiskCache() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIStackView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStackView+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2025. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIStackView { 11 | func removeArrangedSubviews() { 12 | arrangedSubviews.forEach { $0.removeFromSuperview() } 13 | } 14 | 15 | func addArrangedSubviews(_ views: [UIView]) { 16 | views.forEach { addArrangedSubview($0) } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UITableView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 15.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UITableView { 11 | func register(_: T.Type) where T: ReusableView { 12 | register(T.self, forCellReuseIdentifier: T.identifier) 13 | } 14 | 15 | func dequeueReusableCell(for indexPath: IndexPath) -> T where T: ReusableView { 16 | guard let cell = dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as? T 17 | else { 18 | fatalError("Could not dequeue cell with identifier: \(T.identifier)") 19 | } 20 | return cell 21 | } 22 | 23 | func dequeueReusableCell() -> T where T: ReusableView { 24 | guard let cell = dequeueReusableCell(withIdentifier: T.identifier) as? T 25 | else { 26 | fatalError("Could not dequeue cell with identifier: \(T.identifier)") 27 | } 28 | return cell 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | func drawShadow(radius: CGFloat, color: UIColor = ColorBackground.shadowPrimary.color, opacity: Float = 0.3) { 12 | layer.masksToBounds = false 13 | layer.shadowRadius = radius 14 | layer.shadowColor = color.cgColor 15 | layer.shadowOpacity = opacity 16 | layer.shadowOffset = .zero 17 | layer.shadowPath = UIBezierPath(rect: bounds).cgPath 18 | } 19 | 20 | func roundCorners(_ corners: UIRectCorner = .allCorners, 21 | radius: CGFloat, 22 | borderColor: UIColor? = nil, 23 | borderWidth: CGFloat? = nil) { 24 | clipsToBounds = true 25 | layer.cornerRadius = radius 26 | layer.maskedCorners = CACornerMask(rawValue: corners.rawValue) 27 | 28 | if let borderColor = borderColor { 29 | layer.borderColor = borderColor.cgColor 30 | } 31 | if let borderWidth = borderWidth { 32 | layer.borderWidth = borderWidth 33 | } 34 | } 35 | 36 | func roundCornersWithShadow(radius: CGFloat, 37 | corners: UIRectCorner = .allCorners, 38 | color: UIColor = ColorBackground.shadowPrimary.color, 39 | opacity: Float = 0.3, 40 | borderColor: UIColor? = nil, 41 | borderWidth: CGFloat? = nil) { 42 | roundCorners(corners ,radius: radius, borderColor: borderColor, borderWidth: borderWidth) 43 | drawShadow(radius: radius, color: color, opacity: opacity) 44 | layer.masksToBounds = false 45 | } 46 | 47 | func roundCornersTop(radius: CGFloat) { 48 | roundCorners([.topLeft, .topRight], radius: radius) 49 | } 50 | 51 | func roundCornersBottom(radius: CGFloat) { 52 | roundCorners([.bottomLeft, .bottomRight], radius: radius) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AppSeed/Extensions/UIViewController+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.08.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | public extension UIViewController { 11 | func topMostViewController() -> UIViewController { 12 | if let presented = presentedViewController { 13 | return presented.topMostViewController() 14 | } 15 | if let navigation = self as? UINavigationController { 16 | return navigation.visibleViewController?.topMostViewController() ?? navigation 17 | } 18 | if let tab = self as? UITabBarController { 19 | return tab.selectedViewController?.topMostViewController() ?? tab 20 | } 21 | return self 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AppSeed/Extensions/URL+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extension.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URL { 11 | var queryParameters: QueryParameters { return QueryParameters(url: self) } 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Helpers/KeychainHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainHelper.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 12.01.2025. 6 | // 7 | 8 | import Foundation 9 | import Security 10 | 11 | final class KeychainHelper { 12 | // MARK: - Singleton 13 | static let shared = KeychainHelper() 14 | 15 | // MARK: - Init 16 | private init() {} 17 | 18 | // MARK: - Save 19 | func save(key: String, value: String) -> Bool { 20 | guard let data = value.data(using: .utf8) else { return false } 21 | 22 | let query: [String: Any] = [ 23 | kSecClass as String: kSecClassGenericPassword, 24 | kSecAttrAccount as String: key, 25 | kSecValueData as String: data 26 | ] 27 | 28 | SecItemDelete(query as CFDictionary) 29 | 30 | let status = SecItemAdd(query as CFDictionary, nil) 31 | return status == errSecSuccess 32 | } 33 | 34 | // MARK: - Read 35 | func read(key: String) -> String? { 36 | let query: [String: Any] = [ 37 | kSecClass as String: kSecClassGenericPassword, 38 | kSecAttrAccount as String: key, 39 | kSecReturnData as String: true, 40 | kSecMatchLimit as String: kSecMatchLimitOne 41 | ] 42 | 43 | var item: CFTypeRef? 44 | let status = SecItemCopyMatching(query as CFDictionary, &item) 45 | 46 | guard status == errSecSuccess, let data = item as? Data else { return nil } 47 | return String(data: data, encoding: .utf8) 48 | } 49 | 50 | // MARK: - Delete 51 | func delete(key: String) -> Bool { 52 | let query: [String: Any] = [ 53 | kSecClass as String: kSecClassGenericPassword, 54 | kSecAttrAccount as String: key 55 | ] 56 | 57 | let status = SecItemDelete(query as CFDictionary) 58 | return status == errSecSuccess 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AppSeed/Helpers/LoadingHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingHelper.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 18.01.2025. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class LoadingHelper { 12 | 13 | static let shared = LoadingHelper() 14 | 15 | private var hudView: HudView? 16 | 17 | private init() { } 18 | 19 | func showLoading() { 20 | DispatchQueue.main.async { 21 | let window = UIApplication.shared.keyWindow() 22 | let newHudView = HudView() 23 | self.hudView = newHudView 24 | window?.addSubview(newHudView) 25 | newHudView.snp.makeConstraints { make in 26 | make.edges.equalToSuperview() 27 | } 28 | } 29 | } 30 | 31 | func hideLoading() { 32 | DispatchQueue.main.async { 33 | self.hudView?.removeFromSuperview() 34 | self.hudView = nil 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AppSeed/Helpers/UserDefaultsWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsWrapper.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 16.01.2025. 6 | // 7 | 8 | import Foundation 9 | import JsonFellow 10 | 11 | // MARK: - Wrapper 12 | @propertyWrapper 13 | struct UserDefault { 14 | let key: String 15 | let defaultValue: T 16 | 17 | init(_ key: String, defaultValue: T) { 18 | self.key = key 19 | self.defaultValue = defaultValue 20 | } 21 | 22 | var wrappedValue: T { 23 | get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } 24 | set { UserDefaults.standard.set(newValue, forKey: key) } 25 | } 26 | } 27 | 28 | // MARK: - Wrapper Codable 29 | @propertyWrapper 30 | struct UserDefaultCodable { 31 | let key: UserDefaultsKeys 32 | let defaultValue: T 33 | 34 | init(_ key: UserDefaultsKeys, defaultValue: T) { 35 | self.key = key 36 | self.defaultValue = defaultValue 37 | } 38 | 39 | var wrappedValue: T { 40 | get { UserDefaults.standard.getObject(forKey: key, type: T.self) ?? defaultValue } 41 | set { UserDefaults.standard.setObject(newValue, forKey: key) } 42 | } 43 | } 44 | 45 | // MARK: - UserDefaults 46 | extension UserDefaults { 47 | func setObject(_ object: T, forKey key: UserDefaultsKeys) { 48 | guard let encoded = object.encodeToData() else { return } 49 | set(encoded, forKey: key.rawValue) 50 | } 51 | 52 | func getObject(forKey key: UserDefaultsKeys, type: T.Type) -> T? { 53 | guard let data = data(forKey: key.rawValue) else { return nil } 54 | let object = T.decode(data) 55 | return object 56 | } 57 | } 58 | 59 | // MARK: - Keys 60 | enum UserDefaultsKeys: String { 61 | case key 62 | } 63 | -------------------------------------------------------------------------------- /AppSeed/Network/Protocols/QueryParameters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryParameters.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Foundation 9 | 10 | struct QueryParameters { 11 | // MARK: - Properties 12 | let queryItems: [URLQueryItem] 13 | 14 | //MARK: - Init 15 | init(url: URL?) { 16 | queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? [] 17 | } 18 | 19 | subscript(name: String) -> String? { 20 | return queryItems.first(where: { $0.name == name })?.value 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppSeed/Network/Protocols/RequestArrayProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestArrayProtocol.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol RequestArrayProtocol: RequestProtocol { 11 | associatedtype ResultObject: Codable 12 | } 13 | 14 | extension RequestArrayProtocol { 15 | func request(success: @escaping CodableArrayClosure, failure: ResponseErrorClosure? = nil) { 16 | RequestManager.shared.request(self, success: success, failure: failure) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Network/Protocols/RequestObjectProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestObjectProtocol.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol RequestObjectProtocol: RequestProtocol { 11 | associatedtype ResultObject: Codable 12 | } 13 | 14 | extension RequestObjectProtocol { 15 | func request(success: @escaping CodableAnyClosure, failure: ResponseErrorClosure? = nil) { 16 | RequestManager().request(self, success: success, failure: failure) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Network/Protocols/RequestProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestProtocols.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Alamofire 9 | 10 | public protocol RequestProtocol { 11 | var path: String { get } 12 | var method: HTTPMethod { get } 13 | var parameters: Parameters? { get set } 14 | var headers: HTTPHeaders? { get set } 15 | var encodingType: ParameterEncoding { get set } 16 | } 17 | 18 | public protocol RequestGPTProtocol: RequestProtocol { 19 | var gptModel: GPTModel { get } 20 | } 21 | -------------------------------------------------------------------------------- /AppSeed/Network/RequestsManager/BaseUrl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseUrl.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | enum BaseUrl: String { 9 | case gpt = "https://api.openai.com/v1" 10 | case api = "" 11 | } 12 | -------------------------------------------------------------------------------- /AppSeed/Network/RequestsManager/RequestManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestManager.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Foundation 9 | import Alamofire 10 | import JsonFellow 11 | 12 | public class RequestManager { 13 | // TODO: - dont keep it here, keep it safe 🔒 14 | static let apiKey = "" 15 | 16 | // MARK: - Singleton 17 | static let shared = RequestManager() 18 | 19 | //MARK: - Create 20 | private func createRequest(_ request: RequestProtocol, complation: @escaping (DataRequest)-> ()) { 21 | let baseUrl = request is RequestGPTProtocol ? BaseUrl.gpt : BaseUrl.api 22 | let path = baseUrl.rawValue + request.path 23 | let requestAF = AF.request(path, method: request.method, parameters: request.parameters, encoding: request.encodingType, headers: request.headers) 24 | 25 | requestAF.validate() 26 | LoadingHelper.shared.showLoading() 27 | requestAF.responseData { (response) in 28 | LoadingHelper.shared.hideLoading() 29 | complation(requestAF) 30 | } 31 | } 32 | 33 | //MARK: - Request Object 34 | func request(_ request: RequestProtocol, success: @escaping CodableAnyClosure, failure: ResponseErrorClosure? = nil) { 35 | createRequest(request) { [weak self] data in 36 | guard let self else { return } 37 | data.responseData { (response) in 38 | switch response.result { 39 | case .success: 40 | success(T.decode(response.value)) 41 | case .failure: 42 | self.handleError(response: response, failure: failure) 43 | } 44 | } 45 | } 46 | } 47 | 48 | //MARK: - Request Array (may remove cuz request any can cover this case too) 49 | func request(_ request: RequestProtocol, success: @escaping CodableArrayClosure, failure: ResponseErrorClosure? = nil) { 50 | createRequest(request) { [weak self] data in 51 | guard let self else { return } 52 | data.responseData { (response) in 53 | switch response.result { 54 | case .success: 55 | success([T].decode(response.value)) 56 | case .failure: 57 | self.handleError(response: response, failure: failure) 58 | } 59 | } 60 | } 61 | } 62 | 63 | // MARK: - RequestGPT 64 | func requestGPT(_ request: RequestGPTProtocol, success: @escaping CodableAnyClosure, failure: ResponseErrorGPTClosure? = nil) { 65 | createRequest(request) { [weak self] data in 66 | guard let self else { return } 67 | data.responseData { (response) in 68 | switch response.result { 69 | case .success: 70 | success(T.decode(response.value)) 71 | case .failure: 72 | self.handleErrorGPT(response: response, failureGPT: failure) 73 | } 74 | } 75 | } 76 | } 77 | 78 | //MARK: - Error Handler 79 | // TODO: - 80 | private func handleError(response: AFDataResponse, failure: ResponseErrorClosure? = nil) { 81 | guard let data = response.data else { return } 82 | if let error = ResponseError.decode(data) { 83 | debugPrint("🍿 Error: \(error.message ?? "Unknown error")") 84 | failure?(error) 85 | return 86 | } 87 | 88 | debugPrint("🍿 Failed to decode error or no recognizable data structure") 89 | failure?(ResponseError(message: "An unknown error occurred")) 90 | } 91 | 92 | private func handleErrorGPT(response: AFDataResponse, failureGPT: ResponseErrorGPTClosure? = nil) { 93 | guard let data = response.data else { return } 94 | if let error = ResponseErrorGPT.decode(data) { 95 | debugPrint("🍿 ErrorGPT: \(error.error?.message ?? "Unknown error")") 96 | failureGPT?(error) 97 | return 98 | } 99 | 100 | debugPrint("🍿 Failed to decode error or no recognizable data structure") 101 | failureGPT?(ResponseErrorGPT(error: ResponseErrorGPT.Detail(message: "An unknown error occurred"))) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /AppSeed/Network/Responses/ResponseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseError.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | public struct ResponseError: Codable { 9 | let message: String? 10 | } 11 | -------------------------------------------------------------------------------- /AppSeed/Network/Responses/ResponseErrorGPT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseErrorGPT.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 18.01.2025. 6 | // 7 | 8 | public struct ResponseErrorGPT: Codable { 9 | let error: Detail? 10 | 11 | struct Detail: Codable { 12 | let message: String? 13 | let type: String? 14 | let param: String? 15 | let code: String? 16 | 17 | init(message: String?, type: String? = nil, param: String? = nil, code: String? = nil) { 18 | self.message = message 19 | self.type = type 20 | self.param = param 21 | self.code = code 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AppSeed/Network/Services/Gpt/GPTModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GPTModels.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | public enum GPTModel: String { 9 | case chat = "/chat/completions" 10 | case dalle = "/images/generations" 11 | } 12 | 13 | public enum RequestGPTSystemMessage: String { 14 | case empty = "" 15 | } 16 | -------------------------------------------------------------------------------- /AppSeed/Network/Services/Gpt/GptService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GptService.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 12.01.2025. 6 | // 7 | 8 | import Alamofire 9 | 10 | protocol GptServiceProtocol { 11 | func requestGPT(request: RequestGPTProtocol, success: @escaping CodableAnyClosure) 12 | func requestDalle(request: RequestGPTProtocol, success: @escaping CodableAnyClosure) 13 | } 14 | 15 | final class GptService: RequestManager, GptServiceProtocol { 16 | func requestGPT(request: any RequestGPTProtocol, success: @escaping CodableAnyClosure) { 17 | requestGPT(request, success: success) 18 | } 19 | func requestDalle(request: any RequestGPTProtocol, success: @escaping CodableAnyClosure) { 20 | requestGPT(request, success: success) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppSeed/Network/Services/Gpt/Response/ResponseDALLE.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseDALLE.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | struct ResponseDALLE: Codable { 9 | let created: Int? 10 | let data: [ResponseDALLEData]? 11 | 12 | var imageUrl: String? { 13 | data?.first?.url 14 | } 15 | 16 | struct ResponseDALLEData: Codable { 17 | let url: String? 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AppSeed/Network/Services/Gpt/Response/ResponseGPT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseGPT.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | struct ResponseGPT: Codable { 9 | let id: String? 10 | let object: String? 11 | let created: Int? 12 | let model: String? 13 | let choices: [ChoiceGPT]? 14 | let usage: UsageGPT? 15 | 16 | var message: String? { 17 | guard let content = choices?.first?.message?.content else { return nil } 18 | return content 19 | } 20 | } 21 | 22 | struct ChoiceGPT: Codable { 23 | let index: Int? 24 | let message: MessageGPT? 25 | let finish_reason: String? 26 | } 27 | 28 | struct MessageGPT: Codable { 29 | let role: String? 30 | let content: String? 31 | } 32 | 33 | struct UsageGPT: Codable { 34 | let prompt_tokens: Int? 35 | let completion_tokens: Int? 36 | let total_tokens: Int? 37 | } 38 | -------------------------------------------------------------------------------- /AppSeed/Network/Services/RequestDALLE.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestDALLE.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Alamofire 9 | 10 | struct RequestDALLE: RequestGPTProtocol { 11 | // MARK: - Properties 12 | var path: String { GPTModel.dalle.rawValue } 13 | var gptModel: GPTModel = .dalle 14 | var method: HTTPMethod = .post 15 | var parameters: Parameters? 16 | var headers: HTTPHeaders? 17 | var encodingType: ParameterEncoding = JSONEncoding.default 18 | 19 | // MARK: - Init 20 | init(prompt: String) { 21 | self.headers = [ 22 | "Authorization": "Bearer \(RequestManager.apiKey)", 23 | "Content-Type": "application/json" 24 | ] 25 | self.parameters = [ 26 | "prompt": prompt, 27 | "n": 1, 28 | "size": "256x256" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AppSeed/Network/Services/RequestGPT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestGPT.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 11.01.2025. 6 | // 7 | 8 | import Alamofire 9 | 10 | struct RequestGPT: RequestGPTProtocol { 11 | // MARK: - System 12 | var model = "gpt-4" 13 | var systemMessage: RequestGPTSystemMessage 14 | 15 | // MARK: - Properties 16 | var path: String { gptModel.rawValue } 17 | var gptModel: GPTModel = .chat 18 | var method: HTTPMethod 19 | var parameters: Parameters? 20 | var headers: HTTPHeaders? 21 | var encodingType: ParameterEncoding 22 | 23 | // MARK: - Init 24 | init(userMessage: String, systemMessage: RequestGPTSystemMessage = .empty, method: HTTPMethod = .post) { 25 | self.method = method 26 | self.systemMessage = systemMessage 27 | self.encodingType = method == .get ? URLEncoding.default : JSONEncoding.default 28 | 29 | self.headers = [ 30 | "Authorization": "Bearer \(RequestManager.apiKey)", 31 | "Content-Type": "application/json" 32 | ] 33 | self.parameters = [ 34 | "model": model, 35 | "messages": [ 36 | ["role": "system", "content": systemMessage.rawValue], 37 | ["role": "user", "content": userMessage] 38 | ], 39 | "max_tokens": 100 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AppSeed/Resources/Animations/lottie-loading.json: -------------------------------------------------------------------------------- 1 | {"nm":"Trotter It","ddd":0,"h":150,"w":150,"meta":{"g":"LottieFiles AE 3.5.2"},"layers":[{"ty":4,"nm":"Layer 24 Outlines 2","sr":1,"st":0,"op":120.0000048877,"ip":36.0000014663101,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[80,80,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[74.892,72,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-90],"t":0},{"s":[270],"t":50.0000020365418}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-27.614],[27.614,0],[0,27.614],[-27.614,0]],"o":[[0,27.614],[-27.614,0],[0,-27.614],[27.614,0]],"v":[[50,0],[0,50],[-50,0],[0,-50]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":1,"ml":10,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"c":{"a":0,"k":[0.4196,0.5569,0.1373,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[80,80],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":0},{"s":[100],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":20},{"s":[100],"t":50.0000020365418}],"ix":1},"m":1}],"ind":1},{"ty":4,"nm":"Layer 24 Outlines","sr":1,"st":0,"op":120.0000048877,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[80,80,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[74.892,72,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-90],"t":0},{"s":[270],"t":50.0000020365418}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-27.614],[27.614,0],[0,27.614],[-27.614,0]],"o":[[0,27.614],[-27.614,0],[0,-27.614],[27.614,0]],"v":[[50,0],[0,50],[-50,0],[0,-50]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":1,"ml":10,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"c":{"a":0,"k":[0.4196,0.5569,0.1373,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[80,80],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":0},{"s":[100],"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":20},{"s":[100],"t":50.0000020365418}],"ix":1},"m":1}],"ind":2}],"v":"4.8.0","fr":29.9700012207031,"op":50.0000020365418,"ip":0,"assets":[]} -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 1024pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 1024pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 1024pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 29pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 40pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 76pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/1x 76pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 20pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 20pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 29pt 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 29pt 2.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 29pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 40pt 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 40pt 2.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 40pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 60pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 60pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 64pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 64pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 68pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 68pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 76pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 76pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 83,5pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/2x 83,5pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 20pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 20pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 20pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 20pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 29pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 29pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 29pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 38pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 38pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 40pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 40pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 40pt 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 40pt 2.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 40pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 60pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 60pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 60pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 60pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 64pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/3x 64pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "2x 20pt.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "scale" : "2x", 8 | "size" : "20x20" 9 | }, 10 | { 11 | "filename" : "3x 20pt 1.png", 12 | "idiom" : "universal", 13 | "platform" : "ios", 14 | "scale" : "3x", 15 | "size" : "20x20" 16 | }, 17 | { 18 | "filename" : "2x 29pt.png", 19 | "idiom" : "universal", 20 | "platform" : "ios", 21 | "scale" : "2x", 22 | "size" : "29x29" 23 | }, 24 | { 25 | "filename" : "3x 29pt 1.png", 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "scale" : "3x", 29 | "size" : "29x29" 30 | }, 31 | { 32 | "filename" : "1x 76pt.png", 33 | "idiom" : "universal", 34 | "platform" : "ios", 35 | "scale" : "2x", 36 | "size" : "38x38" 37 | }, 38 | { 39 | "filename" : "3x 38pt.png", 40 | "idiom" : "universal", 41 | "platform" : "ios", 42 | "scale" : "3x", 43 | "size" : "38x38" 44 | }, 45 | { 46 | "filename" : "2x 40pt.png", 47 | "idiom" : "universal", 48 | "platform" : "ios", 49 | "scale" : "2x", 50 | "size" : "40x40" 51 | }, 52 | { 53 | "filename" : "3x 40pt 2.png", 54 | "idiom" : "universal", 55 | "platform" : "ios", 56 | "scale" : "3x", 57 | "size" : "40x40" 58 | }, 59 | { 60 | "filename" : "2x 60pt.png", 61 | "idiom" : "universal", 62 | "platform" : "ios", 63 | "scale" : "2x", 64 | "size" : "60x60" 65 | }, 66 | { 67 | "filename" : "3x 60pt 1.png", 68 | "idiom" : "universal", 69 | "platform" : "ios", 70 | "scale" : "3x", 71 | "size" : "60x60" 72 | }, 73 | { 74 | "filename" : "2x 64pt.png", 75 | "idiom" : "universal", 76 | "platform" : "ios", 77 | "scale" : "2x", 78 | "size" : "64x64" 79 | }, 80 | { 81 | "filename" : "3x 64pt.png", 82 | "idiom" : "universal", 83 | "platform" : "ios", 84 | "scale" : "3x", 85 | "size" : "64x64" 86 | }, 87 | { 88 | "filename" : "2x 68pt.png", 89 | "idiom" : "universal", 90 | "platform" : "ios", 91 | "scale" : "2x", 92 | "size" : "68x68" 93 | }, 94 | { 95 | "filename" : "2x 76pt.png", 96 | "idiom" : "universal", 97 | "platform" : "ios", 98 | "scale" : "2x", 99 | "size" : "76x76" 100 | }, 101 | { 102 | "filename" : "2x 83,5pt.png", 103 | "idiom" : "universal", 104 | "platform" : "ios", 105 | "scale" : "2x", 106 | "size" : "83.5x83.5" 107 | }, 108 | { 109 | "filename" : "1x 1024pt 1.png", 110 | "idiom" : "universal", 111 | "platform" : "ios", 112 | "size" : "1024x1024" 113 | }, 114 | { 115 | "filename" : "1x 40pt.png", 116 | "idiom" : "iphone", 117 | "scale" : "2x", 118 | "size" : "20x20" 119 | }, 120 | { 121 | "filename" : "3x 20pt.png", 122 | "idiom" : "iphone", 123 | "scale" : "3x", 124 | "size" : "20x20" 125 | }, 126 | { 127 | "filename" : "1x 29pt.png", 128 | "idiom" : "iphone", 129 | "scale" : "1x", 130 | "size" : "29x29" 131 | }, 132 | { 133 | "filename" : "2x 29pt 2.png", 134 | "idiom" : "iphone", 135 | "scale" : "2x", 136 | "size" : "29x29" 137 | }, 138 | { 139 | "filename" : "3x 29pt.png", 140 | "idiom" : "iphone", 141 | "scale" : "3x", 142 | "size" : "29x29" 143 | }, 144 | { 145 | "filename" : "2x 40pt 2.png", 146 | "idiom" : "iphone", 147 | "scale" : "2x", 148 | "size" : "40x40" 149 | }, 150 | { 151 | "filename" : "3x 40pt 1.png", 152 | "idiom" : "iphone", 153 | "scale" : "3x", 154 | "size" : "40x40" 155 | }, 156 | { 157 | "filename" : "3x 40pt.png", 158 | "idiom" : "iphone", 159 | "scale" : "2x", 160 | "size" : "60x60" 161 | }, 162 | { 163 | "filename" : "3x 60pt.png", 164 | "idiom" : "iphone", 165 | "scale" : "3x", 166 | "size" : "60x60" 167 | }, 168 | { 169 | "filename" : "1x 1024pt.png", 170 | "idiom" : "ios-marketing", 171 | "scale" : "1x", 172 | "size" : "1024x1024" 173 | } 174 | ], 175 | "info" : { 176 | "author" : "xcode", 177 | "version" : 1 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Background/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Background/bg_launch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bg_launch.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Background/bg_launch.imageset/bg_launch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/Logo_1024.imageset/1x 1024pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/Logo/Logo_1024.imageset/1x 1024pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/Logo_1024.imageset/1x 1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/Logo/Logo_1024.imageset/1x 1024pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/Logo_1024.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1x 1024pt.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "1x 1024pt 1.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/logo_40.imageset/1x 40pt 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/Logo/logo_40.imageset/1x 40pt 1.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/logo_40.imageset/1x 40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/AppSeed/Resources/Assets.xcassets/Logo/logo_40.imageset/1x 40pt.png -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Logo/logo_40.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1x 40pt.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "1x 40pt 1.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/tutorial_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tutorial_1.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/tutorial_1.imageset/tutorial_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/tutorial_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tutorial_2.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/tutorial_2.imageset/tutorial_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/tutorial_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tutorial_3.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Resources/Assets.xcassets/Tutorial/tutorial_3.imageset/tutorial_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/AppPalette/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/AppPalette/palette1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x23", 9 | "green" : "0x8E", 10 | "red" : "0x6B" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0x23", 27 | "green" : "0x8E", 28 | "red" : "0x6B" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/AppPalette/palette2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Background/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Background/backgroundPrimary.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.000", 27 | "green" : "0.000", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Shadow/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Shadow/shadowPrimary.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Text/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Text/textPrimary.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors.xcassets/Text/textSecondary.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x14", 9 | "green" : "0x14", 10 | "red" : "0x14" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xEB", 27 | "green" : "0xEB", 28 | "red" : "0xEB" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors/ColorBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorBackground.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | enum ColorBackground: Colorable { 11 | case backgroundPrimary 12 | case backgroundSecondary 13 | 14 | case shadowPrimary 15 | 16 | var hex: Int { 17 | switch self { 18 | case .backgroundPrimary: 19 | return 0xFFFFFF 20 | case .backgroundSecondary: 21 | return 0x333333 22 | 23 | case .shadowPrimary: 24 | return 0x000000 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors/ColorText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorText.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | enum ColorText: Colorable { 11 | case textPrimary 12 | case textSecondary 13 | 14 | var hex: Int { 15 | switch self { 16 | case .textPrimary: 17 | return 0x000000 18 | case .textSecondary: 19 | return 0xEBEBEB 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors/Colorable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colorable.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol Colorable { 11 | var hex: Int { get } 12 | var color: UIColor { get } 13 | } 14 | 15 | extension Colorable { 16 | var color: UIColor { 17 | return UIColor(named: String(describing: self)) ?? UIColor(rgb: hex) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AppSeed/Resources/Colors/Palette.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Palette.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 2.09.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | enum Palette: Colorable { 11 | case palette1 12 | case palette2 13 | 14 | var hex: Int { 15 | switch self { 16 | case .palette1: 17 | return 0xFFFFFF 18 | case .palette2: 19 | return 0x000000 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppSeed/Resources/Images/BackgroundImages.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundImages.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 18.01.2025. 6 | // 7 | 8 | enum BackgroundImages: Imageable { 9 | case bg_launch 10 | } 11 | -------------------------------------------------------------------------------- /AppSeed/Resources/Images/Imageable+Symbolable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Imageable+Symbolable.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol Imageable { 11 | var image: UIImage { get } 12 | } 13 | 14 | extension Imageable { 15 | var image: UIImage { 16 | UIImage(named: String(describing: self)) ?? UIImage() 17 | } 18 | } 19 | 20 | protocol Symbolable { 21 | var symbolName: String { get } 22 | func symbol(size: CGFloat, weight: UIImage.SymbolWeight) -> UIImage 23 | } 24 | 25 | extension Symbolable { 26 | func symbol(size: CGFloat = 15, weight: UIImage.SymbolWeight = .medium) -> UIImage { 27 | let configuration = UIImage.SymbolConfiguration(pointSize: size, weight: weight) 28 | let image = UIImage(systemName: symbolName, withConfiguration: configuration)? 29 | .withRenderingMode(.alwaysTemplate) ?? UIImage() 30 | 31 | return image 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AppSeed/Resources/Images/Logo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logo.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | enum Logo: Imageable { 9 | case logo_1024 10 | case logo_40 11 | } 12 | -------------------------------------------------------------------------------- /AppSeed/Resources/Images/SFSymbols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFSymbols.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | enum Symbols: Symbolable { 9 | case apple_logo 10 | 11 | var symbolName: String { 12 | switch self { 13 | case .apple_logo: 14 | return "apple.logo" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AppSeed/Resources/Images/TutorialImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialImage.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | enum TutorialImage: Imageable { 9 | case tutorial_1 10 | case tutorial_2 11 | case tutorial_3 12 | } 13 | -------------------------------------------------------------------------------- /AppSeed/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UISceneConfigurations 8 | 9 | UIWindowSceneSessionRoleApplication 10 | 11 | 12 | UISceneDelegateClassName 13 | $(PRODUCT_MODULE_NAME).SceneDelegate 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /AppSeed/Scenes/ScrollTest/ScrollTestBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BuilderTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class ScrollTestBuilder: BaseBuilder { 11 | func build() -> UIViewController { 12 | let router = ScrollTestRouter() 13 | let viewModel = ScrollTestViewModel() 14 | let viewController = ScrollTestViewController(viewModel: viewModel, router: router) 15 | 16 | return viewController 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Scenes/ScrollTest/ScrollTestRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouterTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - Route Protocol 11 | protocol ScrollTestRoute { 12 | func presentScrollTest() 13 | } 14 | 15 | extension ScrollTestRoute where Self: BaseRouterProtocol { 16 | func presentScrollTest() { 17 | let vc = ScrollTestBuilder().build() 18 | let nc = BaseNavigationController(rootViewController: vc) 19 | SceneDelegate.setToWindow(nc) 20 | } 21 | } 22 | 23 | // MARK: - Router 24 | final class ScrollTestRouter: BaseRouter { } 25 | -------------------------------------------------------------------------------- /AppSeed/Scenes/ScrollTest/ScrollTestViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class ScrollTestViewController: BaseViewController { 12 | // MARK: - Properties 13 | override var isScrollable: Bool { true } 14 | 15 | // MARK: - Life Cycle 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | 20 | // MARK: - Prepare 21 | override func prepare() { 22 | super.prepare() 23 | draw() 24 | prepareScrollView() 25 | } 26 | 27 | private func prepareScrollView() { 28 | let rows = (1...16).map { _ in RowTest() } 29 | addToScrollView(rows) 30 | setScrollViewStackSpacing(16) 31 | setScrollViewInsets(top: 16, bottom: 16) 32 | setScrollViewStackInsets(left: 16, right: 16) 33 | } 34 | 35 | // MARK: - Bind 36 | override func bindViewModel() { 37 | super.bindViewModel() 38 | } 39 | } 40 | 41 | // MARK: - Draw 42 | private extension ScrollTestViewController { 43 | private func draw() {} 44 | } 45 | 46 | class RowTest: UIView { 47 | override init(frame: CGRect) { 48 | super.init(frame: frame) 49 | prepare() 50 | } 51 | 52 | required init?(coder: NSCoder) { 53 | super.init(coder: coder) 54 | prepare() 55 | } 56 | 57 | init() { 58 | super.init(frame: .zero) 59 | prepare() 60 | } 61 | 62 | override func layoutSubviews() { 63 | super.layoutSubviews() 64 | roundCornersWithShadow(radius: 12, color: randomColor(), opacity: 0.5) 65 | } 66 | 67 | private func prepare() { 68 | backgroundColor = randomColor() 69 | snp.makeConstraints { 70 | $0.height.width.equalTo(48) 71 | } 72 | 73 | } 74 | 75 | private func randomColor() -> UIColor { 76 | return UIColor(red: CGFloat.random(in: 0...1), 77 | green: CGFloat.random(in: 0...1), 78 | blue: CGFloat.random(in: 0...1), 79 | alpha: 1.0) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /AppSeed/Scenes/ScrollTest/ScrollTestViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Source 11 | protocol ScrollTestViewModelDataSource { } 12 | 13 | // MARK: - Closure 14 | protocol ScrollTestViewModelClosureSource { } 15 | 16 | // MARK: - Function 17 | protocol ScrollTestViewModelFunctionSource { } 18 | 19 | // MARK: - Protocol 20 | protocol ScrollTestViewModelProtocol: BaseViewModel, ScrollTestViewModelDataSource, ScrollTestViewModelClosureSource, ScrollTestViewModelFunctionSource { } 21 | 22 | // MARK: - ViewModel 23 | final class ScrollTestViewModel: BaseViewModel, ScrollTestViewModelProtocol { 24 | // MARK: - Source 25 | 26 | // MARK: - Closure 27 | 28 | // MARK: - Function 29 | } 30 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/Localizable/SplashLocalizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashLocalizable.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SplashLocalizable { 11 | private static func localized(_ key: String) -> String { 12 | NSLocalizedString(key, tableName: "SplashLocalizable", bundle: .main, value: "", comment: "") 13 | } 14 | 15 | static var splash_title: String { 16 | localized("splash_title") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/Localizable/en.lproj/SplashLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | SplashLocalizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "splash_title" = "AppSeed"; 10 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/Localizable/tr.lproj/SplashLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | SplashLocalizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "splash_title" = "AppSeed"; 10 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/SplashBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashBuilder.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class SplashBuilder: BaseBuilder { 11 | func build() -> UIViewController { 12 | let service = GptService() 13 | let viewModel = SplashViewModel(gptService: service) 14 | let router = SplashRouter() 15 | let viewController = SplashViewController(viewModel: viewModel, router: router) 16 | 17 | return viewController 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/SplashLocalizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashLocalizable.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SplashLocalizable { 11 | private static func localized(_ key: String) -> String { 12 | NSLocalizedString(key, tableName: "SplashLocalizable", bundle: .main, value: "", comment: "") 13 | } 14 | 15 | static var splash_title: String { 16 | localized("splash_title") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/SplashRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashRouter.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit 9 | 10 | final class SplashRouter: BaseRouter, TutorialRoute { } 11 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/SplashViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewController.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class SplashViewController: BaseViewController { 12 | // MARK: - UI 13 | private lazy var bgImage: UIImageView = { 14 | let imageView = UIImageView(frame: .zero) 15 | imageView.contentMode = .scaleAspectFill 16 | imageView.image = BackgroundImages.bg_launch.image 17 | return imageView 18 | }() 19 | private lazy var splashImage: UIImageView = { 20 | let imageView = UIImageView(frame: .zero) 21 | imageView.contentMode = .scaleAspectFit 22 | imageView.image = Logo.logo_1024.image 23 | return imageView 24 | }() 25 | private lazy var splashTitle: UILabel = { 26 | let label = UILabel() 27 | let text = SplashLocalizable.splash_title 28 | label.text = text 29 | label.font = .systemFont(ofSize: 24, weight: .bold) 30 | label.textColor = ColorText.textPrimary.color 31 | label.textAlignment = .center 32 | return label 33 | }() 34 | 35 | // MARK: - Life Cycle 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | mainRequest() 39 | } 40 | 41 | override func viewDidLayoutSubviews() { 42 | super.viewDidLayoutSubviews() 43 | splashImage.roundCorners(radius: 64) 44 | } 45 | 46 | // MARK: - Prepare 47 | override func prepare() { 48 | super.prepare() 49 | view.backgroundColor = ColorBackground.backgroundSecondary.color.withAlphaComponent(0.5) 50 | draw() 51 | } 52 | 53 | // MARK: - Bind 54 | override func bindViewModel() { 55 | super.bindViewModel() 56 | viewModel?.requestsClosure = { [weak self] in 57 | guard let self else { return } 58 | routeTutorial() 59 | } 60 | } 61 | 62 | // MARK: - Functions 63 | func mainRequest() { 64 | viewModel?.requestFake() 65 | } 66 | 67 | // MARK: - Route 68 | private func routeTutorial() { 69 | router?.showTutorial() 70 | } 71 | } 72 | 73 | // MARK: - Draw 74 | private extension SplashViewController { 75 | private func draw() { 76 | view.addSubview(bgImage) 77 | bgImage.snp.makeConstraints { 78 | $0.edges.equalToSuperview() 79 | } 80 | 81 | view.addSubview(splashImage) 82 | splashImage.snp.makeConstraints { 83 | $0.width.height.equalTo(128) 84 | $0.centerX.equalToSuperview() 85 | $0.centerY.equalTo(view.safeAreaLayoutGuide) 86 | } 87 | 88 | view.addSubview(splashTitle) 89 | splashTitle.snp.makeConstraints { 90 | $0.centerX.equalToSuperview() 91 | $0.top.equalTo(splashImage.snp.bottom).offset(8) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/SplashViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewModel.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 26.07.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Source 11 | protocol SplashViewModelDataSource { 12 | var title: String { get } 13 | } 14 | 15 | // MARK: - Closure 16 | protocol SplashViewModelClosureSource { 17 | var requestsClosure: EmptyClosure? { get } 18 | } 19 | 20 | // MARK: - Function 21 | protocol SplashViewModelFunctionSource { 22 | func requestGPT() 23 | } 24 | 25 | // MARK: - Protocol 26 | protocol SplashViewModelProtocol: BaseViewModel, SplashViewModelDataSource, SplashViewModelClosureSource, SplashViewModelFunctionSource { } 27 | 28 | // MARK: - ViewModel 29 | final class SplashViewModel: BaseViewModel, SplashViewModelProtocol { 30 | // MARK: - Source 31 | var title: String = "splash" 32 | 33 | // MARK: - Service 34 | private var gptService: GptServiceProtocol? 35 | 36 | // MARK: - Closure 37 | var requestsClosure: EmptyClosure? 38 | 39 | // MARK: - Init 40 | init(gptService: GptServiceProtocol) { 41 | super.init() 42 | self.gptService = gptService 43 | } 44 | 45 | // MARK: - Function 46 | func requestFake() { 47 | LoadingHelper.shared.showLoading() 48 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 49 | LoadingHelper.shared.hideLoading() 50 | self.requestsClosure?() 51 | } 52 | } 53 | 54 | func requestGPT() { 55 | let message = "hi dude <3" 56 | let request = RequestGPT(userMessage: message) 57 | gptService?.requestGPT(request: request) { response in 58 | debugPrint(response?.message ?? "") 59 | } 60 | } 61 | 62 | func requestDALLE() { 63 | let prompt = "a futuristic cityscape with flying cars and neon lights" 64 | let request = RequestDALLE(prompt: prompt) 65 | gptService?.requestDalle(request: request) { response in 66 | debugPrint(response?.imageUrl ?? "") 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/en.lproj/SplashLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | SplashLocalizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "splash_title" = "AppSeed"; 10 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Splash/tr.lproj/SplashLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | SplashLocalizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "splash_title" = "AppSeed"; 10 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/Cells/TutorialCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialCell.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class TutorialCell: BaseCollectionViewCell { 12 | // MARK: - UI 13 | private lazy var tutorialImageView: BaseImageView = { 14 | let view = BaseImageView(frame: .zero) 15 | view.contentMode = .scaleAspectFill 16 | view.clipsToBounds = true 17 | return view 18 | }() 19 | private lazy var titleLabel: UILabel = { 20 | let label = UILabel() 21 | label.numberOfLines = 1 22 | label.textAlignment = .center 23 | label.textColor = ColorText.textPrimary.color 24 | label.font = .systemFont(ofSize: 18, weight: .bold) 25 | return label 26 | }() 27 | private lazy var subtitleLabel: UILabel = { 28 | let label = UILabel() 29 | label.numberOfLines = 0 30 | label.textAlignment = .center 31 | label.textColor = ColorText.textSecondary.color 32 | label.font = .systemFont(ofSize: 14, weight: .bold) 33 | return label 34 | }() 35 | 36 | // MARK: - Init 37 | required init?(coder: NSCoder) { 38 | super.init(coder: coder) 39 | } 40 | 41 | override init(frame: CGRect) { 42 | super.init(frame: frame) 43 | draw() 44 | } 45 | 46 | // MARK: - Configure Cell 47 | func configure(with model: TutorialUIModel?) { 48 | titleLabel.text = model?.title 49 | subtitleLabel.text = model?.subtitle 50 | tutorialImageView.image = model?.image?.image 51 | } 52 | } 53 | 54 | // MARK: - Draw 55 | private extension TutorialCell { 56 | private func draw() { 57 | addSubview(tutorialImageView) 58 | tutorialImageView.snp.makeConstraints { 59 | $0.centerX.equalToSuperview() 60 | $0.centerY.equalToSuperview() 61 | $0.left.right.equalToSuperview() 62 | $0.height.equalTo(tutorialImageView.snp.width) 63 | } 64 | 65 | addSubview(titleLabel) 66 | titleLabel.snp.makeConstraints { 67 | $0.left.equalToSuperview().offset(16) 68 | $0.right.equalToSuperview().offset(-16) 69 | $0.top.equalTo(tutorialImageView.snp.bottom).offset(16) 70 | } 71 | 72 | addSubview(subtitleLabel) 73 | subtitleLabel.snp.makeConstraints { 74 | $0.left.equalToSuperview().offset(16) 75 | $0.right.equalToSuperview().offset(-16) 76 | $0.top.equalTo(titleLabel.snp.bottom).offset(4) 77 | $0.bottom.lessThanOrEqualToSuperview().offset(-16) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/Localizable/TutorialLocalizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialLocalizable.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | enum TutorialLocalizable { 11 | private static func localized(_ key: String) -> String { 12 | NSLocalizedString(key, tableName: "TutorialLocalizable", bundle: .main, value: "", comment: "") 13 | } 14 | 15 | static var tutorial_title_1: String { 16 | localized("tutorial_title_1") 17 | } 18 | static var tutorial_title_2: String { 19 | localized("tutorial_title_2") 20 | } 21 | static var tutorial_title_3: String { 22 | localized("tutorial_title_3") 23 | } 24 | 25 | static var tutorial_subtitle_1: String { 26 | localized("tutorial_subtitle_1") 27 | } 28 | static var tutorial_subtitle_2: String { 29 | localized("tutorial_subtitle_2") 30 | } 31 | static var tutorial_subtitle_3: String { 32 | localized("tutorial_subtitle_3") 33 | } 34 | 35 | static var actionButton_title: String { 36 | localized("actionButton_title") 37 | } 38 | static var actionButton_title_end: String { 39 | localized("actionButton_title_end") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/Localizable/en.lproj/TutorialLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | TutorialLocalizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "tutorial_title_1" = "tutorial_title_1"; 10 | "tutorial_title_2" = "tutorial_title_2"; 11 | "tutorial_title_3" = "tutorial_title_3"; 12 | 13 | "tutorial_subtitle_1" = "tutorial_subtitle_1"; 14 | "tutorial_subtitle_2" = "tutorial_subtitle_2"; 15 | "tutorial_subtitle_3" = "tutorial_subtitle_3"; 16 | 17 | "actionButton_title" = "Continue"; 18 | "actionButton_title_end" = "Start"; 19 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/Localizable/tr.lproj/TutorialLocalizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | TutorialLocalizable.strings 3 | AppSeed 4 | 5 | Created by tunay alver on 24.11.2024. 6 | 7 | */ 8 | 9 | "tutorial_title_1" = "tutorial_title_1"; 10 | "tutorial_title_2" = "tutorial_title_2"; 11 | "tutorial_title_3" = "tutorial_title_3"; 12 | 13 | "tutorial_subtitle_1" = "tutorial_subtitle_1"; 14 | "tutorial_subtitle_2" = "tutorial_subtitle_2"; 15 | "tutorial_subtitle_3" = "tutorial_subtitle_3"; 16 | 17 | "actionButton_title" = "Devam Et"; 18 | "actionButton_title_end" = "Başla"; 19 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/TutorialBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BuilderTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class TutorialBuilder: BaseBuilder { 11 | func build() -> UIViewController { 12 | let router = TutorialRouter() 13 | let viewModel = TutorialViewModel() 14 | let viewController = TutorialViewController(viewModel: viewModel, router: router) 15 | 16 | return viewController 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/TutorialRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouterTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - Route Protocol 11 | protocol TutorialRoute { 12 | func showTutorial() 13 | } 14 | 15 | extension TutorialRoute where Self: BaseRouter { 16 | func showTutorial() { 17 | let vc = TutorialBuilder().build() 18 | SceneDelegate.setToWindow(vc) 19 | } 20 | } 21 | 22 | // MARK: - Router 23 | final class TutorialRouter: BaseRouter, ScrollTestRoute { } 24 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/TutorialViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class TutorialViewController: BaseViewController { 12 | // MARK: - UI 13 | private lazy var collectionView: BaseCollectionView = { 14 | let layout = UICollectionViewFlowLayout() 15 | layout.scrollDirection = .horizontal 16 | let view = BaseCollectionView(frame: .zero, collectionViewLayout: layout) 17 | view.isPagingEnabled = true 18 | return view 19 | }() 20 | private lazy var pageControl: UIPageControl = { 21 | let view = UIPageControl() 22 | view.currentPageIndicatorTintColor = Palette.palette1.color 23 | view.pageIndicatorTintColor = ColorBackground.backgroundSecondary.color 24 | view.currentPage = 0 25 | view.numberOfPages = viewModel?.models.count ?? 0 26 | return view 27 | }() 28 | private lazy var actionView: UIView = { 29 | let view = UIView() 30 | view.backgroundColor = ColorBackground.backgroundSecondary.color 31 | return view 32 | }() 33 | private lazy var actionButton: BaseButton = { 34 | let button = BaseButton(style: .primary) 35 | button.setTitle(TutorialLocalizable.actionButton_title, for: .normal) 36 | button.addTarget(self, action: #selector(actionButtonTapped), for: .touchUpInside) 37 | return button 38 | }() 39 | 40 | // MARK: - Life Cycle 41 | override func viewDidLoad() { 42 | super.viewDidLoad() 43 | } 44 | 45 | // MARK: - Prepare 46 | override func prepare() { 47 | super.prepare() 48 | draw() 49 | prepareCollectionView() 50 | } 51 | 52 | private func prepareCollectionView() { 53 | collectionView.dataSource = self 54 | collectionView.delegate = self 55 | collectionView.register(TutorialCell.self) 56 | } 57 | 58 | // MARK: - Bind 59 | override func bindViewModel() { 60 | super.bindViewModel() 61 | } 62 | 63 | // MARK: - Action 64 | @objc private func actionButtonTapped() { 65 | let lastPage = (viewModel?.numberOfItems() ?? 0) - 1 66 | if pageControl.currentPage == lastPage { 67 | debugPrint("LAST PAGE ACTION") 68 | router?.presentScrollTest() 69 | } else { 70 | let nextIndex = min(pageControl.currentPage + 1, lastPage) 71 | let indexPath = IndexPath(item: nextIndex, section: 0) 72 | guard collectionView.isValid(indexPath: indexPath) else { return } 73 | pageControl.currentPage = nextIndex 74 | collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) 75 | } 76 | } 77 | } 78 | 79 | // MARK: - CollectionView DataSource & Delegate 80 | extension TutorialViewController: UICollectionViewDataSource { 81 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 82 | viewModel?.numberOfItems() ?? 0 83 | } 84 | 85 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 86 | let tutorial = viewModel?.getTutorial(indexPath.row) 87 | let cell: TutorialCell = collectionView.dequeueReusableCell(for: indexPath) 88 | cell.configure(with: tutorial) 89 | return cell 90 | } 91 | } 92 | 93 | // MARK: - UIScrollView Delegate 94 | extension TutorialViewController: UIScrollViewDelegate { 95 | func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 96 | let x = targetContentOffset.pointee.x 97 | pageControl.currentPage = Int(round(x / view.frame.width)) 98 | } 99 | } 100 | 101 | // MARK: - CollectionView UICollectionViewDelegateFlowLayout 102 | extension TutorialViewController: UICollectionViewDelegateFlowLayout { 103 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 104 | return collectionView.frame.size 105 | } 106 | 107 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 108 | return CGFloat.leastNonzeroMagnitude 109 | } 110 | 111 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 112 | return CGFloat.leastNonzeroMagnitude 113 | } 114 | 115 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 116 | return UIEdgeInsets(top: .zero, left: .zero, bottom: .zero, right: .zero) 117 | } 118 | } 119 | 120 | // MARK: - Draw 121 | private extension TutorialViewController { 122 | private func draw() { 123 | view.addSubview(actionView) 124 | actionView.snp.makeConstraints { 125 | $0.left.right.equalToSuperview() 126 | $0.bottom.equalToSuperview() 127 | } 128 | 129 | actionView.addSubview(actionButton) 130 | actionButton.snp.makeConstraints { 131 | $0.left.equalToSuperview().offset(16) 132 | $0.right.equalToSuperview().offset(-16) 133 | $0.height.equalTo(48) 134 | $0.top.equalToSuperview().offset(16) 135 | $0.bottom.equalTo(actionView.safeAreaLayoutGuide.snp.bottom) 136 | } 137 | 138 | view.addSubview(pageControl) 139 | pageControl.snp.makeConstraints { 140 | $0.left.right.equalToSuperview() 141 | $0.centerX.equalToSuperview() 142 | $0.height.equalTo(16) 143 | $0.bottom.equalTo(actionView.snp.top).offset(-8) 144 | } 145 | 146 | view.addSubview(collectionView) 147 | collectionView.snp.makeConstraints { 148 | $0.top.left.right.equalToSuperview() 149 | $0.bottom.equalTo(pageControl.snp.top).offset(-8) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/TutorialViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Source 11 | protocol TutorialViewModelDataSource { 12 | var models: [TutorialUIModel] { get } 13 | } 14 | 15 | // MARK: - Closure 16 | protocol TutorialViewModelClosureSource { 17 | var completedClosure: EmptyClosure? { get set } 18 | } 19 | 20 | // MARK: - Function 21 | protocol TutorialViewModelFunctionSource { 22 | func numberOfItems() -> Int 23 | func getTutorial(_ index: Int) -> TutorialUIModel 24 | } 25 | 26 | // MARK: - Protocol 27 | protocol TutorialViewModelProtocol: BaseViewModel, TutorialViewModelDataSource, TutorialViewModelClosureSource, TutorialViewModelFunctionSource { } 28 | 29 | // MARK: - ViewModel 30 | final class TutorialViewModel: BaseViewModel, TutorialViewModelProtocol { 31 | // MARK: - Source 32 | var models: [TutorialUIModel] = TutorialUIModel.getTutorialData() 33 | 34 | // MARK: - Closure 35 | var completedClosure: EmptyClosure? 36 | 37 | // MARK: - Function 38 | func numberOfItems() -> Int { 39 | return models.count 40 | } 41 | 42 | func getTutorial(_ index: Int) -> TutorialUIModel { 43 | return models[index] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /AppSeed/Scenes/Tutorial/UIModel/TutorialUIModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialUIModel.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 24.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TutorialUIModel { 11 | let image: TutorialImage? 12 | let title: String? 13 | let subtitle: String? 14 | } 15 | 16 | // MARK: - Models 17 | extension TutorialUIModel { 18 | static func getTutorialData() -> [TutorialUIModel] { 19 | let models = [ 20 | TutorialUIModel( 21 | image: .tutorial_1, 22 | title: TutorialLocalizable.tutorial_title_1, 23 | subtitle: TutorialLocalizable.tutorial_subtitle_1 24 | ), 25 | TutorialUIModel( 26 | image: .tutorial_2, 27 | title: TutorialLocalizable.tutorial_title_2, 28 | subtitle: TutorialLocalizable.tutorial_subtitle_2 29 | ), 30 | TutorialUIModel( 31 | image: .tutorial_3, 32 | title: TutorialLocalizable.tutorial_title_3, 33 | subtitle: TutorialLocalizable.tutorial_subtitle_3 34 | ) 35 | ] 36 | 37 | return models 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AppSeed/UIComponents/View/HudView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HudView.swift 3 | // AppSeed 4 | // 5 | // Created by tunay alver on 4.01.2024. 6 | // 7 | 8 | import UIKit 9 | import Lottie 10 | import SnapKit 11 | 12 | final class HudView: UIView { 13 | // MARK: - Properties 14 | private lazy var lottieView: LottieAnimationView = { 15 | let view = LottieAnimationView(name: "lottie-loading") 16 | view.contentMode = .scaleAspectFit 17 | view.loopMode = .loop 18 | return view 19 | }() 20 | 21 | private lazy var logo: CircleImageView = { 22 | let view = CircleImageView(frame: .zero) 23 | view.image = Logo.logo_1024.image 24 | view.contentMode = .scaleAspectFit 25 | return view 26 | }() 27 | 28 | override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | draw() 31 | prepare() 32 | lottieView.play() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | super.init(coder: aDecoder) 37 | draw() 38 | prepare() 39 | lottieView.play() 40 | } 41 | 42 | func prepare() { 43 | let backgroundColor = ColorBackground.backgroundPrimary.color 44 | self.backgroundColor = backgroundColor.withAlphaComponent(0.5) 45 | } 46 | } 47 | 48 | private extension HudView { 49 | private func draw() { 50 | addSubview(lottieView) 51 | lottieView.snp.makeConstraints { make in 52 | make.centerX.centerY.equalToSuperview() 53 | make.width.equalTo(80) 54 | make.height.equalTo(80) 55 | } 56 | 57 | addSubview(logo) 58 | logo.snp.makeConstraints { make in 59 | make.centerX.centerY.equalToSuperview() 60 | make.width.equalTo(40) 61 | make.height.equalTo(40) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Swift](https://github.com/devno39/AppSeed/blob/develop/AppSeed/Resources/Assets.xcassets/Logo/logo_40.imageset/1x%2040pt%201.png?raw=true) AppSeed 2 | 3 | This is a seed application designed for kickstarting new projects for Swift. Pull this repository and customize the necessary components to serve as a base for new applications. It includes various extensions, base classes, helpers, and packages curated for use in our projects. 4 | 5 | ## Usage 6 | 7 | To install and use AppSeed, follow these steps: 8 | 9 | 1. Clone the `develop` branch of this repository: 10 | 11 | ```bash 12 | git clone -b develop https://github.com/devno39/AppSeed.git 13 | ``` 14 | 15 | 2. Install templates: 16 | 17 | ```bash 18 | cd ...PATH.../AppSeed/Templates 19 | ``` 20 | ```bash 21 | swift install.swift 22 | ``` 23 | 24 | 2. Rename to suit your project's name: 25 | 26 | ```bash 27 | cd ...PATH.../AppSeed/Renamer 28 | ``` 29 | ```bash 30 | swift Renamer.swift AppSeed NewProjectName 31 | ``` 32 | 33 | 34 | ### Contributing 35 | 36 | We welcome contributions to AppSeed. To contribute: 37 | 38 | 1. Open an issue with a clear title and detailed description. 39 | 2. For pull requests: 40 | - Name your branch using the format: `feature/issue39`. If there is no issue `feature/name_name` is just fine. 41 | - Make sure to open the pull request into the `develop` branch. 42 | - Reviews will be conducted before merging. 43 | 44 | ## Get in Touch 45 | 46 |

47 | 48 | https://www.instagram.com/devno39 49 | @devno39 50 | 51 |

52 | -------------------------------------------------------------------------------- /Renamer/Renamer.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env swift 2 | 3 | import Foundation 4 | 5 | final class Renamer: NSObject { 6 | // MARK: - Properties 7 | let fileManager = FileManager.default 8 | var processedPaths = [String]() 9 | 10 | var oldName: String 11 | let newName: String 12 | 13 | // MARK: - Init 14 | init(oldName: String, newName: String) { 15 | self.oldName = oldName 16 | self.newName = newName 17 | } 18 | 19 | // MARK: - API 20 | func run() { 21 | var currentPath = fileManager.currentDirectoryPath 22 | let scriptPath: URL = URL(fileURLWithPath: currentPath) 23 | var folderPath: URL = scriptPath.deletingLastPathComponent() // Go one level up 24 | 25 | print("Script is running in directory: \(folderPath.path)") 26 | 27 | if let oldProjectName = findOldProjectName(folderPath) { 28 | oldName = oldProjectName 29 | print("Old project name found: \(oldName). Processing continues.") 30 | } else { 31 | print("Error: Could not detect an .xcodeproj file in \(folderPath.path).") 32 | print("Please provide the old project name manually:") 33 | if let name = readLine(), !name.isEmpty { 34 | oldName = name 35 | } else { 36 | print("No project name entered. Exiting.") 37 | exit(1) 38 | } 39 | } 40 | 41 | if fileManager.changeCurrentDirectoryPath(folderPath.path) { 42 | print("\nSuccess: Changed to project directory.") 43 | currentPath = fileManager.currentDirectoryPath 44 | } else { 45 | print("Error: Could not change to project directory at \(folderPath.path). Exiting.") 46 | exit(1) 47 | } 48 | 49 | print("Current directory is \(currentPath)") 50 | print("\n------------------------------------------") 51 | print("Rename Xcode Project from [\(oldName)] to [\(newName)]") 52 | print("------------------------------------------\n") 53 | 54 | if validatePath(currentPath) { 55 | enumeratePath(currentPath) 56 | } else { 57 | print("Error: Xcode project or workspace with name [\(oldName)] not found.") 58 | exit(1) 59 | } 60 | 61 | print("\n------------------------------------------") 62 | print("Xcode Project Rename Finished!") 63 | print("------------------------------------------\n") 64 | } 65 | 66 | // MARK: - Helpers 67 | private func findOldProjectName(_ projectFolderPath: URL) -> String? { 68 | do { 69 | let directoryContents = try fileManager.contentsOfDirectory(at: projectFolderPath, includingPropertiesForKeys: nil, options: []) 70 | print("Searching for .xcodeproj in \(projectFolderPath.path)...") 71 | guard let projectFileName = directoryContents.filter({ $0.pathExtension == "xcodeproj" }).first?.deletingPathExtension().lastPathComponent else { 72 | return nil 73 | } 74 | return projectFileName 75 | } catch { 76 | print("Error reading directory contents: \(error.localizedDescription)") 77 | return nil 78 | } 79 | } 80 | 81 | private func validatePath(_ path: String) -> Bool { 82 | let projectPath = path.appending("/\(oldName).xcodeproj") 83 | let workspacePath = path.appending("/\(oldName).xcworkspace") 84 | let isValid = fileManager.fileExists(atPath: projectPath) || fileManager.fileExists(atPath: workspacePath) 85 | return isValid 86 | } 87 | 88 | private func enumeratePath(_ path: String) { 89 | let enumerator = fileManager.enumerator(atPath: path) 90 | while let element = enumerator?.nextObject() as? String { 91 | let itemPath = path.appending("/\(element)") 92 | if !processedPaths.contains(itemPath) && !shouldSkip(element) { 93 | processPath(itemPath) 94 | } 95 | } 96 | } 97 | 98 | private func processPath(_ path: String) { 99 | print("Processing: \(path)") 100 | 101 | var isDir: ObjCBool = false 102 | if fileManager.fileExists(atPath: path, isDirectory: &isDir) { 103 | if isDir.boolValue { 104 | enumeratePath(path) 105 | } else { 106 | updateContentsOfFile(atPath: path) 107 | } 108 | renameItem(atPath: path) 109 | } 110 | 111 | processedPaths.append(path) 112 | } 113 | 114 | private func shouldSkip(_ element: String) -> Bool { 115 | guard 116 | !element.hasPrefix("."), 117 | !element.contains(".DS_Store"), 118 | !element.contains("Carthage"), 119 | !element.contains("Pods"), 120 | !element.contains("fastlane"), 121 | !element.contains("build") 122 | else { return true } 123 | 124 | let fileExtension = URL(fileURLWithPath: element).pathExtension 125 | switch fileExtension { 126 | case "appiconset", "json", "png", "xcuserstate": 127 | return true 128 | default: 129 | return false 130 | } 131 | } 132 | 133 | private func updateContentsOfFile(atPath path: String) { 134 | do { 135 | let oldContent = try String(contentsOfFile: path, encoding: .utf8) 136 | if oldContent.contains(oldName) { 137 | var newContent = oldContent.replacingOccurrences(of: oldName, with: newName) 138 | if oldContent.contains("\(oldName)Tests") || oldContent.contains("\(oldName)UITests") { 139 | let testClassOldName = oldName.replacingOccurrences(of: "-", with: "_") 140 | newContent = newContent.replacingOccurrences(of: testClassOldName, with: newName) 141 | } 142 | try newContent.write(toFile: path, atomically: true, encoding: .utf8) 143 | } 144 | } catch { 145 | print("Error updating file: \(error.localizedDescription)") 146 | } 147 | } 148 | 149 | private func renameItem(atPath path: String) { 150 | do { 151 | let oldItemName = URL(fileURLWithPath: path).lastPathComponent 152 | if oldItemName.contains(oldName) { 153 | let newItemName = oldItemName.replacingOccurrences(of: oldName, with: newName) 154 | let directoryURL = URL(fileURLWithPath: path).deletingLastPathComponent() 155 | let newPath = directoryURL.appendingPathComponent(newItemName).path 156 | try fileManager.moveItem(atPath: path, toPath: newPath) 157 | print("-- Renamed: \(oldItemName) -> \(newItemName)") 158 | } 159 | } catch { 160 | print("Error renaming file: \(error.localizedDescription)") 161 | } 162 | } 163 | } 164 | 165 | let arguments = CommandLine.arguments 166 | if arguments.count == 3 { 167 | let oldName = arguments[1] 168 | let newName = arguments[2].replacingOccurrences(of: " ", with: "") 169 | let renamer = Renamer(oldName: oldName, newName: newName) 170 | renamer.run() 171 | } else { 172 | print("Invalid number of arguments! Expected OLD and NEW project name.") 173 | } 174 | -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/Templates/AppSeedScene.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devno39/AppSeed/4a6584a4f051b2b4e8238485788fb059b5b8acab/Templates/AppSeedScene.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | AppSeed Scene 7 | Kind 8 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 9 | Description 10 | Template for creating a new ViewController, ViewModel, Router, and Builder 11 | Summary 12 | Creates ViewController, ViewModel, Router, and Builder 13 | Platforms 14 | 15 | com.apple.platform.iphoneos 16 | com.apple.platform.macosx 17 | com.apple.platform.watchos 18 | com.apple.platform.appletvos 19 | 20 | Options 21 | 22 | 23 | Identifier 24 | name 25 | Name 26 | Scene Name 27 | Type 28 | text 29 | Default 30 | MyScene 31 | 32 | 33 | RequiredOptions 34 | 35 | name 36 | 37 | Nodes 38 | 39 | 40 | Group 41 | ViewController 42 | Path 43 | ___VARIABLE_name___ViewController.swift 44 | 45 | 46 | Group 47 | ViewModel 48 | Path 49 | ___VARIABLE_name___ViewModel.swift 50 | 51 | 52 | Group 53 | Router 54 | Path 55 | ___VARIABLE_name___Router.swift 56 | 57 | 58 | Group 59 | Builder 60 | Path 61 | ___VARIABLE_name___Builder.swift 62 | 63 | 64 | Version 65 | 1.0 66 | 67 | 68 | -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/___VARIABLE_name___Builder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BuilderTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class ___VARIABLE_name___Builder: BaseBuilder { 11 | func build() -> UIViewController { 12 | let router = ___VARIABLE_name___Router() 13 | let viewModel = ___VARIABLE_name___ViewModel() 14 | let viewController = ___VARIABLE_name___ViewController(viewModel: viewModel, router: router) 15 | 16 | return viewController 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/___VARIABLE_name___Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouterTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - Route Protocol 11 | protocol ___VARIABLE_name___Route { 12 | func push___VARIABLE_name___() 13 | } 14 | 15 | extension ___VARIABLE_name___Route where Self: BaseRouterProtocol { 16 | func push___VARIABLE_name___() { 17 | let vc = ___VARIABLE_name___Builder().build() 18 | viewController?.navigationController?.pushViewController(vc, animated: true) 19 | } 20 | } 21 | 22 | // MARK: - Router 23 | final class ___VARIABLE_name___Router: BaseRouter { } 24 | -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/___VARIABLE_name___ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class ___VARIABLE_name___ViewController: BaseViewController<___VARIABLE_name___ViewModel, ___VARIABLE_name___Router> { 12 | // MARK: - Life Cycle 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | } 16 | 17 | // MARK: - Prepare 18 | override func prepare() { 19 | super.prepare() 20 | draw() 21 | } 22 | 23 | // MARK: - Bind 24 | override func bindViewModel() { 25 | super.bindViewModel() 26 | } 27 | } 28 | 29 | // MARK: - Draw 30 | private extension ___VARIABLE_name___ViewController { 31 | private func draw() {} 32 | } 33 | -------------------------------------------------------------------------------- /Templates/AppSeedScene.xctemplate/___VARIABLE_name___ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelTemplate.swift 3 | // AppSeed 4 | // 5 | // Created by Tunay Alver on 17.11.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Source 11 | protocol ___VARIABLE_name___ViewModelDataSource { } 12 | 13 | // MARK: - Closure 14 | protocol ___VARIABLE_name___ViewModelClosureSource { } 15 | 16 | // MARK: - Function 17 | protocol ___VARIABLE_name___ViewModelFunctionSource { } 18 | 19 | // MARK: - Protocol 20 | protocol ___VARIABLE_name___ViewModelProtocol: BaseViewModel, ___VARIABLE_name___ViewModelDataSource, ___VARIABLE_name___ViewModelClosureSource, ___VARIABLE_name___ViewModelFunctionSource { } 21 | 22 | // MARK: - ViewModel 23 | final class ___VARIABLE_name___ViewModel: BaseViewModel, ___VARIABLE_name___ViewModelProtocol { 24 | // MARK: - Source 25 | 26 | // MARK: - Closure 27 | 28 | // MARK: - Function 29 | } 30 | -------------------------------------------------------------------------------- /Templates/install.swift: -------------------------------------------------------------------------------- 1 | // Run with "swift install.swift" command 2 | import Foundation 3 | 4 | let destinationFolder = "Library/Developer/Xcode/Templates/File Templates/Source" 5 | let homeDirectoryURL = FileManager.default.homeDirectoryForCurrentUser 6 | let destinationFolderURL = homeDirectoryURL.appendingPathComponent(destinationFolder) 7 | 8 | func install() { 9 | 10 | let fileManager = FileManager.default 11 | let currentPath = fileManager.currentDirectoryPath 12 | 13 | do { 14 | let files = try fileManager.contentsOfDirectory(atPath: currentPath) 15 | for file in files { 16 | if file == "install.swift" || file == ".DS_Store" { 17 | continue 18 | } 19 | let template = file 20 | let templateURL = URL(fileURLWithPath: template) 21 | let destinationFileURL = destinationFolderURL.appendingPathComponent(template) 22 | 23 | if FileManager.default.fileExists(atPath: destinationFileURL.path) { 24 | try FileManager.default.removeItem(at: destinationFileURL) 25 | try FileManager.default.copyItem(at: templateURL, to: destinationFileURL) 26 | print("✅ Template already exists. So has been replaced successfully 🎉.") 27 | } else { 28 | if FileManager.default.fileExists(atPath: destinationFolderURL.path) == false { 29 | try FileManager.default.createDirectory(at: destinationFolderURL, withIntermediateDirectories: true, attributes: nil) 30 | } 31 | try FileManager.default.copyItem(at: templateURL, to: destinationFileURL) 32 | print("✅ Template installed successfully 🎉.") 33 | } 34 | } 35 | } catch { 36 | print("❌ Ooops! Something went wrong.") 37 | } 38 | } 39 | 40 | install() 41 | -------------------------------------------------------------------------------- /txts/privacy_policy_dreamcatcher.txt: -------------------------------------------------------------------------------- 1 | Privacy Policy for Dreamcatcher 2 | Gizlilik Politikası - Dreamcatcher 3 | 4 | Effective Date: May 11, 2025 5 | Yürürlük Tarihi: 11 Mayıs 2025 6 | 7 | Dreamcatcher ("we", "us", or "our") respects your privacy. 8 | Dreamcatcher ("biz") gizliliğinize saygı duyar. 9 | 10 | - We do not collect personal information unless explicitly provided. 11 | - Kişisel bilgilerinizi açıkça vermediğiniz sürece toplamıyoruz. 12 | 13 | - We may collect anonymized usage data for improving user experience. 14 | - Kullanıcı deneyimini geliştirmek için anonim kullanım verileri toplayabiliriz. 15 | 16 | By using Dreamcatcher, you agree to this Privacy Policy. 17 | Dreamcatcher uygulamasını kullanarak bu gizlilik politikasını kabul etmiş oluyorsunuz. -------------------------------------------------------------------------------- /txts/terms_of_use_dreamcatcher.txt: -------------------------------------------------------------------------------- 1 | Terms of Use for Dreamcatcher 2 | Kullanım Şartları - Dreamcatcher 3 | 4 | Effective Date: May 11, 2025 5 | Yürürlük Tarihi: 11 Mayıs 2025 6 | 7 | By using Dreamcatcher, you agree to the following terms: 8 | Dreamcatcher uygulamasını kullanarak aşağıdaki şartları kabul etmiş oluyorsunuz: 9 | 10 | - Subscription renews automatically unless canceled 24 hours before the period ends. 11 | - Abonelik süresi bitmeden 24 saat önce iptal edilmediği sürece abonelik otomatik olarak yenilenir. 12 | 13 | - Prices and terms may change with prior notice. 14 | - Fiyatlar ve şartlar önceden bildirilerek değiştirilebilir. 15 | 16 | If you do not agree to these terms, do not use the App. 17 | Bu şartları kabul etmiyorsanız uygulamayı kullanmayınız. --------------------------------------------------------------------------------