├── .DS_Store ├── iWallet ├── Assets.xcassets │ ├── Contents.json │ ├── ColorRed │ │ ├── Contents.json │ │ ├── colorRed.colorset │ │ │ └── Contents.json │ │ ├── colorRed1.colorset │ │ │ └── Contents.json │ │ └── colorRed2.colorset │ │ │ └── Contents.json │ ├── Image │ │ ├── Contents.json │ │ └── icon.imageset │ │ │ ├── icon 1.png │ │ │ ├── icon 2.png │ │ │ ├── icon.png │ │ │ └── Contents.json │ ├── ColorBlue │ │ ├── Contents.json │ │ ├── colorBlue.colorset │ │ │ └── Contents.json │ │ ├── colorBlue1.colorset │ │ │ └── Contents.json │ │ └── colorBlue2.colorset │ │ │ └── Contents.json │ ├── ColorBrown │ │ ├── Contents.json │ │ ├── colorBrown.colorset │ │ │ └── Contents.json │ │ ├── colorBrown1.colorset │ │ │ └── Contents.json │ │ └── colorBrown2.colorset │ │ │ └── Contents.json │ ├── ColorGray │ │ ├── Contents.json │ │ ├── colorGray1.colorset │ │ │ └── Contents.json │ │ ├── colorGray2.colorset │ │ │ └── Contents.json │ │ └── colorGray.colorset │ │ │ └── Contents.json │ ├── ColorGreen │ │ ├── Contents.json │ │ ├── colorGreen.colorset │ │ │ └── Contents.json │ │ ├── colorGreen1.colorset │ │ │ └── Contents.json │ │ └── colorGreen2.colorset │ │ │ └── Contents.json │ ├── ColorOther │ │ ├── Contents.json │ │ ├── colorBG.colorset │ │ │ └── Contents.json │ │ ├── colorBalanceBG.colorset │ │ │ └── Contents.json │ │ ├── colorBlack.colorset │ │ │ └── Contents.json │ │ ├── colorBalanceText.colorset │ │ │ └── Contents.json │ │ └── colorPickerBG.colorset │ │ │ └── Contents.json │ ├── ColorPurple │ │ ├── Contents.json │ │ ├── colorPurple.colorset │ │ │ └── Contents.json │ │ ├── colorPurple1.colorset │ │ │ └── Contents.json │ │ └── colorPurple2.colorset │ │ │ └── Contents.json │ ├── ColorYellow │ │ ├── Contents.json │ │ ├── colorYellow.colorset │ │ │ └── Contents.json │ │ ├── colorYellow1.colorset │ │ │ └── Contents.json │ │ └── colorYellow2.colorset │ │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── iWalletIcon.jpg │ │ └── Contents.json │ └── AccentColor.colorset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Other │ └── PlayFeedbackHaptic.swift ├── Model │ ├── CategoryTypeModel.swift │ ├── TransactionModel.swift │ ├── CategoryModel.swift │ ├── CurrencyModel.swift │ └── DefaultCategoriesModel.swift ├── ViewModel │ ├── AppViewModel.swift │ ├── CategoryViewModel.swift │ └── TransactionViewModel.swift ├── Extension │ └── ExtensionFloat.swift ├── View │ ├── Templates │ │ ├── ColorPickerView.swift │ │ ├── IconPickerView.swift │ │ ├── BalanceView.swift │ │ └── PreviewCardView.swift │ ├── Category │ │ ├── CategoryItemView.swift │ │ ├── CategoryView.swift │ │ ├── PickerCategoryView.swift │ │ └── AddCategoryView.swift │ ├── Welcome │ │ └── WelcomeView.swift │ ├── Transaction │ │ ├── TransactionView.swift │ │ ├── TransactionCategoryView.swift │ │ ├── EditTransactionView.swift │ │ └── AddTransactionView.swift │ ├── Home │ │ └── HomeView.swift │ └── Setting │ │ └── SettingView.swift ├── App │ └── iWalletApp.swift ├── Resources │ ├── Colors.swift │ └── Icons.swift └── Localizable │ ├── en.lproj │ └── Localizable.strings │ └── ru.lproj │ └── Localizable.strings ├── iWallet.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── idevnva.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── IDEFindNavigatorScopes.plist │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── idevnva.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iWalletTeam/iWallet/HEAD/.DS_Store -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorRed/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/Image/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBlue/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBrown/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGray/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGreen/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorOther/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorPurple/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorYellow/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/Image/icon.imageset/icon 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iWalletTeam/iWallet/HEAD/iWallet/Assets.xcassets/Image/icon.imageset/icon 1.png -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/Image/icon.imageset/icon 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iWalletTeam/iWallet/HEAD/iWallet/Assets.xcassets/Image/icon.imageset/icon 2.png -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/Image/icon.imageset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iWalletTeam/iWallet/HEAD/iWallet/Assets.xcassets/Image/icon.imageset/icon.png -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/AppIcon.appiconset/iWalletIcon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iWalletTeam/iWallet/HEAD/iWallet/Assets.xcassets/AppIcon.appiconset/iWalletIcon.jpg -------------------------------------------------------------------------------- /iWallet.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iWallet/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 | -------------------------------------------------------------------------------- /iWallet.xcodeproj/project.xcworkspace/xcuserdata/idevnva.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iWalletTeam/iWallet/HEAD/iWallet.xcodeproj/project.xcworkspace/xcuserdata/idevnva.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /iWallet.xcodeproj/project.xcworkspace/xcuserdata/idevnva.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iWalletIcon.jpg", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /iWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iWallet/Other/PlayFeedbackHaptic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayFeedbackHaptic.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public func playFeedbackHaptic(_ selected: Bool) { 11 | if selected == true { 12 | let generator = UIImpactFeedbackGenerator(style: .light) 13 | generator.impactOccurred() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /iWallet/Model/CategoryTypeModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryType.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | enum CategoryType: String, PersistableEnum, CaseIterable { 12 | case expense = "Expense" 13 | case income = "Income" 14 | 15 | func localizedName() -> String { 16 | return NSLocalizedString(self.rawValue, comment: "") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/Image/icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icon 1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icon 2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /iWallet/Model/TransactionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransactionModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | class TransactionItem: Object, ObjectKeyIdentifiable { 12 | @Persisted(primaryKey: true) var id: ObjectId = ObjectId.generate() 13 | @Persisted var categoryId: ObjectId = ObjectId.generate() 14 | @Persisted var amount: Float = 0 15 | @Persisted var note: String = "" 16 | @Persisted var date: Date = Date() 17 | @Persisted var type: CategoryType = .expense 18 | @Persisted(originProperty: "transactions") var category: LinkingObjects 19 | } 20 | 21 | -------------------------------------------------------------------------------- /iWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "realm-core", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/realm/realm-core.git", 7 | "state" : { 8 | "revision" : "36b2df44453ba5e830369c76ab0683799a287605", 9 | "version" : "13.13.0" 10 | } 11 | }, 12 | { 13 | "identity" : "realm-swift", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/realm/realm-swift.git", 16 | "state" : { 17 | "branch" : "master", 18 | "revision" : "eed598a7aa9b56fcf7273ea4279a516af8f7d3f3" 19 | } 20 | } 21 | ], 22 | "version" : 2 23 | } 24 | -------------------------------------------------------------------------------- /iWallet/ViewModel/AppViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmViewModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | import SwiftUI 11 | 12 | final class AppViewModel: ObservableObject { 13 | 14 | @AppStorage("playFeedbackHaptic") var selectedFeedbackHaptic: Bool = true 15 | @AppStorage("hasRunBefore") var hasRunBefore: Bool = false 16 | @AppStorage("currencySymbol") var currencySymbol: String = "USD" 17 | @AppStorage("roundingNumbers") var roundingNumbers: Bool = false 18 | 19 | init() { 20 | let config = Realm.Configuration(schemaVersion: 15) 21 | Realm.Configuration.defaultConfiguration = config 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /iWallet/Extension/ExtensionFloat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionFloat.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | // метод возвращает сумму с точками после каждых трех символов 11 | extension Float { 12 | func formattedWithSeparatorAndCurrency(roundingNumbers: Bool) -> String { 13 | let formatter = NumberFormatter() 14 | formatter.numberStyle = .decimal 15 | if roundingNumbers == true { 16 | formatter.maximumFractionDigits = 0 17 | } else { 18 | formatter.maximumFractionDigits = 2 19 | } 20 | let formattedNumber = formatter.string(from: NSNumber(value: self)) ?? "\(self)" 21 | return formattedNumber 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorOther/colorBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.949", 9 | "green" : "0.949", 10 | "red" : "0.945" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "gray-gamma-22", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "white" : "0.060" 27 | } 28 | }, 29 | "idiom" : "universal" 30 | } 31 | ], 32 | "info" : { 33 | "author" : "xcode", 34 | "version" : 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBlue/colorBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xD3", 10 | "red" : "0xB7" 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" : "0xFF", 27 | "green" : "0xD3", 28 | "red" : "0xB7" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBlue/colorBlue1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF8", 9 | "green" : "0xF0", 10 | "red" : "0xCA" 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" : "0xF8", 27 | "green" : "0xF0", 28 | "red" : "0xCA" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBlue/colorBlue2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xE4", 9 | "green" : "0xCA", 10 | "red" : "0x48" 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" : "0xE4", 27 | "green" : "0xCA", 28 | "red" : "0x48" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBrown/colorBrown.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xD4", 9 | "green" : "0xE0", 10 | "red" : "0xED" 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" : "0xD4", 27 | "green" : "0xE0", 28 | "red" : "0xED" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGray/colorGray1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF2", 9 | "green" : "0xF2", 10 | "red" : "0xF1" 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" : "0xF2", 27 | "green" : "0xF2", 28 | "red" : "0xF1" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGray/colorGray2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xC0", 9 | "green" : "0xBE", 10 | "red" : "0xBC" 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" : "0xC0", 27 | "green" : "0xBE", 28 | "red" : "0xBC" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorRed/colorRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x82", 9 | "green" : "0x9F", 10 | "red" : "0xFA" 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.510", 27 | "green" : "0.624", 28 | "red" : "0.984" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorRed/colorRed1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xD0", 9 | "green" : "0xCA", 10 | "red" : "0xF7" 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" : "0xD0", 27 | "green" : "0xCA", 28 | "red" : "0xF7" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorRed/colorRed2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x75", 9 | "green" : "0x83", 10 | "red" : "0xF3" 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" : "0x75", 27 | "green" : "0x83", 28 | "red" : "0xF3" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGray/colorGray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x97", 9 | "green" : "0x94", 10 | "red" : "0x92" 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.596", 27 | "green" : "0.584", 28 | "red" : "0.576" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGreen/colorGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x8A", 9 | "green" : "0xBC", 10 | "red" : "0x77" 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.545", 27 | "green" : "0.741", 28 | "red" : "0.467" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGreen/colorGreen1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xC7", 9 | "green" : "0xE4", 10 | "red" : "0xB7" 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" : "0xC7", 27 | "green" : "0xE4", 28 | "red" : "0xB7" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorGreen/colorGreen2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x57", 9 | "green" : "0xC9", 10 | "red" : "0xA7" 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" : "0x57", 27 | "green" : "0xC9", 28 | "red" : "0xA7" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorPurple/colorPurple.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" : "0.761", 10 | "red" : "0.847" 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" : "0xFF", 27 | "green" : "0xC2", 28 | "red" : "0xD8" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorPurple/colorPurple1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xB5", 10 | "red" : "0xB9" 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" : "0xFF", 27 | "green" : "0xB5", 28 | "red" : "0xB9" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorPurple/colorPurple2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xA9", 10 | "red" : "0x9B" 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" : "0xFF", 27 | "green" : "0xA9", 28 | "red" : "0x9B" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorYellow/colorYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x6B", 9 | "green" : "0xC4", 10 | "red" : "0xFF" 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.420", 27 | "green" : "0.769", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorYellow/colorYellow1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0xEA", 10 | "red" : "0xFF" 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" : "0x00", 27 | "green" : "0xEA", 28 | "red" : "0xFF" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorYellow/colorYellow2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0xA2", 10 | "red" : "0xFF" 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" : "0x00", 27 | "green" : "0xA2", 28 | "red" : "0xFF" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBrown/colorBrown1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.698", 9 | "green" : "0.800", 10 | "red" : "0.902" 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.698", 27 | "green" : "0.800", 28 | "red" : "0.902" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorBrown/colorBrown2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.573", 9 | "green" : "0.722", 10 | "red" : "0.867" 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.573", 27 | "green" : "0.722", 28 | "red" : "0.867" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorOther/colorBalanceBG.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.153", 27 | "green" : "0.153", 28 | "red" : "0.153" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorOther/colorBlack.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.153", 9 | "green" : "0.153", 10 | "red" : "0.153" 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.153", 27 | "green" : "0.153", 28 | "red" : "0.153" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorOther/colorBalanceText.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.153", 9 | "green" : "0.153", 10 | "red" : "0.153" 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 | -------------------------------------------------------------------------------- /iWallet/Assets.xcassets/ColorOther/colorPickerBG.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.937", 9 | "green" : "0.933", 10 | "red" : "0.933" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.239", 27 | "green" : "0.227", 28 | "red" : "0.227" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iWallet.xcodeproj/xcuserdata/idevnva.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GettingStarted (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 2 13 | 14 | GettingStarted (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 3 20 | 21 | GettingStarted (Playground).xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 1 27 | 28 | iWallet.xcscheme_^#shared#^_ 29 | 30 | orderHint 31 | 0 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /iWallet/View/Templates/ColorPickerView.swift: -------------------------------------------------------------------------------- 1 | // ColorPickerView.swift 2 | 3 | import SwiftUI 4 | 5 | struct ColorPickerView: View { 6 | @Binding var selectedColor: String 7 | 8 | var body: some View { 9 | ScrollView(.horizontal, showsIndicators: false) { 10 | HStack(alignment: .center) { 11 | ForEach(Colors.allColors, id: \.self) { color in 12 | Circle() 13 | .foregroundColor(Color(color)) 14 | .frame(width: 25, height: 25) 15 | .opacity(color == selectedColor ? 1.0 : 0.5) 16 | .scaleEffect(color == selectedColor ? 1.1 : 1.0) 17 | .onTapGesture { 18 | selectedColor = color 19 | } 20 | } 21 | } 22 | .padding() 23 | } 24 | } 25 | } 26 | 27 | struct ColorPickerView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | ColorPickerView(selectedColor: .constant("colorBlue")) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /iWallet/App/iWalletApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iWalletApp.swift 3 | // iWallet 4 | // 5 | // Created by Владислав Новошинский on 28.03.2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct iWalletApp: App { 12 | @ObservedObject var appVM = AppViewModel() 13 | @ObservedObject var categoryVM = CategoryViewModel() 14 | @ObservedObject var transactionVM = TransactionViewModel() 15 | 16 | var body: some Scene { 17 | 18 | // переменная для поиска пути локального хранения базы данных 19 | // let _ = print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path) 20 | 21 | WindowGroup { 22 | if !appVM.hasRunBefore { 23 | WelcomeView() 24 | .environmentObject(appVM) 25 | .environmentObject(categoryVM) 26 | .environmentObject(transactionVM) 27 | 28 | } else { 29 | HomeView() 30 | .environmentObject(appVM) 31 | .environmentObject(categoryVM) 32 | .environmentObject(transactionVM) 33 | } 34 | } 35 | } 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /iWallet/Model/CategoryModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | class Category: Object, ObjectKeyIdentifiable { 12 | @Persisted(primaryKey: true) var id: ObjectId = ObjectId.generate() 13 | @Persisted var name: String = "" 14 | @Persisted var icon: String = "" 15 | @Persisted var color: String = "" 16 | @Persisted var type: CategoryType = .expense 17 | @Persisted var transactions: List = List() 18 | 19 | // Функция проверяет, есть ли в категории транзакции с выбранным типом (доход или расход) 20 | func hasTransactions(type: CategoryType) -> Bool { 21 | for transaction in transactions { 22 | if transaction.type == type { 23 | return true 24 | } 25 | } 26 | return false 27 | } 28 | 29 | // Функция которая вычисляет сумму всех транзакций определенного типа (категории) из списка транзакций. 30 | func categoryAmount(type: CategoryType) -> Float { 31 | var totalAmount: Float = 0 32 | for transaction in transactions { 33 | if transaction.type == type { 34 | totalAmount += transaction.amount 35 | } 36 | } 37 | return totalAmount 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /iWallet/View/Templates/IconPickerView.swift: -------------------------------------------------------------------------------- 1 | // IconPickerView.swift 2 | 3 | import SwiftUI 4 | 5 | struct IconPickerView: View { 6 | @Binding var selectedImage: String 7 | 8 | var body: some View { 9 | ScrollView(.horizontal, showsIndicators: false) { 10 | HStack(alignment: .center) { 11 | ForEach(Icons.allIcons, id: \.self) { image in 12 | HStack { 13 | VStack { 14 | Image(systemName: image) 15 | .foregroundColor(Color(Colors.mainText)) 16 | .font(.system(size: 20)) 17 | .frame(width: 40, height: 40) 18 | .background { 19 | RoundedRectangle(cornerRadius: 10, style: .circular) 20 | .strokeBorder(Color(Colors.mainText)) 21 | } 22 | } 23 | } 24 | .opacity(image == selectedImage ? 1.0 : 0.5) 25 | .scaleEffect(image == selectedImage ? 1.1 : 1.0) 26 | .onTapGesture { 27 | selectedImage = image 28 | } 29 | } 30 | } 31 | .padding(2) 32 | } 33 | } 34 | } 35 | 36 | struct IconPickerView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | IconPickerView(selectedImage: .constant("folder.circle")) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iWallet/View/Templates/BalanceView.swift: -------------------------------------------------------------------------------- 1 | // BalanceView.swift 2 | 3 | import SwiftUI 4 | 5 | struct BalanceView: View { 6 | @EnvironmentObject var appVM: AppViewModel 7 | 8 | let amount: Float 9 | let curren: String 10 | let type: String 11 | let icon: String 12 | let iconBG: Color 13 | 14 | var body: some View { 15 | VStack { 16 | VStack(alignment: .leading) { 17 | HStack { 18 | Image(systemName: icon) 19 | .foregroundColor(Color(Colors.colorBlack)) 20 | .frame(width: 30, height: 30) 21 | .background(iconBG) 22 | .cornerRadius(7.5) 23 | Spacer() 24 | Text("\(amount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers)) \(curren)") 25 | .font(.headline) 26 | .fontWeight(.bold) 27 | .foregroundColor(Color(Colors.mainText)) 28 | } 29 | 30 | HStack { 31 | Text(type) 32 | .foregroundColor(.gray).textCase(.uppercase) 33 | .font(.subheadline).dynamicTypeSize(.small) 34 | } .padding(.top, 5) 35 | } 36 | .padding(10) 37 | .padding(.vertical, 5) 38 | .background(Color(Colors.colorBalanceBG)) 39 | .cornerRadius(10) 40 | .frame(maxWidth: .infinity, maxHeight: .infinity) 41 | } 42 | } 43 | } 44 | 45 | struct BalanceView_Previews: PreviewProvider { 46 | static var previews: some View { 47 | BalanceView(amount: 1000, curren: "$", type: "Income", icon: "plus", iconBG: .blue) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /iWallet/Resources/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 26.05.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Colors { 11 | 12 | // основные цвета 13 | static let colorBalanceBG: String = "colorBalanceBG" 14 | static let mainText: String = "colorBalanceText" 15 | static let mainBG: String = "colorBG" 16 | static let colorBlack: String = "colorBlack" 17 | static let colorPickerBG: String = "colorPickerBG" 18 | 19 | // дополнительные цвета для иконок кол-во 21 20 | static let colorBlue: String = "colorBlue" 21 | static let colorBlue1: String = "colorBlue1" 22 | static let colorBlue2: String = "colorBlue2" 23 | static let colorBrown: String = "colorBrown" 24 | static let colorBrown1: String = "colorBrown1" 25 | static let colorBrown2: String = "colorBrown2" 26 | static let colorGray: String = "colorGray" 27 | static let colorGray1: String = "colorGray1" 28 | static let colorGray2: String = "colorGray2" 29 | static let colorGreen: String = "colorGreen" 30 | static let colorGreen1: String = "colorGreen1" 31 | static let colorGreen2: String = "colorGreen2" 32 | static let colorPurple: String = "colorPurple" 33 | static let colorPurple1: String = "colorPurple1" 34 | static let colorPurple2: String = "colorPurple2" 35 | static let colorRed: String = "colorRed" 36 | static let colorRed1: String = "colorRed1" 37 | static let colorRed2: String = "colorRed2" 38 | static let colorYellow: String = "colorYellow" 39 | static let colorYellow1: String = "colorYellow1" 40 | static let colorYellow2: String = "colorYellow2" 41 | 42 | // все дополнительные цвета в массиве кол-во 21 43 | static let allColors: [String] = 44 | [ 45 | colorBlue, colorBlue1, colorBlue2, colorRed, colorRed1, colorRed2, colorYellow, colorYellow1, colorYellow2, colorPurple, colorPurple1, colorPurple2, colorBrown, colorBrown1, colorBrown2, colorGray, colorGray1, colorGray2 46 | ] 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /iWallet/Model/CurrencyModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Currency: String, CaseIterable, Identifiable, Hashable { 11 | case usd = "USD" 12 | case eur = "EUR" 13 | case jpy = "JPY" 14 | case gbp = "GBP" 15 | case aud = "AUD" 16 | case cad = "CAD" 17 | case chf = "CHF" 18 | case cny = "CNY" 19 | case rub = "RUB" 20 | case inr = "INR" 21 | case brl = "BRL" 22 | case zar = "ZAR" 23 | case TRY = "TRY" 24 | case mxn = "MXN" 25 | case idr = "IDR" 26 | case hkd = "HKD" 27 | case sgd = "SGD" 28 | case twd = "TWD" 29 | case krw = "KRW" 30 | case thb = "THB" 31 | case myr = "MYR" 32 | case php = "PHP" 33 | case vnd = "VND" 34 | case kes = "KES" 35 | case egp = "EGP" 36 | case byn = "BYN" 37 | case uah = "UAH" 38 | case kzt = "KZT" 39 | case tmt = "TMT" 40 | case czk = "CZK" 41 | 42 | var id: String { 43 | return self.rawValue 44 | } 45 | 46 | var symbol: String { 47 | let currencySymbols: [Currency: String] = [ 48 | .usd: "$", 49 | .eur: "€", 50 | .jpy: "¥", 51 | .gbp: "£", 52 | .aud: "$", 53 | .cad: "$", 54 | .chf: "CHF", 55 | .cny: "¥", 56 | .rub: "₽", 57 | .inr: "₹", 58 | .brl: "R$", 59 | .zar: "R", 60 | .TRY: "₺", 61 | .mxn: "$", 62 | .idr: "Rp", 63 | .hkd: "HK$", 64 | .sgd: "S$", 65 | .twd: "NT$", 66 | .krw: "₩", 67 | .thb: "฿", 68 | .myr: "RM", 69 | .php: "₱", 70 | .vnd: "₫", 71 | .kes: "KSh", 72 | .egp: "£", 73 | .byn: "Br", 74 | .uah: "₴", 75 | .kzt: "₸", 76 | .tmt: "m", 77 | .czk: "Kč" 78 | ] 79 | return currencySymbols[self] ?? "" 80 | } 81 | 82 | static var sortedCases: [Currency] { 83 | return allCases.sorted { $0.rawValue < $1.rawValue } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /iWallet/View/Category/CategoryItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 26.05.2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CategoryItemView: View { 11 | 12 | @EnvironmentObject var appVM: AppViewModel 13 | 14 | let categoryColor: String 15 | let categoryIcon: String 16 | let categoryName: String 17 | let totalAmount: Float 18 | let currencySymbol: String 19 | 20 | var body: some View { 21 | VStack(alignment: .leading, spacing: 0) { 22 | HStack { 23 | HStack { 24 | Divider() 25 | .foregroundColor(Color(categoryColor)) 26 | .frame(width: 5, height: 50) 27 | .background(Color(categoryColor)) 28 | } 29 | 30 | Image(systemName: categoryIcon) 31 | .font(.system(size: 15)) 32 | .foregroundColor(.black) 33 | .frame(width: 30, height: 30) 34 | .background(Color(categoryColor)) 35 | .cornerRadius(7.5) 36 | .padding(0) 37 | 38 | Text(categoryName) 39 | .font(.headline) 40 | .fontWeight(.light) 41 | .foregroundColor(Color(Colors.mainText)) 42 | 43 | Spacer() 44 | 45 | Text("\(totalAmount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers)) \(currencySymbol)") 46 | .font(.headline).bold() 47 | .foregroundColor(Color(Colors.mainText)) 48 | 49 | Image(systemName: "chevron.forward") 50 | .foregroundColor(Color(Colors.mainText)) 51 | .opacity(0.5) 52 | 53 | } 54 | .padding() 55 | .frame(maxWidth: .infinity, maxHeight: 50) 56 | .background(Color(Colors.colorBalanceBG)) 57 | 58 | Divider() 59 | } 60 | } 61 | } 62 | 63 | struct CategoryItemView_Previews: PreviewProvider { 64 | static var previews: some View { 65 | CategoryItemView(categoryColor: Colors.colorBlue, categoryIcon: "plus", categoryName: "Food", totalAmount: 100, currencySymbol: "$") 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

✨ iWallet - Expenses and Income ✨

2 | 3 | ![iWallet](https://github.com/idevnva/iWallet/assets/127990298/5f509056-3207-4b3e-8995-4be1347d28c6) 4 | 5 |

6 | It is a personal finance management assessment tool that will help you keep track of your income and expenses, keep an eye on your budget, and achieve your financial goals. Install iWallet and enjoy complete control over your finances! 7 |

8 | 9 |

10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 |

19 | 20 | ## You can 21 | * Calculate the average expense per day, taking into account all financial transactions, for more accurate planning and control of your budget. 22 | 23 | * Data security and privacy, since all data is stored on your device and you have full control over access to it. 24 | 25 | ## Create categories 26 | * One of the main features of iWallet is the ability to create custom categories for expenses and income. 27 | 28 | * You can choose a unique icon and color for each category to help you better represent your financial activities. In addition, the application allows you to analyze data and identify areas where you can reduce costs. 29 | 30 | ## Recording transactions 31 | * iWallet is a reliable financial management assistant: track all income and expenses with high accuracy (including date, category and notes) and control your finances with convenience. 32 | 33 | * Be aware of your financial flows and make informed decisions about your money. 34 | 35 | ![iWalletTeam](https://github.com/iWalletTeam/iWallet/assets/127990298/41e45886-da1c-445c-b5d0-e5c3a2605a30) 36 | 37 | ## Our team 38 | * Our team is working on the development of iWallet — an open source project that allows users to manage their finances more efficiently. We believe that everyone should have access to a quality tool to keep track of their income and expenses. 39 | 40 | * We strive to create an application that is convenient and easy to use, but at the same time functional and powerful. We are constantly working to improve iWallet and add new features so that our users can get the most out of it. 41 | 42 | * Our team consists of beginner developers, designers and a huge amount of motivation. We work with open source to ensure that our project is transparent and accessible to everyone. We also welcome contributions from the community and encourage participation in iWallet development. 43 | 44 | ## iWalletTeam 45 | > ### " Our goal is to make iWallet the best financial accounting tool available to everyone. We believe that every person deserves financial stability and we do our best to help them achieve this goal. " 46 | 47 | ![iWallet](https://github.com/idevnva/iWallet/assets/127990298/c1397684-89f4-4d84-b936-4c32f6962dfc) 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /iWallet/View/Category/CategoryView.swift: -------------------------------------------------------------------------------- 1 | // AddCategoryView.swift 2 | 3 | import SwiftUI 4 | import RealmSwift 5 | 6 | struct CategoryView: View { 7 | @EnvironmentObject var appVM: AppViewModel 8 | @EnvironmentObject var categoryVM: CategoryViewModel 9 | @EnvironmentObject var transactionVM: TransactionViewModel 10 | @ObservedResults(Category.self) var categories 11 | @Environment(\.dismiss) var dismiss 12 | 13 | @State var selectedType: CategoryType = .expense 14 | 15 | var body: some View { 16 | NavigationStack { 17 | VStack { 18 | List { 19 | if categoryVM.filteredCategory(category: categories, type: selectedType).isEmpty { 20 | previewCardCategory() 21 | } else { 22 | ForEach(categoryVM.filteredCategory(category: categories, type: selectedType), id: \.self) { category in 23 | HStack { 24 | Image(systemName: category.icon) 25 | .font(.system(size: 15)) 26 | .foregroundColor(Color(.black)) 27 | .frame(width: 30, height: 30) 28 | .background(Color(category.color)) 29 | .cornerRadius(7.5) 30 | Text(category.name) 31 | .foregroundColor(Color("colorBalanceText")) 32 | } 33 | } 34 | .onDelete(perform: { indexSet in 35 | categoryVM.deleteCategories(category: categories, at: indexSet, type: selectedType, transaction: transactionVM) 36 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 37 | }) 38 | } 39 | } 40 | .scrollContentBackground(.hidden) 41 | .background(Color("colorBG")) 42 | } 43 | .toolbar { 44 | ToolbarItem(placement: .navigationBarLeading) { 45 | Button { 46 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 47 | dismiss() 48 | } label: { 49 | Text("Back") 50 | } 51 | } 52 | ToolbarItem(placement: .navigationBarTrailing) { 53 | NavigationLink(destination: AddCategoryView(selectedType: selectedType), label: { 54 | Text("New") 55 | }) 56 | } 57 | ToolbarItem(placement: .principal) { 58 | Picker("Type", selection: $selectedType) { 59 | ForEach(CategoryType.allCases, id: \.self) { type in 60 | Text(type.localizedName()) 61 | } 62 | } .pickerStyle(.segmented) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | struct CategoryView_Previews: PreviewProvider { 70 | static var previews: some View { 71 | CategoryView() 72 | .environmentObject(AppViewModel()) 73 | .environmentObject(TransactionViewModel()) 74 | .environmentObject(CategoryViewModel()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /iWallet/View/Templates/PreviewCardView.swift: -------------------------------------------------------------------------------- 1 | // PreviewCardView.swift 2 | 3 | import SwiftUI 4 | 5 | @ViewBuilder 6 | func previewHomeTransaction() -> some View { 7 | VStack { 8 | VStack(alignment: .center) { 9 | Spacer(minLength: 20) 10 | Image("icon") 11 | .resizable() 12 | .frame(width: 25, height: 25) 13 | Spacer() 14 | Text("iWallet") 15 | .foregroundColor(.gray).bold() 16 | .font(.title) 17 | Spacer(minLength: 20) 18 | 19 | Text("The list of transactions is currently empty,") 20 | .foregroundColor(.gray) 21 | .font(.system(size: 12)) 22 | Text("please add transaction.") 23 | .foregroundColor(.gray) 24 | .font(.system(size: 12)) 25 | Spacer(minLength: 20) 26 | } 27 | .frame(maxWidth: .infinity, maxHeight: 300) 28 | .background(Color(Colors.colorBalanceBG)) 29 | } 30 | } 31 | 32 | @ViewBuilder 33 | func previewCardTransaction() -> some View { 34 | VStack { 35 | VStack(alignment: .center) { 36 | Spacer(minLength: 20) 37 | Image("icon") 38 | .resizable() 39 | .frame(width: 25, height: 25) 40 | Spacer() 41 | Text("iWallet") 42 | .foregroundColor(.gray).bold() 43 | .font(.title) 44 | Spacer(minLength: 20) 45 | 46 | Text("The list of transactions is currently empty,") 47 | .foregroundColor(.gray) 48 | .font(.system(size: 12)) 49 | Text("please add transaction.") 50 | .foregroundColor(.gray) 51 | .font(.system(size: 12)) 52 | Spacer(minLength: 20) 53 | } 54 | .frame(maxWidth: .infinity, maxHeight: 300) 55 | } 56 | } 57 | 58 | @ViewBuilder 59 | func previewCardCategory() -> some View { 60 | VStack { 61 | VStack(alignment: .center) { 62 | Spacer(minLength: 20) 63 | Image("icon") 64 | .resizable() 65 | .frame(width: 25, height: 25) 66 | Spacer() 67 | Text("iWallet") 68 | .foregroundColor(.gray).bold() 69 | .font(.title) 70 | Spacer(minLength: 20) 71 | 72 | Text("The list of categories is currently empty,") 73 | .foregroundColor(.gray) 74 | .font(.system(size: 12)) 75 | Text("please add category.") 76 | .foregroundColor(.gray) 77 | .font(.system(size: 12)) 78 | Spacer(minLength: 20) 79 | } 80 | .frame(maxWidth: .infinity, maxHeight: 300) 81 | } 82 | } 83 | 84 | func previewNoCategory() -> some View { 85 | VStack { 86 | VStack(alignment: .center) { 87 | Spacer(minLength: 20) 88 | Image("icon") 89 | .resizable() 90 | .frame(width: 25, height: 25) 91 | Spacer() 92 | Text("iWallet") 93 | .foregroundColor(.gray).bold() 94 | .font(.title) 95 | Spacer(minLength: 20) 96 | 97 | Text("There are currently no categories.") 98 | .foregroundColor(.gray) 99 | .font(.system(size: 12)) 100 | Text("Please add.") 101 | .foregroundColor(.gray) 102 | .font(.system(size: 12)) 103 | Spacer(minLength: 20) 104 | } 105 | .frame(maxWidth: .infinity, maxHeight: 300) 106 | .background(Color(Colors.colorBalanceBG)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /iWallet/Localizable/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | iWallet 4 | 5 | Created by Владислав Новошинский on 13.04.2023. 6 | 7 | */ 8 | 9 | // HomeView 10 | "Balance" = "Balance"; 11 | "Expense average" = "Expense average"; 12 | "Income" = "Income"; 13 | "Expense" = "Expense"; 14 | "Settings" = "Settings"; 15 | 16 | // AddTransaction 17 | "Enter amount:" = "Enter amount:"; 18 | "Note" = "Note"; 19 | "Enter note:" = "Enter note:"; 20 | "Category type" = "Category type"; 21 | "Category" = "Category"; 22 | "Purpose:" = "Purpose:"; 23 | "Date" = "Date"; 24 | "Enter date:" = "Enter date:"; 25 | "Cancel" = "Cancel"; 26 | "Add" = "Add"; 27 | "Please enter amount" = "Please enter amount"; 28 | "Please select a category" = "Please select a category"; 29 | "Okay" = "Okay"; 30 | "Select a category" = "Select a category"; 31 | "Category:" = "Category:"; 32 | "Addendum" = "Addendum"; 33 | 34 | // TransactionView 35 | "Transactions" = "Transactions"; 36 | "Back" = "Back"; 37 | 38 | // AddCategoryView 39 | "Back" = "Back"; 40 | "New" = "New"; 41 | 42 | // AddCategory 43 | "Select type:" = "Select type:"; 44 | "Name" = "Name"; 45 | "Enter Name" = "Enter Name"; 46 | "Choose an icon:" = "Choose an icon:"; 47 | "Choose color:" = "Choose color:"; 48 | "Create a category" = "Create a category"; 49 | "Back" = "Back"; 50 | "Add" = "Add"; 51 | 52 | // SettingView 53 | "Back" = "Back"; 54 | "Settings" = "Settings"; 55 | "Transactions" = "Transactions"; 56 | "Categories" = "Categories"; 57 | "Data" = "Data"; 58 | "Web-site" = "Web-site"; 59 | "Community" = "Community"; 60 | "Write to the developer" = "Write to the developer"; 61 | "Feedback" = "Feedback"; 62 | "Application" = "Application"; 63 | "Currency" = "Currency"; 64 | "Vibration" = "Vibration"; 65 | "https://idevnva.com/" = "https://idevnva.com/iwallet"; 66 | "https://t.me/idevnva" = "https://t.me/idevnva"; 67 | "App version: 1.1.5" = "App version: 1.1.5"; 68 | "Rounding numbers" = "Rounding numbers"; 69 | 70 | //PreviewCard 71 | "Welcome" = "Welcome!"; 72 | "The list of transactions is currently empty," = "The list of transactions is currently empty,"; 73 | "please add transaction." = "please add transaction."; 74 | "Welcome" = "Welcome"; 75 | "The list of categories is currently empty," = "The list of categories is currently empty,"; 76 | "please add category." = "please add category."; 77 | "There are currently no categories." = "There are currently no categories."; 78 | "Please add." = "Please add."; 79 | 80 | // DefaultCategories 81 | // Все категории для расхода 82 | "Car" = "Car"; 83 | "Bank" = "Bank"; 84 | "Business services" = "Business services"; 85 | "Charity" = "Charity"; 86 | "State" = "State"; 87 | "Children" = "Children"; 88 | "House" = "House"; 89 | "Pets" = "Pets"; 90 | "Eating out" = "Eating out"; 91 | "Health" = "Health"; 92 | "Beauty" = "Beauty"; 93 | "Mobile connection" = "Mobile connection"; 94 | "Education" = "Education"; 95 | "Clothing and footwear" = "Clothing and footwear"; 96 | "Present" = "Present"; 97 | "Food" = "Food"; 98 | "Trips" = "Trips"; 99 | "Entertainment" = "Entertainment"; 100 | "Technique" = "Technique"; 101 | "Transport" = "Transport"; 102 | 103 | // Все категории для дохода 104 | "Rent" = "Rent"; 105 | "Exchange" = "Exchange"; 106 | "Dividends" = "Dividends"; 107 | "Wage" = "Wage"; 108 | "Present" = "Present"; 109 | "Part time job" = "Part time job"; 110 | "Interest on accounts" = "Interest on accounts"; 111 | 112 | // WelcomeView 113 | "iWallet" = "iWallet"; 114 | "Currency" = "Currency"; 115 | "Initial application setup" = "Initial application setup:"; 116 | "Basic categories" = "Basic categories"; 117 | "Note: Enabling this feature will create base categories for expenses and income." = "Note: Enabling this feature will create base categories for expenses and income."; 118 | "Continue" = "Continue"; 119 | 120 | // PickerCategoryView 121 | "Categories" = "Categories"; 122 | "Create category" = "Create category"; 123 | 124 | // EditTransactionView 125 | "Editing" = "Editing"; 126 | "Edit" = "Edit"; 127 | -------------------------------------------------------------------------------- /iWallet/Localizable/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | iWallet 4 | 5 | Created by Владислав Новошинский on 13.04.2023. 6 | 7 | */ 8 | 9 | // HomeView 10 | "Balance" = "Баланс"; 11 | "Expense average" = "Средний расход"; 12 | "Income" = "Доход"; 13 | "Expense" = "Расход"; 14 | "Settings" = "Настройки"; 15 | 16 | // AddTransaction 17 | "Enter amount:" = "Введите сумму:"; 18 | "Note" = "Заметка"; 19 | "Enter note:" = "Введите заметку:"; 20 | "Category type" = "Тип категории"; 21 | "Category" = "Категория"; 22 | "Purpose:" = "Назначание:"; 23 | "Date" = "Дата"; 24 | "Enter date:" = "Введите дату:"; 25 | "Cancel" = "Отменить"; 26 | "Add" = "Добавить"; 27 | "Please enter amount" = "Пожалуйста введите сумму"; 28 | "Please select a category" = "Пожалуйста, выберите категорию"; 29 | "Okay" = "Хорошо"; 30 | "Select a category" = "Выберите категорию"; 31 | "Category:" = "Категория:"; 32 | "Addendum" = "Добавление"; 33 | 34 | // TransactionView 35 | "Transactions" = "Транзакции"; 36 | "Back" = "Назад"; 37 | 38 | // AddCategoryView 39 | "Back" = "Назад"; 40 | "New" = "Новая"; 41 | 42 | // AddCategory 43 | "Select type:" = "Выберите тип:"; 44 | "Name" = "Название"; 45 | "Enter Name" = "Введите название"; 46 | "Choose an icon:" = "Выберите иконку:"; 47 | "Choose color:" = "Выберите цвет:"; 48 | "Create a category" = "Создание категории"; 49 | "Back" = "Назад"; 50 | "Add" = "Добавить"; 51 | 52 | // SettingView 53 | "Back" = "Назад"; 54 | "Settings" = "Настройки"; 55 | "Transactions" = "Транзакции"; 56 | "Categories" = "Категории"; 57 | "Data" = "Данные"; 58 | "Web-site" = "Веб-сайт"; 59 | "Community" = "Сообщество"; 60 | "Write to the developer" = "Написать разработчику"; 61 | "Feedback" = "Обратная звязь"; 62 | "Application" = "Приложение"; 63 | "Currency" = "Валюта"; 64 | "Vibration" = "Вибрация"; 65 | "https://idevnva.com/" = "https://idevnva.com/iwallet"; 66 | "https://t.me/idevnva" = "https://t.me/idevnva"; 67 | "App version: 1.1.5" = "Версия приложения: 1.1.5"; 68 | "Rounding numbers" = "Округление чисел"; 69 | 70 | //PreviewCard 71 | "Welcome" = "Добро пожаловать!"; 72 | "The list of transactions is currently empty," = "Список транзакций в настоящее время пуст,"; 73 | "please add transaction." = "пожалуйста добавьте транзакцию."; 74 | "Welcome" = "Добро пожаловать!"; 75 | "The list of categories is currently empty," = "Список категорий в настоящее время пуст,"; 76 | "please add category." = "пожалуйста добавьте категорию."; 77 | "There are currently no categories." = "В настоящее время нет категорий."; 78 | "Please add." = "Пожалуйста, добавьте."; 79 | 80 | // DefaultCategories 81 | // Все категории для расхода 82 | "Car" = "Автомобиль"; 83 | "Bank" = "Банк"; 84 | "Business services" = "Бизнес услуги"; 85 | "Charity" = "Благотворительность"; 86 | "State" = "Государство"; 87 | "Children" = "Дети"; 88 | "House" = "Дом"; 89 | "Pets" = "Домашние животные"; 90 | "Eating out" = "Еда вне дома"; 91 | "Health" = "Здоровье"; 92 | "Beauty" = "Красота"; 93 | "Mobile connection" = "Мобильная связь"; 94 | "Education" = "Образование"; 95 | "Clothing and footwear" = "Одежда и обувь"; 96 | "Present" = "Подарки"; 97 | "Food" = "Продукты питания"; 98 | "Trips" = "Путешествие"; 99 | "Entertainment" = "Развлечение"; 100 | "Technique" = "Техника"; 101 | "Transport" = "Транспорт"; 102 | 103 | // Все категории для дохода 104 | "Rent" = "Аренда"; 105 | "Exchange" = "Биржа"; 106 | "Dividends" = "Дивиденды"; 107 | "Wage" = "Заработная плата"; 108 | "Present" = "Подарки"; 109 | "Part time job" = "Подработка"; 110 | "Interest on accounts" = "Проценты по счетам"; 111 | 112 | // WelcomeView 113 | "iWallet" = "iWallet"; 114 | "Currency" = "Валюта"; 115 | "Initial application setup" = "Первоначальная настройка приложения:"; 116 | "Basic categories" = "Базовые категории"; 117 | "Note: Enabling this feature will create base categories for expenses and income." = "Примечание: При включении этой функции будут созданы базовые категории расходов и доходов."; 118 | "Continue" = "Продолжить"; 119 | 120 | // PickerCategoryView 121 | "Categories" = "Категории"; 122 | "Create category" = "Создать категорию"; 123 | 124 | // EditTransactionView 125 | "Editing" = "Редактирование"; 126 | "Edit" = "Изменить"; 127 | -------------------------------------------------------------------------------- /iWallet/View/Category/PickerCategoryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerCategoryView.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 03.06.2023. 6 | // 7 | 8 | import SwiftUI 9 | import RealmSwift 10 | 11 | struct PickerCategoryView: View { 12 | @ObservedResults(Category.self) var categories 13 | @Environment(\.dismiss) var dismiss 14 | 15 | @Binding var selected: Category 16 | @State var selectedType: CategoryType = .expense 17 | 18 | var body: some View { 19 | NavigationStack { 20 | ScrollView(.vertical, showsIndicators: false) { 21 | if categories.filter({ $0.type == selectedType }).isEmpty { 22 | VStack { 23 | VStack { 24 | previewNoCategory() 25 | } 26 | .cornerRadius(10) 27 | .padding(.horizontal) 28 | 29 | HStack { 30 | NavigationLink(destination: AddCategoryView(selectedType: selectedType), label: { 31 | HStack { 32 | Text("Create category") 33 | } 34 | }) 35 | } 36 | .padding() 37 | .frame(maxWidth: .infinity, maxHeight: 50) 38 | .background(Color(Colors.colorBalanceBG)) 39 | .cornerRadius(10) 40 | .padding(.horizontal) 41 | } 42 | } else { 43 | VStack(alignment: .leading, spacing: 0) { 44 | ForEach(categories.filter { $0.type == selectedType }, id: \.name) { category in 45 | Button { 46 | selected = category 47 | dismiss() 48 | } label: { 49 | HStack { 50 | HStack { 51 | Divider() 52 | .foregroundColor(Color(category.color)) 53 | .frame(width: 5, height: 50) 54 | .background(Color(category.color)) 55 | } 56 | Image(systemName: category.icon) 57 | .font(.system(size: 15)) 58 | .foregroundColor(.black) 59 | .frame(width: 30, height: 30) 60 | .background(Color(category.color)) 61 | .cornerRadius(7.5) 62 | .padding(0) 63 | 64 | Text(category.name) 65 | .font(.headline) 66 | .fontWeight(.light) 67 | .foregroundColor(Color(Colors.mainText)) 68 | 69 | Spacer() 70 | } 71 | } 72 | .padding() 73 | .frame(maxWidth: .infinity, maxHeight: 50) 74 | .background(Color(Colors.colorBalanceBG)) 75 | 76 | Divider() 77 | } 78 | } 79 | .cornerRadius(10) 80 | .padding() 81 | } 82 | } 83 | .background(Color(Colors.mainBG)) 84 | .navigationTitle("Categories") 85 | .navigationBarTitleDisplayMode(.inline) 86 | } 87 | } 88 | } 89 | 90 | struct PickerCategoryView_Previews: PreviewProvider { 91 | static var previews: some View { 92 | PickerCategoryView(selected: .constant(Category(value: ["name": NSLocalizedString("Interest on accounts", comment: "Interest on accounts"), "icon": "percent", "color": "colorYellow", "type": CategoryType.income] as [String : Any]))) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /iWallet/ViewModel/CategoryViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryViewModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class CategoryViewModel: ObservableObject { 12 | @Published var categories: [Category] = [] 13 | 14 | init() { 15 | loadData() 16 | } 17 | 18 | // Метод для загрузки категорий 19 | func loadData() { 20 | guard let realm = try? Realm() else { 21 | print("Ошибка: loadData") 22 | return 23 | } 24 | let categoriesResult = realm.objects(Category.self) 25 | categories = Array(categoriesResult) 26 | } 27 | 28 | // Добавления категорий по умолчанию 29 | func createDefaultCategories() { 30 | guard let realm = try? Realm() else { 31 | print("Ошибка: Не удалось создать категории по умолчанию Realm") 32 | return 33 | } 34 | 35 | let defaultCategory = defaultCategoriesModel 36 | 37 | try! realm.write { 38 | for category in defaultCategory { 39 | realm.add(category) 40 | } 41 | } 42 | } 43 | 44 | // Метод сохранения категории 45 | func saveCategory(name: String, icon: String, color: String, type: CategoryType) { 46 | guard let realm = try? Realm() else { 47 | print("Ошибка: Не удалось создать экземпляр Realm") 48 | return 49 | } 50 | let newCategory = Category() 51 | newCategory.name = name 52 | newCategory.icon = icon 53 | newCategory.color = color 54 | newCategory.type = type 55 | do { 56 | try realm.write { 57 | realm.add(newCategory) 58 | } 59 | // отладочное сообщение 60 | return print("Категория сохранена: \(newCategory)") 61 | } catch { 62 | // отладочное сообщение 63 | return print("Ошибка сохранения категории: \(error)") 64 | } 65 | } 66 | 67 | // Метод для удаления категории 68 | func deleteCategory(id: ObjectId) { 69 | do { 70 | let realm = try Realm() 71 | if let category = realm.object(ofType: Category.self, forPrimaryKey: id) { 72 | try realm.write { 73 | // Удаление всех транзакций, связанных с категорией 74 | for transaction in category.transactions { 75 | realm.delete(transaction) 76 | } 77 | 78 | // Удаление категории 79 | realm.delete(category) 80 | } 81 | loadData() 82 | } 83 | } catch { 84 | print("Error deleting category: \(error)") 85 | } 86 | } 87 | 88 | // MARK: HomeView 89 | // Функция фильтрует категории из массива categories, сохраняя только те, сумма транзакций которых для заданного типа type (доход или расход) больше 0 90 | func filteredCategories(categories: [Category], type: CategoryType) -> [Category] { 91 | var result: [Category] = [] 92 | for category in categories { 93 | if category.categoryAmount(type: type) > 0 { 94 | result.append(category) 95 | } 96 | } 97 | return result 98 | } 99 | 100 | // Функция которая фильтрует список категорий, чтобы найти только те, которые имеют транзакции определенного типа. 101 | func categoriesWithTransaction(categories: Results, type: CategoryType) -> [Category] { 102 | var result: [Category] = [] 103 | for category in categories { 104 | if category.hasTransactions(type: type) { 105 | result.append(category) 106 | } 107 | } 108 | return result 109 | } 110 | 111 | // MARK: CategoryView 112 | func filteredCategory(category: Results, type: CategoryType) -> [Category] { 113 | return category.filter { $0.type == type 114 | } 115 | } 116 | 117 | func deleteCategories(category: Results, at offsets: IndexSet, type: CategoryType, transaction: TransactionViewModel) { 118 | let filtered = filteredCategory(category: category, type: type) 119 | offsets.forEach { index in 120 | deleteCategory(id: filtered[index].id) 121 | loadData() 122 | transaction.loadData() 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /iWallet/Model/DefaultCategoriesModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultCategoriesModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 24.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | let defaultCategoriesModel = { 12 | [ 13 | // Все категории для расхода 14 | Category(value: ["name": NSLocalizedString("Car", comment: "Car"), "icon": "car", "color": "colorBlue", "type": CategoryType.expense] as [String : Any]), 15 | Category(value: ["name": NSLocalizedString("Bank", comment: "Bank"), "icon": "creditcard", "color": "colorBlue1", "type": CategoryType.expense] as [String : Any]), 16 | Category(value: ["name": NSLocalizedString("Business services", comment: "Business services"), "icon": "person.2", "color": "colorBlue2", "type": CategoryType.expense] as [String : Any]), 17 | Category(value: ["name": NSLocalizedString("Charity", comment: "Charity"), "icon": "figure.roll", "color": "colorGreen", "type": CategoryType.expense] as [String : Any]), 18 | Category(value: ["name": NSLocalizedString("State", comment: "State"), "icon": "network.badge.shield.half.filled", "color": "colorGreen1", "type": CategoryType.expense] as [String : Any]), 19 | Category(value: ["name": NSLocalizedString("Children", comment: "Children"), "icon": "figure.2.and.child.holdinghands", "color": "colorGreen2", "type": CategoryType.expense] as [String : Any]), 20 | Category(value: ["name": NSLocalizedString("House", comment: "House"), "icon": "house", "color": "colorYellow", "type": CategoryType.expense] as [String : Any]), 21 | Category(value: ["name": NSLocalizedString("Pets", comment: "Pets"), "icon": "fish", "color": "colorYellow1", "type": CategoryType.expense] as [String : Any]), 22 | Category(value: ["name": NSLocalizedString("Eating out", comment: "Eating out"), "icon": "popcorn", "color": "colorYellow2", "type": CategoryType.expense] as [String : Any]), 23 | Category(value: ["name": NSLocalizedString("Health", comment: "Health"), "icon": "heart", "color": "colorRed", "type": CategoryType.expense] as [String : Any]), 24 | Category(value: ["name": NSLocalizedString("Beauty", comment: "Beauty"), "icon": "fleuron", "color": "colorRed1", "type": CategoryType.expense] as [String : Any]), 25 | Category(value: ["name": NSLocalizedString("Mobile connection", comment: "Mobile connection"), "icon": "wifi", "color": "colorRed2", "type": CategoryType.expense] as [String : Any]), 26 | Category(value: ["name": NSLocalizedString("Education", comment: "Education"), "icon": "book", "color": "colorBrown", "type": CategoryType.expense] as [String : Any]), 27 | Category(value: ["name": NSLocalizedString("Clothing and footwear", comment: "Clothing and footwear"), "icon": "backpack", "color": "colorBrown1", "type": CategoryType.expense] as [String : Any]), 28 | Category(value: ["name": NSLocalizedString("Present", comment: "Present"), "icon": "gift", "color": "colorBrown2", "type": CategoryType.expense] as [String : Any]), 29 | Category(value: ["name": NSLocalizedString("Food", comment: "Food"), "icon": "cart", "color": "colorPurple", "type": CategoryType.expense] as [String : Any]), 30 | Category(value: ["name": NSLocalizedString("Trips", comment: "Trips"), "icon": "airplane", "color": "colorPurple1", "type": CategoryType.expense] as [String : Any]), 31 | Category(value: ["name": NSLocalizedString("Entertainment", comment: "Entertainment"), "icon": "music.mic", "color": "colorPurple2", "type": CategoryType.expense] as [String : Any]), 32 | Category(value: ["name": NSLocalizedString("Technique", comment: "Technique"), "icon": "display", "color": "colorGray", "type": CategoryType.expense] as [String : Any]), 33 | Category(value: ["name": NSLocalizedString("Transport", comment: "Transport"), "icon": "bus.fill", "color": "colorGray1", "type": CategoryType.expense] as [String : Any]), 34 | 35 | // Все категории для дохода 36 | Category(value: ["name": NSLocalizedString("Rent", comment: "Rent"), "icon": "key", "color": "colorBlue", "type": CategoryType.income] as [String : Any]), 37 | Category(value: ["name": NSLocalizedString("Exchange", comment: "Exchange"), "icon": "arrow.triangle.2.circlepath", "color": "colorBlue1", "type": CategoryType.income] as [String : Any]), 38 | Category(value: ["name": NSLocalizedString("Dividends", comment: "Dividends"), "icon": "chart.xyaxis.line", "color": "colorBlue2", "type": CategoryType.income] as [String : Any]), 39 | Category(value: ["name": NSLocalizedString("Wage", comment: "Wage"), "icon": "dollarsign", "color": "colorGreen", "type": CategoryType.income] as [String : Any]), 40 | Category(value: ["name": NSLocalizedString("Present", comment: "Present"), "icon": "shippingbox.circle", "color": "colorGreen1", "type": CategoryType.income] as [String : Any]), 41 | Category(value: ["name": NSLocalizedString("Part time job", comment: "Part time job"), "icon": "person.fill.checkmark", "color": "colorGreen2", "type": CategoryType.income] as [String : Any]), 42 | Category(value: ["name": NSLocalizedString("Interest on accounts", comment: "Interest on accounts"), "icon": "percent", "color": "colorYellow", "type": CategoryType.income] as [String : Any]) 43 | ] 44 | }() 45 | -------------------------------------------------------------------------------- /iWallet/View/Welcome/WelcomeView.swift: -------------------------------------------------------------------------------- 1 | // WelcomeView.swift 2 | 3 | import SwiftUI 4 | import RealmSwift 5 | 6 | struct WelcomeView: View { 7 | @EnvironmentObject var appVM: AppViewModel 8 | @EnvironmentObject var categoryVM: CategoryViewModel 9 | 10 | @State private var selectedCurrency: Currency = .usd 11 | @State private var createCategories: Bool = true 12 | 13 | var body: some View { 14 | NavigationStack { 15 | ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) { 16 | ScrollView(.vertical, showsIndicators: false) { 17 | VStack(alignment: .center) { 18 | VStack { 19 | Spacer(minLength: 100) 20 | Image("icon") 21 | .resizable() 22 | .frame(width: 100, height: 100) 23 | Spacer(minLength: 20) 24 | Text("iWallet") 25 | .foregroundColor(.gray).bold() 26 | .font(.largeTitle) 27 | Spacer(minLength: 50) 28 | } 29 | Section { 30 | HStack { 31 | Text(selectedCurrency.symbol) 32 | .foregroundColor(Color("colorBlack")) 33 | .frame(width: 30, height: 30) 34 | .background(Color("colorBrown1")) 35 | .cornerRadius(7.5) 36 | Text("Currency") 37 | Spacer() 38 | Picker("Currency", selection: $selectedCurrency) { 39 | ForEach(Currency.sortedCases, id: \.self) { currency in 40 | Text(currency.rawValue) 41 | .tag(currency) 42 | } 43 | } 44 | } 45 | .padding() 46 | .frame(maxWidth: .infinity, maxHeight: 50) 47 | .background(Color(Colors.colorBalanceBG)) 48 | .cornerRadius(12.5) 49 | } header: { 50 | HStack { 51 | Text("Initial application setup").font(.caption).fontWeight(.light).padding(.leading).textCase(.uppercase) 52 | Spacer() 53 | } 54 | } 55 | 56 | HStack { 57 | Text(appVM.roundingNumbers ? "0" : "0.0") 58 | .foregroundColor(Color("colorBlack")) 59 | .frame(width: 30, height: 30) 60 | .background(Color(Colors.colorGreen)) 61 | .cornerRadius(7.5) 62 | Toggle("Rounding numbers", isOn: $appVM.roundingNumbers) 63 | } 64 | .padding() 65 | .frame(maxWidth: .infinity, maxHeight: 50) 66 | .background(Color(Colors.colorBalanceBG)) 67 | .cornerRadius(12.5) 68 | 69 | VStack(alignment: .leading) { 70 | HStack { 71 | Toggle("Basic categories", isOn: $createCategories) 72 | .toggleStyle(SwitchToggleStyle(tint: Color.green)) 73 | } 74 | HStack { 75 | Image(systemName: "exclamationmark.shield") 76 | Text("Note: Enabling this feature will create base categories for expenses and income.") 77 | } 78 | .font(.subheadline) 79 | .fontWeight(.ultraLight) 80 | } 81 | .padding() 82 | .frame(maxWidth: .infinity, maxHeight: 200) 83 | .background(Color(Colors.colorBalanceBG)) 84 | .cornerRadius(12.5) 85 | } 86 | } 87 | VStack { 88 | Button { 89 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 90 | appVM.hasRunBefore = true 91 | appVM.currencySymbol = selectedCurrency.symbol 92 | if createCategories { 93 | categoryVM.createDefaultCategories() 94 | } 95 | } label: { 96 | HStack(alignment: .center) { 97 | Text("Continue") 98 | .frame(maxWidth: .infinity, maxHeight: 20) 99 | .padding() 100 | .background(Color.accentColor) 101 | .foregroundColor(.white).bold() 102 | .cornerRadius(15) 103 | } 104 | } 105 | } 106 | } 107 | .padding(15) 108 | .background(Color(Colors.mainBG)) 109 | } 110 | .onChange(of: selectedCurrency) { newCurrency in 111 | // Сохраняем символ валюты при изменении выбора 112 | appVM.currencySymbol = newCurrency.symbol 113 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 114 | } 115 | } 116 | } 117 | 118 | struct WelcomeView_Previews: PreviewProvider { 119 | static var previews: some View { 120 | WelcomeView() 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /iWallet/View/Transaction/TransactionView.swift: -------------------------------------------------------------------------------- 1 | // TransactionView.swift 2 | 3 | import SwiftUI 4 | import RealmSwift 5 | 6 | struct TransactionView: View { 7 | @EnvironmentObject var appVM: AppViewModel 8 | @EnvironmentObject var transactionVM: TransactionViewModel 9 | @Environment(\.dismiss) var dismiss 10 | 11 | @ObservedResults(TransactionItem.self) var transactions 12 | @ObservedResults(Category.self) var categories 13 | 14 | var body: some View { 15 | NavigationStack { 16 | List { 17 | if transactions.isEmpty { 18 | previewCardTransaction() 19 | } else { 20 | let groupedTransactions = transactionVM.transactionsByDate(Array(transactions)) 21 | ForEach(groupedTransactions.keys.sorted(by: { $0 > $1 }), id: \.self) { date in 22 | Section(header: Text(date, style: .date).bold()) { 23 | let sortedTransactions = transactionVM.sortTransactionsByDate(transactions: groupedTransactions[date]!) 24 | 25 | ForEach(sortedTransactions, id: \.self) { transaction in 26 | if let category = transactionVM.filterCategories(categories: Array(categories), transaction: transaction) { 27 | NavigationLink(destination: EditTransactionView(selectedTransaction: transaction)) { 28 | transactionRow(transaction: transaction, category: category) 29 | } 30 | } 31 | } 32 | .onDelete(perform: { indexSet in 33 | transactionVM.deleteTransaction(at: indexSet, from: sortedTransactions) 34 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 35 | }) 36 | } 37 | } 38 | } 39 | } 40 | .scrollContentBackground(.hidden) 41 | .background(Color("colorBG")) 42 | .navigationBarTitle("Transactions", displayMode: .inline) 43 | .toolbar { 44 | ToolbarItem(placement: .navigationBarLeading) { 45 | Button { 46 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 47 | dismiss() 48 | } label: { 49 | Text("Back") 50 | } 51 | } 52 | 53 | ToolbarItem(placement: .navigationBarTrailing) { 54 | if transactions.isEmpty { 55 | EditButton().disabled(true) 56 | } else { 57 | EditButton() 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | // Метод для оптимизации отображения списка 65 | @ViewBuilder 66 | private func transactionRow(transaction: TransactionItem, category: Category) -> some View { 67 | VStack(alignment: .leading, spacing: 0) { 68 | HStack { 69 | HStack { 70 | Divider() 71 | .foregroundColor(Color(category.color)) 72 | .frame(width: 5, height: 72) 73 | .background(Color(category.color)) 74 | } .padding(.trailing, 3) 75 | 76 | VStack(alignment: .leading) { 77 | HStack { 78 | if transaction.type == CategoryType.expense { 79 | Text("-\(transaction.amount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers)) \(appVM.currencySymbol)") 80 | .font(.title3).bold() 81 | } else { 82 | Text("\(transaction.amount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers)) \(appVM.currencySymbol)") 83 | .font(.title3).bold() 84 | } 85 | Spacer() 86 | HStack { 87 | Text(category.name) 88 | .foregroundColor(Color("colorBalanceText")).textCase(.uppercase) 89 | .font(.caption) 90 | .multilineTextAlignment(.trailing) 91 | .dynamicTypeSize(.small) 92 | .padding(0) 93 | Image(systemName: category.icon) 94 | .font(.caption).dynamicTypeSize(.small) 95 | .foregroundColor(.black) 96 | .frame(width: 20, height: 20) 97 | .background(Color(category.color)) 98 | .cornerRadius(5) 99 | .padding(0) 100 | } .padding(0) 101 | } 102 | HStack { 103 | Text(transaction.note) 104 | .foregroundColor(Color(.gray)).textCase(.uppercase) 105 | .font(.subheadline).dynamicTypeSize(.small) 106 | Spacer() 107 | Text(category.type.localizedName()) 108 | .foregroundColor(Color(.gray)).textCase(.uppercase) 109 | .font(.subheadline).dynamicTypeSize(.small) 110 | } 111 | } .padding(.leading, 10) 112 | } 113 | } 114 | .padding(.vertical, 5) 115 | .frame(height: 50) 116 | } 117 | } 118 | 119 | struct TransactionView_Previews: PreviewProvider { 120 | static var previews: some View { 121 | TransactionView() 122 | .environmentObject(AppViewModel()) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /iWallet/View/Transaction/TransactionCategoryView.swift: -------------------------------------------------------------------------------- 1 | // TransactionCategoryView.swift 2 | 3 | import SwiftUI 4 | import RealmSwift 5 | 6 | struct TransactionCategoryView: View { 7 | @EnvironmentObject var appVM: AppViewModel 8 | @EnvironmentObject var transactionVM: TransactionViewModel 9 | @ObservedResults(TransactionItem.self) var transactions 10 | 11 | @State var selectedCategory: Category 12 | 13 | var filteredTransactions: [TransactionItem] { 14 | transactionVM.filterTransaction(category: selectedCategory, transactions: Array(transactions)) 15 | } 16 | 17 | var body: some View { 18 | List { 19 | if transactions.isEmpty { 20 | previewCardTransaction() 21 | } else { 22 | let groupedTransactions = transactionVM.transactionsByDate(Array(filteredTransactions)) 23 | ForEach(groupedTransactions.keys.sorted(by: { $0 > $1 }), id: \.self) { date in 24 | Section(header: Text(date, style: .date).bold()) { 25 | let sortedTransactions = transactionVM.sortTransactionsByDate(transactions: groupedTransactions[date]!) 26 | 27 | ForEach(sortedTransactions, id: \.self) { transaction in 28 | NavigationLink(destination: EditTransactionView(selectedTransaction: transaction)) { 29 | VStack(alignment: .leading, spacing: 0) { 30 | HStack { 31 | HStack { 32 | Divider() 33 | .foregroundColor(Color(selectedCategory.color)) 34 | .frame(width: 5, height: 72) 35 | .background(Color(selectedCategory.color)) 36 | } .padding(.trailing, 3) 37 | 38 | VStack(alignment: .leading) { 39 | HStack { 40 | if transaction.type == CategoryType.expense { 41 | Text("-\(transaction.amount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers)) \(appVM.currencySymbol)") 42 | .font(.title3).bold() 43 | } else { 44 | Text("\(transaction.amount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers)) \(appVM.currencySymbol)") 45 | .font(.title3).bold() 46 | } 47 | Spacer() 48 | HStack { 49 | Text(selectedCategory.name) 50 | .foregroundColor(Color("colorBalanceText")).textCase(.uppercase) 51 | .font(.caption) 52 | .multilineTextAlignment(.trailing) 53 | .dynamicTypeSize(.small) 54 | .padding(0) 55 | Image(systemName: selectedCategory.icon) 56 | .font(.caption).dynamicTypeSize(.small) 57 | .foregroundColor(.black) 58 | .frame(width: 20, height: 20) 59 | .background(Color(selectedCategory.color)) 60 | .cornerRadius(5) 61 | .padding(0) 62 | } .padding(0) 63 | } 64 | HStack { 65 | Text(transaction.note) 66 | .foregroundColor(Color(.gray)).textCase(.uppercase) 67 | .font(.subheadline).dynamicTypeSize(.small) 68 | Spacer() 69 | Text(selectedCategory.type.localizedName()) 70 | .foregroundColor(Color(.gray)).textCase(.uppercase) 71 | .font(.subheadline).dynamicTypeSize(.small) 72 | } 73 | } .padding(.leading, 10) 74 | } 75 | } 76 | .padding(.vertical, 5) 77 | .frame(height: 50) 78 | } 79 | } 80 | .onDelete(perform: { indexSet in 81 | transactionVM.deleteTransaction(at: indexSet, from: sortedTransactions) 82 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 83 | }) 84 | } 85 | } 86 | } 87 | } 88 | .navigationTitle(selectedCategory.name) 89 | .background(Color("colorBG")) 90 | .scrollContentBackground(.hidden) 91 | .toolbar { 92 | ToolbarItem(placement: .navigationBarTrailing) { 93 | if transactions.isEmpty { 94 | EditButton().disabled(true) 95 | } else { 96 | EditButton() 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | struct TransactionCategoryView_Previews: PreviewProvider { 104 | static var previews: some View { 105 | TransactionCategoryView(selectedCategory: Category()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /iWallet/View/Category/AddCategoryView.swift: -------------------------------------------------------------------------------- 1 | // AddCategoryView.swift 2 | 3 | import SwiftUI 4 | 5 | struct AddCategoryView: View { 6 | @EnvironmentObject var appVM: AppViewModel 7 | @EnvironmentObject var categoryVM: CategoryViewModel 8 | @Environment(\.dismiss) var dismiss 9 | 10 | @FocusState private var nameIsFocused: Bool 11 | 12 | @State var selectedType: CategoryType = .expense 13 | @State private var name: String = "" 14 | @State private var selectedImage: String = "folder.circle" 15 | @State private var selectedColor: String = "colorBlue" 16 | @State private var showAlert: Bool = false 17 | 18 | var body: some View { 19 | NavigationStack { 20 | VStack { 21 | ScrollView(.vertical, showsIndicators: false) { 22 | VStack(alignment: .leading) { 23 | HStack(alignment: .center) { 24 | Spacer() 25 | Image(systemName: selectedImage) 26 | .foregroundColor(Color(.black)) 27 | .font(.system(size: 50)) 28 | .frame(width: 100, height: 100) 29 | .background(Color(selectedColor)) 30 | .cornerRadius(25) 31 | Spacer() 32 | } .padding(.bottom, 15) 33 | 34 | Section { 35 | VStack(alignment: .leading) { 36 | Picker("Type", selection: $selectedType) { 37 | ForEach(CategoryType.allCases, id: \.self) { type in 38 | Text(type.localizedName()) 39 | } 40 | } 41 | .pickerStyle(.segmented) 42 | .padding() 43 | .frame(maxWidth: .infinity, maxHeight: .infinity) 44 | .background(Color("colorBalanceBG")) 45 | .cornerRadius(10) 46 | .padding(.bottom, 15) 47 | } 48 | } header: { 49 | Text("Select type:") 50 | .font(.caption).textCase(.uppercase) 51 | .padding(.leading, 10) 52 | } 53 | 54 | Section { 55 | VStack(alignment: .leading) { 56 | TextField("Name", text: $name) 57 | .padding() 58 | .frame(maxWidth: .infinity, maxHeight: .infinity) 59 | .background(Color("colorBalanceBG")) 60 | .cornerRadius(10) 61 | .padding(.bottom, 15) 62 | .focused($nameIsFocused) 63 | } 64 | } header: { 65 | Text("Enter Name") 66 | .font(.caption).textCase(.uppercase) 67 | .padding(.leading, 10) 68 | } 69 | .onTapGesture { 70 | nameIsFocused.toggle() 71 | } 72 | 73 | Section { 74 | IconPickerView(selectedImage: $selectedImage) 75 | .foregroundColor(Color(.black)) 76 | .padding() 77 | .frame(maxWidth: .infinity, maxHeight: .infinity) 78 | .background(Color("colorBalanceBG")) 79 | .cornerRadius(10) 80 | .padding(.bottom, 15) 81 | } header: { 82 | Text("Choose an icon:") 83 | .font(.caption).textCase(.uppercase) 84 | .padding(.leading, 10) 85 | } 86 | Section { 87 | ColorPickerView(selectedColor: $selectedColor) 88 | .padding(5) 89 | .frame(maxWidth: .infinity, maxHeight: .infinity) 90 | .background(Color("colorBalanceBG")) 91 | .cornerRadius(10) 92 | 93 | } header: { 94 | Text("Choose color:") 95 | .font(.caption).textCase(.uppercase) 96 | .padding(.leading, 10) 97 | } 98 | } 99 | .padding(.horizontal, 15) 100 | .padding(.top, 20) 101 | } 102 | } 103 | .background(Color("colorBG")) 104 | .navigationBarTitle("Create a category", displayMode: .inline) 105 | .scrollDismissesKeyboard(.immediately) 106 | .toolbar { 107 | ToolbarItem(placement: .navigationBarTrailing) { 108 | Button { 109 | if name.isEmpty { 110 | showAlert.toggle() 111 | } else { 112 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 113 | categoryVM.saveCategory(name: name, icon: selectedImage, color: selectedColor, type: selectedType) 114 | dismiss() 115 | } 116 | } label: { 117 | if name.isEmpty { 118 | Text("Add") 119 | .foregroundColor(.gray) 120 | } else { 121 | Text("Add") 122 | } 123 | } 124 | } 125 | } 126 | .alert("Пожалуйста введите название категории", isPresented: $showAlert) { 127 | Button("Okay", role: .cancel) { 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | struct AddCategoryView_Previews: PreviewProvider { 135 | static var previews: some View { 136 | AddCategoryView() 137 | .environmentObject(AppViewModel()) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /iWallet/View/Home/HomeView.swift: -------------------------------------------------------------------------------- 1 | // ContentView.swift 2 | 3 | import SwiftUI 4 | import RealmSwift 5 | 6 | struct HomeView: View { 7 | @EnvironmentObject var appVM: AppViewModel 8 | @EnvironmentObject var categoryVM: CategoryViewModel 9 | @EnvironmentObject var transactionVM: TransactionViewModel 10 | @ObservedResults(Category.self) var categories 11 | 12 | @State private var showAddTransaction: Bool = false 13 | @State private var selectedCategoryType: CategoryType = .expense 14 | 15 | private let adaptive = 16 | [ 17 | GridItem(.adaptive(minimum: 165)) 18 | ] 19 | 20 | var body: some View { 21 | NavigationStack { 22 | ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { 23 | ScrollView(.vertical, showsIndicators: false) { 24 | LazyVGrid(columns: adaptive) { 25 | 26 | BalanceView(amount: transactionVM.balance(), curren: appVM.currencySymbol, type: NSLocalizedString("Balance", comment: "Balance"), icon: "equal.circle", iconBG: Color(Colors.colorBlue)) 27 | 28 | BalanceView(amount: transactionVM.averageDailyExpense(), curren: appVM.currencySymbol, type: NSLocalizedString("Expense average", comment: "Expense average"), icon: "plusminus.circle", iconBG: Color(Colors.colorYellow)) 29 | 30 | BalanceView(amount: transactionVM.totalIncomes(), curren: appVM.currencySymbol, type: NSLocalizedString("Income", comment: "Income"), icon: "plus.circle", iconBG: Color(Colors.colorGreen)) 31 | 32 | BalanceView(amount: transactionVM.totalExpenses(), curren: appVM.currencySymbol, type: NSLocalizedString("Expense", comment: "Expense"), icon: "minus.circle", iconBG: Color(Colors.colorRed)) 33 | 34 | } 35 | .padding(.horizontal) 36 | .padding(.top) 37 | 38 | Picker("Тип", selection: $selectedCategoryType) { 39 | ForEach(CategoryType.allCases, id: \.self) { type in 40 | Text(type.localizedName()) 41 | } 42 | } 43 | .pickerStyle(SegmentedPickerStyle()) 44 | .padding(10) 45 | .frame(maxWidth: .infinity, maxHeight: .infinity) 46 | .background(Color(Colors.colorBalanceBG)) 47 | .cornerRadius(10) 48 | .padding(.horizontal, 15) 49 | 50 | VStack(spacing: 0) { 51 | // создаем массив транзакций по категориями 52 | let categoriesWithTransactionsArray = categoryVM.categoriesWithTransaction(categories: categories, type: selectedCategoryType) 53 | 54 | // фильтруем категории по типу 55 | var filteredCategoriesArray = categoryVM.filteredCategories(categories: categoriesWithTransactionsArray, type: selectedCategoryType) 56 | 57 | // сортируем категории по сумме 58 | let _: () = filteredCategoriesArray.sort(by: { $0.categoryAmount(type: selectedCategoryType) > $1.categoryAmount(type: selectedCategoryType)}) 59 | 60 | if filteredCategoriesArray.isEmpty { 61 | previewHomeTransaction() 62 | } else { 63 | 64 | ForEach(filteredCategoriesArray, id: \.self) { category in 65 | let totalAmount = category.categoryAmount(type: selectedCategoryType) 66 | NavigationLink(destination: TransactionCategoryView(selectedCategory: category)) { 67 | 68 | CategoryItemView(categoryColor: category.color, categoryIcon: category.icon, categoryName: category.name, totalAmount: totalAmount, currencySymbol: appVM.currencySymbol) 69 | } 70 | } 71 | } 72 | } 73 | .cornerRadius(10) 74 | .padding(.horizontal, 15) 75 | .padding(.bottom, 100) 76 | } 77 | 78 | 79 | HStack { 80 | Button { 81 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 82 | showAddTransaction.toggle() 83 | } label: { 84 | ZStack { 85 | Circle() 86 | .frame(width: 55, height: 55) 87 | .foregroundColor(Color("colorBalanceText")) 88 | Image(systemName: "plus") 89 | .foregroundColor(Color("colorBG")) 90 | .font(.system(size: 30)) 91 | } 92 | } 93 | } 94 | .padding(.all, 25) 95 | .toolbar { 96 | ToolbarItem(placement: .navigationBarTrailing) { 97 | Button { 98 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 99 | } label: { 100 | NavigationLink(destination: SettingView(), label: { 101 | HStack { 102 | Text("Settings") 103 | } 104 | }) 105 | } 106 | } 107 | ToolbarItem(placement: .navigationBarLeading) { 108 | Text("iWallet") 109 | .font(.title) 110 | .fontWeight(.bold) 111 | .foregroundColor(Color("colorBalanceText")) 112 | } 113 | } 114 | } 115 | .background(Color("colorBG")) 116 | } 117 | .sheet(isPresented: $showAddTransaction) { 118 | AddTransactionView(selectedCategory: Category(), selectedType: selectedCategoryType) 119 | } 120 | } 121 | } 122 | 123 | struct HomeView_Previews: PreviewProvider { 124 | static var previews: some View { 125 | HomeView() 126 | .environmentObject(AppViewModel()) 127 | .environmentObject(TransactionViewModel()) 128 | .environmentObject(CategoryViewModel()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /iWallet/Resources/Icons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Icons.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 29.05.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Icons { 11 | 12 | // все иконки создания категории 13 | static let car: String = "car" 14 | static let box_truck: String = "box.truck" 15 | static let car_2: String = "car.2" 16 | static let creditcard: String = "creditcard" 17 | static let banknote: String = "banknote" 18 | static let giftcard: String = "giftcard" 19 | static let person_2: String = "person.2" 20 | static let person_bust: String = "person.bust" 21 | static let figure_wave: String = "figure.wave" 22 | static let figure_roll: String = "figure.roll" 23 | static let circle_hexagonpath: String = "circle.hexagonpath" 24 | static let captions_bubble: String = "captions.bubble" 25 | static let network_badge_shield_half_filled: String = "network.badge.shield.half.filled" 26 | static let icloud: String = "icloud" 27 | static let bonjour: String = "bonjour" 28 | static let figure_2_and_child_holdinghands: String = "figure.2.and.child.holdinghands" 29 | static let figure_and_child_holdinghands: String = "figure.and.child.holdinghands" 30 | static let face_smiling: String = "face.smiling" 31 | static let house: String = "house" 32 | static let sofa: String = "sofa" 33 | static let fireplace: String = "fireplace" 34 | static let fish: String = "fish" 35 | static let hare: String = "hare" 36 | static let pawprint: String = "pawprint" 37 | static let popcorn: String = "popcorn" 38 | static let balloon_2: String = "balloon.2" 39 | static let figure_walk: String = "figure.walk" 40 | static let heart: String = "heart" 41 | static let pill: String = "pill" 42 | static let cross: String = "cross" 43 | static let fleuron: String = "fleuron" 44 | static let wand_and_stars_inverse: String = "wand.and.stars.inverse" 45 | static let pencil_tip: String = "pencil.tip" 46 | static let wifi: String = "wifi" 47 | static let antenna_radiowaves_left_and_right: String = "antenna.radiowaves.left.and.right" 48 | static let network: String = "network" 49 | static let book: String = "book" 50 | static let graduationcap: String = "graduationcap" 51 | static let pencil_and_ruler: String = "pencil.and.ruler" 52 | static let backpack: String = "backpack" 53 | static let tshirt: String = "tshirt" 54 | static let tag: String = "tag" 55 | static let gift: String = "gift" 56 | static let shippingbox: String = "shippingbox" 57 | static let party_popper_fill: String = "party.popper.fill" 58 | static let cart: String = "cart" 59 | static let carrot: String = "carrot" 60 | static let airpods_chargingcase: String = "airpods.chargingcase" 61 | static let airplane: String = "airplane" 62 | static let sailboat: String = "sailboat" 63 | static let road_lanes: String = "road.lanes" 64 | static let music_mic: String = "music.mic" 65 | static let theatermasks: String = "theatermasks" 66 | static let birthday_cake: String = "birthday.cake" 67 | static let display: String = "display" 68 | static let applewatch_watchface: String = "applewatch.watchface" 69 | static let gamecontroller: String = "gamecontroller" 70 | static let bus_fill: String = "bus.fill" 71 | static let cablecar: String = "cablecar" 72 | static let steeringwheel: String = "steeringwheel" 73 | static let key: String = "key" 74 | static let door_right_hand_closed: String = "door.right.hand.closed" 75 | static let house_and_flag: String = "house.and.flag" 76 | static let arrow_triangle_2_circlepath: String = "arrow.triangle.2.circlepath" 77 | static let dollarsign_arrow_circlepath: String = "dollarsign.arrow.circlepath" 78 | static let hourglass: String = "hourglass" 79 | static let chart_xyaxis_line: String = "chart.xyaxis.line" 80 | static let creditcard_and_123: String = "creditcard.and.123" 81 | static let chart_bar: String = "chart.bar" 82 | static let dollarsign: String = "dollarsign" 83 | static let rublesign: String = "rublesign" 84 | static let eurosign: String = "eurosign" 85 | static let shippingbox_circle: String = "shippingbox.circle" 86 | static let timelapse: String = "timelapse" 87 | static let camera_metering_matrix: String = "camera.metering.matrix" 88 | static let person_fill_checkmark: String = "person.fill.checkmark" 89 | static let person_crop_square_filled_and_at_rectangle_fill: String = "person.crop.square.filled.and.at.rectangle.fill" 90 | static let hand_thumbsup: String = "hand.thumbsup" 91 | static let percent: String = "percent" 92 | static let sum: String = "sum" 93 | static let number: String = "number" 94 | 95 | static let allIcons: [String] = 96 | [ 97 | car, 98 | box_truck, 99 | car_2, 100 | creditcard, 101 | banknote, 102 | giftcard, 103 | person_2, 104 | person_bust, 105 | figure_wave, 106 | figure_roll, 107 | circle_hexagonpath, 108 | captions_bubble, 109 | network_badge_shield_half_filled, 110 | icloud, 111 | bonjour, 112 | figure_2_and_child_holdinghands, 113 | figure_and_child_holdinghands, 114 | face_smiling, 115 | house, 116 | sofa, 117 | fireplace, 118 | fish, 119 | hare, 120 | pawprint, 121 | popcorn, 122 | balloon_2, 123 | figure_walk, 124 | heart, 125 | pill, 126 | cross, 127 | fleuron, 128 | wand_and_stars_inverse, 129 | pencil_tip, 130 | wifi, 131 | antenna_radiowaves_left_and_right, 132 | network, 133 | book, 134 | graduationcap, 135 | pencil_and_ruler, 136 | backpack, 137 | tshirt, 138 | tag, 139 | gift, 140 | shippingbox, 141 | party_popper_fill, 142 | cart, 143 | carrot, 144 | airpods_chargingcase, 145 | airplane, 146 | sailboat, 147 | road_lanes, 148 | music_mic, 149 | theatermasks, 150 | birthday_cake, 151 | display, 152 | applewatch_watchface, 153 | gamecontroller, 154 | bus_fill, 155 | cablecar, 156 | steeringwheel, 157 | key, 158 | door_right_hand_closed, 159 | house_and_flag, 160 | arrow_triangle_2_circlepath, 161 | dollarsign_arrow_circlepath, 162 | hourglass, 163 | chart_xyaxis_line, 164 | creditcard_and_123, 165 | chart_bar, 166 | dollarsign, 167 | rublesign, 168 | eurosign, 169 | shippingbox_circle, 170 | timelapse, 171 | camera_metering_matrix, 172 | person_fill_checkmark, 173 | person_crop_square_filled_and_at_rectangle_fill, 174 | hand_thumbsup, 175 | percent, 176 | sum, 177 | number 178 | ] 179 | } 180 | -------------------------------------------------------------------------------- /iWallet/View/Transaction/EditTransactionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditTransactionView.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 13.06.2023. 6 | // 7 | 8 | import SwiftUI 9 | import RealmSwift 10 | 11 | struct EditTransactionView: View { 12 | @EnvironmentObject private var appVM: AppViewModel 13 | @EnvironmentObject private var transactionVM: TransactionViewModel 14 | @EnvironmentObject private var categoryVM: CategoryViewModel 15 | @Environment(\.dismiss) private var dismiss 16 | 17 | @State var selectedTransaction: TransactionItem 18 | @State private var alertAmount: Bool = false 19 | 20 | @FocusState private var amountIsFocused: Bool 21 | @FocusState private var noteIsFocused: Bool 22 | 23 | let formatter: NumberFormatter = { 24 | let formatter = NumberFormatter() 25 | formatter.numberStyle = .decimal 26 | if AppViewModel().roundingNumbers == true { 27 | formatter.maximumFractionDigits = 0 28 | } else { 29 | formatter.maximumFractionDigits = 2 30 | } 31 | return formatter 32 | }() 33 | 34 | var body: some View { 35 | ScrollView(.vertical, showsIndicators: false) { 36 | VStack(alignment: .leading) { 37 | Section { 38 | TextField(selectedTransaction.type == .expense ? "-100 \(appVM.currencySymbol)" : "+100 \(appVM.currencySymbol)", value: $selectedTransaction.amount, formatter: formatter) 39 | .font(.title3) 40 | .keyboardType(appVM.roundingNumbers ? .numberPad : .decimalPad) 41 | .padding() 42 | .frame(maxWidth: .infinity, maxHeight: .infinity) 43 | .background(Color("colorBalanceBG")) 44 | .cornerRadius(10) 45 | .padding(.bottom, 15) 46 | .focused($amountIsFocused) 47 | } header: { 48 | Text("Enter amount:") 49 | .font(.caption).textCase(.uppercase) 50 | .padding(.leading, 10) 51 | } 52 | .onTapGesture { 53 | amountIsFocused.toggle() 54 | } 55 | 56 | Section { 57 | TextField("Note", text: $selectedTransaction.note) 58 | .padding() 59 | .frame(maxWidth: .infinity, maxHeight: .infinity) 60 | .background(Color("colorBalanceBG")) 61 | .cornerRadius(10) 62 | .focused($noteIsFocused) 63 | 64 | ScrollView(.horizontal, showsIndicators: false) { 65 | HStack { 66 | // находит категорию в которую была записана транзакция 67 | let categoryResult = categorySearch(categories: categoryVM.categories) 68 | 69 | // находит все заметки в той категории в которую была записанна транзакция 70 | let filterTransaction = transactionVM.filterTransactionsNote(category: categoryResult, transactions: transactionVM.transactions) 71 | 72 | ForEach(filterTransaction.reversed(), id: \.self) { notes in 73 | Button { 74 | selectedTransaction.note = notes.note 75 | } label: { 76 | Text(String(notes.note.prefix(20))) 77 | .font(Font.caption) 78 | .foregroundColor(Color(.systemGray2)) 79 | .padding(.vertical, 5) 80 | .padding(.horizontal, 10) 81 | .background( 82 | RoundedRectangle(cornerRadius: 7, style: .continuous) 83 | .strokeBorder(Color(.systemGray2)) 84 | ) 85 | .padding(.bottom, 10) 86 | } 87 | } 88 | } 89 | .padding(.horizontal, 10) 90 | } 91 | } header: { 92 | Text("Enter note:") 93 | .font(.caption).textCase(.uppercase) 94 | .padding(.leading, 10) 95 | } 96 | .onTapGesture { 97 | noteIsFocused.toggle() 98 | } 99 | 100 | Section { 101 | HStack { 102 | DatePicker("Date", selection: $selectedTransaction.date, displayedComponents: .date) 103 | } 104 | .pickerStyle(SegmentedPickerStyle()) 105 | .padding() 106 | .frame(maxWidth: .infinity, maxHeight: .infinity) 107 | .background(Color("colorBalanceBG")) 108 | .cornerRadius(10) 109 | } header: { 110 | Text("Enter date:") 111 | .font(.caption).textCase(.uppercase) 112 | .padding(.leading, 10) 113 | .padding(.top, 10) 114 | } 115 | } 116 | .padding(.horizontal, 15) 117 | .padding(.top, 20) 118 | } 119 | .navigationBarTitle("Editing", displayMode: .inline) 120 | .scrollDismissesKeyboard(.immediately) 121 | .background(Color("colorBG")) 122 | .toolbar { 123 | ToolbarItem(placement: .navigationBarTrailing) { 124 | Button { 125 | if selectedTransaction.amount.formattedWithSeparatorAndCurrency(roundingNumbers: appVM.roundingNumbers).count == 0 { 126 | alertAmount = true 127 | } else { 128 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 129 | dismiss() 130 | } 131 | } label: { 132 | Text("Edit") 133 | } 134 | } 135 | } 136 | .alert("Please enter amount", isPresented: $alertAmount) { 137 | Button("Okay", role: .cancel) { } 138 | } 139 | } 140 | 141 | // находит категорию в которую была записана транзакция 142 | func categorySearch(categories: [Category]) -> Category { 143 | var result = Category() 144 | 145 | for category in categories { 146 | if category.id == selectedTransaction.categoryId { 147 | if category.hasTransactions(type: selectedTransaction.type) { 148 | result = category 149 | } 150 | } 151 | } 152 | return result 153 | } 154 | } 155 | 156 | struct EditTransactionView_Previews: PreviewProvider { 157 | static var previews: some View { 158 | EditTransactionView(selectedTransaction: TransactionItem()) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /iWallet/ViewModel/TransactionViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransactionViewModel.swift 3 | // iWallet 4 | // 5 | // Created by Vladislav Novoshinskiy on 25.05.2023. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class TransactionViewModel: ObservableObject { 12 | @Published var transactions: [TransactionItem] = [] 13 | 14 | init() { 15 | loadData() 16 | } 17 | 18 | // Метод для загрузки транзакций 19 | func loadData() { 20 | guard let realm = try? Realm() else { 21 | print("Ошибка: loadData") 22 | return 23 | } 24 | let transactionsResult = realm.objects(TransactionItem.self) 25 | transactions = Array(transactionsResult) 26 | } 27 | 28 | // Метод сохранения транзакции 29 | func saveTransaction(amount: Float, date: Date, note: String, type: CategoryType, category: Category) { 30 | guard let realm = try? Realm() else { 31 | print("Ошибка: Не удалось создать экземпляр Realm") 32 | return 33 | } 34 | if let newCategory = realm.object(ofType: Category.self, forPrimaryKey: category.id) { 35 | let newTransaction = TransactionItem() 36 | newTransaction.categoryId = newCategory.id 37 | newTransaction.amount = amount 38 | newTransaction.date = date 39 | newTransaction.note = note 40 | newTransaction.type = type 41 | do { 42 | try realm.write { 43 | newCategory.transactions.append(newTransaction) 44 | } 45 | 46 | // Обновляю список транзакций после сохранения 47 | transactions.append(newTransaction) 48 | 49 | // Отладочное сообщение 50 | print("Транзакция сохранена: \(newTransaction)") 51 | } catch { 52 | // Отладочное сообщение 53 | print("Ошибка сохранения транзакции: \(error)") 54 | } 55 | } else { 56 | // Отладочное сообщение, если категория не найдена 57 | print("Ошибка: категория не найдена") 58 | } 59 | } 60 | 61 | // Метод для удаления транзакций 62 | private func deleteTransaction(withId id: ObjectId) { 63 | do { 64 | let realm = try Realm() 65 | 66 | if let transaction = realm.object(ofType: TransactionItem.self, forPrimaryKey: id) { 67 | try realm.write { 68 | if let category = transaction.category.first { 69 | if let index = category.transactions.firstIndex(of: transaction) { 70 | category.transactions.remove(at: index) 71 | } 72 | } 73 | realm.delete(transaction) 74 | } 75 | loadData() 76 | } else { 77 | print("Транзакция с ID \(id) не найдена") 78 | } 79 | } catch let error { 80 | print("Ошибка удаления транзакции: \(error)") 81 | } 82 | } 83 | 84 | // Метод удаления транзакций 85 | func deleteTransaction(at offsets: IndexSet, from sortedTransactions: [TransactionItem]) { 86 | offsets.forEach { index in 87 | let transaction = sortedTransactions[index] 88 | deleteTransaction(withId: transaction.id) 89 | } 90 | } 91 | 92 | // MARK: TransactionView 93 | // Метод для группировки транзакций по дате 94 | func transactionsByDate(_ transactions: [TransactionItem]) -> [Date: [TransactionItem]] { 95 | var groupedTransactions: [Date: [TransactionItem]] = [:] 96 | 97 | let dateFormatter = DateFormatter() 98 | dateFormatter.dateStyle = .medium 99 | dateFormatter.timeStyle = .none 100 | 101 | for transaction in transactions { 102 | let dateString = dateFormatter.string(from: transaction.date) 103 | if let date = dateFormatter.date(from: dateString) { 104 | if groupedTransactions[date] == nil { 105 | groupedTransactions[date] = [] 106 | } 107 | groupedTransactions[date]?.append(transaction) 108 | } 109 | } 110 | 111 | return groupedTransactions 112 | } 113 | 114 | // Метод фильтрации категорий 115 | func filterCategories(categories: [Category], transaction: TransactionItem) -> Category? { 116 | for category in categories { 117 | if category.id == transaction.categoryId { 118 | return category 119 | } 120 | } 121 | return nil 122 | } 123 | 124 | // метод сортировки транзакций по дате 125 | func sortTransactionsByDate(transactions: [TransactionItem]) -> [TransactionItem] { 126 | return transactions.sorted(by: { $0.date > $1.date }) 127 | } 128 | 129 | // MARK: TransactionCategoryView 130 | // метод фильтрации транзакции с выбранной категорией 131 | func filterTransaction(category: Category, transactions: [TransactionItem]) -> [TransactionItem] { 132 | var groupedTransaction: [TransactionItem] = [] 133 | 134 | for transaction in transactions { 135 | if category.id == transaction.categoryId { 136 | groupedTransaction.append(transaction) 137 | } 138 | } 139 | return groupedTransaction 140 | } 141 | 142 | // MARK: AddTransactionView 143 | // метод фильтрации транзакций для отбора уникальных заметок 144 | func filterTransactionsNote(category: Category, transactions: [TransactionItem]) -> [TransactionItem] { 145 | var groupedTransaction: [TransactionItem] = [] 146 | var uniqueNotes: [String] = [] 147 | 148 | for transaction in transactions.prefix(20) { 149 | if category.id == transaction.categoryId { 150 | if transaction.note.count != 0 { 151 | if !uniqueNotes.contains(transaction.note.description) { 152 | uniqueNotes.append(transaction.note.description) 153 | groupedTransaction.append(transaction) 154 | } 155 | } 156 | } 157 | } 158 | return groupedTransaction 159 | } 160 | 161 | // MARK: HomeView 162 | // Считает расход 163 | func totalExpenses() -> Float { 164 | var expenses: Float = 0 165 | for transaction in transactions { 166 | if transaction.type == .expense { 167 | expenses += transaction.amount 168 | } 169 | } 170 | return expenses 171 | } 172 | 173 | // Считает доход 174 | func totalIncomes() -> Float { 175 | var icncome: Float = 0 176 | for transaction in transactions { 177 | if transaction.type == .income { 178 | icncome += transaction.amount 179 | } 180 | } 181 | return icncome 182 | } 183 | 184 | // Считает балланс 185 | func balance() -> Float { 186 | return totalIncomes() - totalExpenses() 187 | } 188 | 189 | // Расчет среднего расхода за день, сначала нахожу общее количество дней c транзакциями, а затем разделим общую сумму расходных транзакций на количество дней 190 | func averageDailyExpense() -> Float { 191 | let expenseTransactions = transactions.filter { $0.type == .expense } 192 | guard !expenseTransactions.isEmpty else { 193 | return 0 194 | } 195 | 196 | let uniqueExpenseDates = Set(expenseTransactions.map { transaction -> Date in 197 | let calendar = Calendar.current 198 | let components = calendar.dateComponents([.year, .month, .day], from: transaction.date) 199 | return calendar.date(from: components) ?? transaction.date 200 | }) 201 | 202 | let daysWithTransactions = uniqueExpenseDates.count 203 | 204 | let totalExpenseAmount = totalExpenses() 205 | 206 | return totalExpenseAmount / Float(daysWithTransactions) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /iWallet/View/Setting/SettingView.swift: -------------------------------------------------------------------------------- 1 | // SettingView.swift 2 | 3 | import SwiftUI 4 | 5 | struct SettingView: View { 6 | @EnvironmentObject var appVM: AppViewModel 7 | @Environment(\.dismiss) var dismiss 8 | @Environment(\.openURL) var openURL 9 | 10 | @State private var selectedCurrency: Currency = .usd 11 | @State private var showCategory: Bool = false 12 | @State private var showTransactionView: Bool = false 13 | 14 | var body: some View { 15 | NavigationStack { 16 | VStack { 17 | List { 18 | Section { 19 | Button { 20 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 21 | showTransactionView.toggle() 22 | } label: { 23 | HStack { 24 | Image(systemName: "clock.circle") 25 | .foregroundColor(Color("colorBlack")) 26 | .frame(width: 30, height: 30) 27 | .background(Color("colorYellow")) 28 | .cornerRadius(7.5) 29 | Text("Transactions") 30 | .foregroundColor(Color("colorBalanceText")) 31 | Spacer() 32 | Image(systemName: "chevron.forward") 33 | .foregroundColor(Color("colorBalanceText")) 34 | .opacity(0.5) 35 | } 36 | } 37 | 38 | Button { 39 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 40 | showCategory.toggle() 41 | } label: { 42 | HStack { 43 | Image(systemName: "list.bullet.circle") 44 | .foregroundColor(Color("colorBlack")) 45 | .frame(width: 30, height: 30) 46 | .background(Color("colorBlue")) 47 | .cornerRadius(7.5) 48 | Text("Categories") 49 | .foregroundColor(Color("colorBalanceText")) 50 | Spacer() 51 | Image(systemName: "chevron.forward") 52 | .foregroundColor(Color("colorBalanceText")) 53 | .opacity(0.5) 54 | } 55 | } 56 | } header: { 57 | Text("Data") 58 | } 59 | 60 | Section(header: Text("Application")) { 61 | HStack { 62 | Text(selectedCurrency.symbol) 63 | .foregroundColor(Color("colorBlack")) 64 | .frame(width: 30, height: 30) 65 | .background(Color("colorBrown1")) 66 | .cornerRadius(7.5) 67 | Picker("Currency", selection: $selectedCurrency) { 68 | ForEach(Currency.sortedCases, id: \.self) { currency in 69 | Text(currency.rawValue) 70 | .tag(currency) 71 | } 72 | } 73 | } 74 | HStack { 75 | Image(systemName: appVM.selectedFeedbackHaptic ? "iphone.radiowaves.left.and.right.circle" : "iphone.slash.circle") 76 | .foregroundColor(Color("colorBlack")) 77 | .frame(width: 30, height: 30) 78 | .background(Color("colorPurple2")) 79 | .cornerRadius(7.5) 80 | Toggle("Vibration", isOn: $appVM.selectedFeedbackHaptic) 81 | } 82 | 83 | HStack { 84 | Text(appVM.roundingNumbers ? "0" : "0.0") 85 | .foregroundColor(Color("colorBlack")) 86 | .frame(width: 30, height: 30) 87 | .background(Color(Colors.colorGreen)) 88 | .cornerRadius(7.5) 89 | Toggle("Rounding numbers", isOn: $appVM.roundingNumbers) 90 | } 91 | } 92 | 93 | Section { 94 | Button { 95 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 96 | openURL(URL(string: NSLocalizedString("https://idevnva.com/", comment: "https://idevnva.com/"))!) 97 | } label: { 98 | HStack { 99 | Image(systemName: "network") 100 | .foregroundColor(Color("colorBlack")) 101 | .frame(width: 30, height: 30) 102 | .background(Color("colorRed")) 103 | .cornerRadius(7.5) 104 | Text("Web-site") 105 | .foregroundColor(Color("colorBalanceText")) 106 | Spacer() 107 | Image(systemName: "chevron.forward") 108 | .foregroundColor(Color("colorBalanceText")) 109 | .opacity(0.5) 110 | } 111 | } 112 | 113 | Button { 114 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 115 | openURL(URL(string: NSLocalizedString("https://t.me/idevnva", comment: "https://t.me/idevnva"))!) 116 | } label: { 117 | HStack { 118 | Image(systemName: "envelope.circle") 119 | .foregroundColor(Color("colorBlack")) 120 | .frame(width: 30, height: 30) 121 | .background(Color("colorGray1")) 122 | .cornerRadius(7.5) 123 | Text("Write to the developer") 124 | .foregroundColor(Color("colorBalanceText")) 125 | Spacer() 126 | Image(systemName: "chevron.forward") 127 | .foregroundColor(Color("colorBalanceText")) 128 | .opacity(0.5) 129 | } 130 | } 131 | } header: { 132 | Text("Feedback") 133 | } 134 | } 135 | VStack { 136 | Image(systemName: "exclamationmark.shield") 137 | Text("App version: 1.1.7") 138 | } 139 | .font(.caption2).bold() 140 | .padding() 141 | } 142 | .navigationTitle("Settings") 143 | .scrollContentBackground(.hidden) 144 | .background(Color("colorBG")) 145 | .onAppear { 146 | // Устанавливаем selectedCurrency на основе сохраненного символа валюты 147 | if let currency = Currency.allCases.first(where: { $0.symbol == appVM.currencySymbol }) { 148 | selectedCurrency = currency 149 | } 150 | } 151 | .onChange(of: selectedCurrency) { newCurrency in 152 | // Сохраняем символ валюты при изменении выбора 153 | appVM.currencySymbol = newCurrency.symbol 154 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 155 | } 156 | .fullScreenCover(isPresented: $showCategory) { 157 | CategoryView() 158 | } 159 | .sheet(isPresented: $showTransactionView) { 160 | TransactionView() 161 | } 162 | } 163 | } 164 | } 165 | 166 | 167 | struct SettingView_Previews: PreviewProvider { 168 | static var previews: some View { 169 | SettingView() 170 | .environmentObject(AppViewModel()) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /iWallet/View/Transaction/AddTransactionView.swift: -------------------------------------------------------------------------------- 1 | // AddTransactionView.swift 2 | 3 | import SwiftUI 4 | import RealmSwift 5 | 6 | struct AddTransactionView: View { 7 | @EnvironmentObject var appVM: AppViewModel 8 | @EnvironmentObject var transactionVM: TransactionViewModel 9 | @Environment(\.dismiss) var dismiss 10 | 11 | @FocusState private var amountIsFocused: Bool 12 | @FocusState private var noteIsFocused: Bool 13 | @FocusState private var keyIsFocused: Bool 14 | 15 | @State var selectedCategory: Category 16 | @State var amount: Float = 0 17 | @State var date: Date = Date() 18 | @State var note: String = "" 19 | @State var selectedType: CategoryType = .expense 20 | @State var alertAmount: Bool = false 21 | @State var alertCategory: Bool = false 22 | 23 | private let formatter: NumberFormatter = { 24 | let formatter = NumberFormatter() 25 | formatter.numberStyle = .decimal 26 | formatter.maximumFractionDigits = 2 27 | formatter.zeroSymbol = "" 28 | return formatter 29 | }() 30 | 31 | var body: some View { 32 | NavigationStack { 33 | ScrollView(.vertical, showsIndicators: false) { 34 | VStack(alignment: .leading) { 35 | Section { 36 | TextField(selectedType == .expense ? "-100 \(appVM.currencySymbol)" : "+100 \(appVM.currencySymbol)", value: $amount, formatter: formatter) 37 | .font(.title3) 38 | .keyboardType(appVM.roundingNumbers ? .numberPad : .decimalPad) 39 | .padding() 40 | .frame(maxWidth: .infinity, maxHeight: .infinity) 41 | .background(Color("colorBalanceBG")) 42 | .cornerRadius(10) 43 | .padding(.bottom, 15) 44 | .focused($amountIsFocused) 45 | } header: { 46 | Text("Enter amount:") 47 | .font(.caption).textCase(.uppercase) 48 | .padding(.leading, 10) 49 | } 50 | .onTapGesture { 51 | amountIsFocused.toggle() 52 | } 53 | 54 | Section { 55 | TextField("Note", text: $note) 56 | .padding() 57 | .frame(maxWidth: .infinity, maxHeight: .infinity) 58 | .background(Color("colorBalanceBG")) 59 | .cornerRadius(10) 60 | .focused($noteIsFocused) 61 | 62 | ScrollView(.horizontal, showsIndicators: false) { 63 | HStack { 64 | let filterTransaction = transactionVM.filterTransactionsNote(category: selectedCategory, transactions: transactionVM.transactions) 65 | ForEach(filterTransaction.reversed(), id: \.self) { notes in 66 | Button { 67 | note = notes.note 68 | } label: { 69 | Text(String(notes.note.prefix(20))) 70 | .font(Font.caption) 71 | .foregroundColor(Color(.systemGray2)) 72 | .padding(.vertical, 5) 73 | .padding(.horizontal, 10) 74 | .background( 75 | RoundedRectangle(cornerRadius: 7, style: .continuous) 76 | .strokeBorder(Color(.systemGray2)) 77 | ) 78 | .padding(.bottom, 10) 79 | } 80 | } 81 | } 82 | .padding(.horizontal, 10) 83 | } 84 | } header: { 85 | Text("Enter note:") 86 | .font(.caption).textCase(.uppercase) 87 | .padding(.leading, 10) 88 | } 89 | .onTapGesture { 90 | noteIsFocused.toggle() 91 | } 92 | 93 | Section { 94 | Picker("Category type", selection: $selectedType) { 95 | ForEach(CategoryType.allCases, id: \.self) { type in 96 | Text(type.localizedName()) 97 | } 98 | } 99 | .pickerStyle(SegmentedPickerStyle()) 100 | .padding(10) 101 | .frame(maxWidth: .infinity, maxHeight: .infinity) 102 | .background(Color("colorBalanceBG")) 103 | .cornerRadius(10) 104 | .onChange(of: selectedType) { _ in 105 | withAnimation() { 106 | selectedCategory = Category() 107 | } 108 | 109 | } 110 | 111 | HStack { 112 | NavigationLink(destination: PickerCategoryView(selected: $selectedCategory, selectedType: selectedType), label: { 113 | if selectedCategory.name.isEmpty { 114 | HStack { 115 | Spacer() 116 | Text("?") 117 | .font(.system(size: 15)) 118 | .frame(width: 30, height: 30) 119 | .background { 120 | RoundedRectangle(cornerRadius: 10, style: .circular) 121 | .strokeBorder(Color(Colors.mainText)) 122 | } 123 | 124 | Text("Select a category") 125 | Spacer() 126 | } 127 | .font(Font.subheadline) 128 | .padding(10) 129 | .frame(maxWidth: .infinity) 130 | .background(Color(Colors.colorPickerBG)) 131 | .cornerRadius(10) 132 | } else { 133 | HStack { 134 | Text("Category:") 135 | .font(.headline) 136 | Spacer() 137 | Image(systemName: selectedCategory.icon) 138 | .font(.system(size: 15)) 139 | .foregroundColor(.black) 140 | .frame(width: 30, height: 30) 141 | .background(Color(selectedCategory.color)) 142 | .cornerRadius(7.5) 143 | Text(selectedCategory.name) 144 | .font(.headline) 145 | .fontWeight(.light) 146 | } 147 | .padding(5) 148 | } 149 | }) 150 | .foregroundColor(Color(Colors.mainText)) 151 | .padding(10) 152 | .background(Color(Colors.colorBalanceBG)) 153 | .cornerRadius(10) 154 | } 155 | } header: { 156 | Text("Purpose:") 157 | .font(.caption).textCase(.uppercase) 158 | .padding(.leading, 10) 159 | } 160 | 161 | Section { 162 | HStack { 163 | DatePicker("Date", selection: $date, displayedComponents: .date) 164 | } 165 | .pickerStyle(SegmentedPickerStyle()) 166 | .padding() 167 | .frame(maxWidth: .infinity, maxHeight: .infinity) 168 | .background(Color("colorBalanceBG")) 169 | .cornerRadius(10) 170 | } header: { 171 | Text("Enter date:") 172 | .font(.caption).textCase(.uppercase) 173 | .padding(.leading, 10) 174 | .padding(.top, 10) 175 | } 176 | } 177 | .padding(.horizontal, 15) 178 | .padding(.top, 20) 179 | 180 | } 181 | .scrollDismissesKeyboard(.immediately) 182 | .background(Color("colorBG")) 183 | .navigationBarTitle("Addendum", displayMode: .inline) 184 | .toolbar { 185 | ToolbarItem(placement: .navigationBarLeading) { 186 | Button { 187 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 188 | dismiss() 189 | } label: { 190 | Text("Cancel") 191 | } 192 | } 193 | ToolbarItem(placement: .navigationBarTrailing) { 194 | Button { 195 | if amount == 0 { 196 | alertAmount = true 197 | } else if selectedCategory.name == "" { 198 | alertCategory = true 199 | } else if selectedType != selectedCategory.type { 200 | alertCategory = true 201 | selectedCategory = Category() 202 | } else { 203 | playFeedbackHaptic(appVM.selectedFeedbackHaptic) 204 | transactionVM.saveTransaction(amount: amount, date: date, note: note, type: selectedType, category: selectedCategory) 205 | dismiss() 206 | } 207 | } label: { 208 | Text("Add") 209 | } 210 | .alert("Please select a category", isPresented: $alertCategory) { 211 | Button("Okay", role: .cancel) { } 212 | } 213 | .alert("Please enter amount", isPresented: $alertAmount) { 214 | Button("Okay", role: .cancel) { } 215 | } 216 | } 217 | } 218 | } 219 | } 220 | } 221 | 222 | struct AddTransactionView_Previews: PreviewProvider { 223 | static var previews: some View { 224 | AddTransactionView(selectedCategory: Category()) 225 | .environmentObject(AppViewModel()) 226 | .environmentObject(TransactionViewModel()) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /iWallet.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0812FA482A39085A00764C0E /* EditTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0812FA472A39085A00764C0E /* EditTransactionView.swift */; }; 11 | 08558B8629D35EA40047F959 /* iWalletApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B8529D35EA40047F959 /* iWalletApp.swift */; }; 12 | 08558B8829D35EA40047F959 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B8729D35EA40047F959 /* HomeView.swift */; }; 13 | 08558B8A29D35EA40047F959 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08558B8929D35EA40047F959 /* Assets.xcassets */; }; 14 | 08558B8D29D35EA40047F959 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08558B8C29D35EA40047F959 /* Preview Assets.xcassets */; }; 15 | 08558B9829D35F890047F959 /* BalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B9729D35F890047F959 /* BalanceView.swift */; }; 16 | 08558B9A29D36D6D0047F959 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B9929D36D6D0047F959 /* SettingView.swift */; }; 17 | 08558B9C29D37DDC0047F959 /* CategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B9B29D37DDC0047F959 /* CategoryView.swift */; }; 18 | 08558B9E29D380920047F959 /* AddCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B9D29D380920047F959 /* AddCategoryView.swift */; }; 19 | 08558BA029D381F10047F959 /* ColorPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558B9F29D381F10047F959 /* ColorPickerView.swift */; }; 20 | 08558BA229D388080047F959 /* IconPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558BA129D388080047F959 /* IconPickerView.swift */; }; 21 | 08558BA629D394950047F959 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08558BA529D394950047F959 /* AppViewModel.swift */; }; 22 | 08558BA929D394F40047F959 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 08558BA829D394F40047F959 /* Realm */; }; 23 | 08558BAB29D394F40047F959 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 08558BAA29D394F40047F959 /* RealmSwift */; }; 24 | 086459892A201BB0006C28AB /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086459882A201BB0006C28AB /* Colors.swift */; }; 25 | 0864598E2A20269C006C28AB /* CategoryItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0864598D2A20269C006C28AB /* CategoryItemView.swift */; }; 26 | 08645FDC2A1E80460059BDCB /* CategoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FDB2A1E80460059BDCB /* CategoryModel.swift */; }; 27 | 08645FDE2A1E808E0059BDCB /* CategoryTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FDD2A1E808E0059BDCB /* CategoryTypeModel.swift */; }; 28 | 08645FE02A1E80CF0059BDCB /* TransactionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FDF2A1E80CF0059BDCB /* TransactionModel.swift */; }; 29 | 08645FE32A1E817D0059BDCB /* ExtensionFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FE22A1E817D0059BDCB /* ExtensionFloat.swift */; }; 30 | 08645FE52A1E81E60059BDCB /* CurrencyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FE42A1E81E60059BDCB /* CurrencyModel.swift */; }; 31 | 08645FE72A1E824B0059BDCB /* DefaultCategoriesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FE62A1E824B0059BDCB /* DefaultCategoriesModel.swift */; }; 32 | 08645FE92A1E835D0059BDCB /* PlayFeedbackHaptic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FE82A1E835D0059BDCB /* PlayFeedbackHaptic.swift */; }; 33 | 08645FEE2A1EA7280059BDCB /* CategoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FED2A1EA7280059BDCB /* CategoryViewModel.swift */; }; 34 | 08645FF02A1FBD030059BDCB /* TransactionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08645FEF2A1FBD030059BDCB /* TransactionViewModel.swift */; }; 35 | 086A5A2029E34B0000D044F7 /* PreviewCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5A1F29E34B0000D044F7 /* PreviewCardView.swift */; }; 36 | 0880C66D29E8682B00E088DB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0880C66F29E8682B00E088DB /* Localizable.strings */; }; 37 | 0880C67529E8AB9200E088DB /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0880C67429E8AB9200E088DB /* WelcomeView.swift */; }; 38 | 08A2C2752A24C77400B8FA6D /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A2C2742A24C77400B8FA6D /* Icons.swift */; }; 39 | 08AE95FF29E3236600F8AFA7 /* TransactionCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AE95FE29E3236600F8AFA7 /* TransactionCategoryView.swift */; }; 40 | 08D38C5029D465640078C6C4 /* AddTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D38C4F29D465640078C6C4 /* AddTransactionView.swift */; }; 41 | 08D38C5429D48FE00078C6C4 /* TransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D38C5329D48FE00078C6C4 /* TransactionView.swift */; }; 42 | 08F15F1A2A2A95890009FC0A /* PickerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F15F192A2A95890009FC0A /* PickerCategoryView.swift */; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 0812FA472A39085A00764C0E /* EditTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTransactionView.swift; sourceTree = ""; }; 47 | 08558B8229D35EA40047F959 /* iWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iWallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 08558B8529D35EA40047F959 /* iWalletApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iWalletApp.swift; sourceTree = ""; }; 49 | 08558B8729D35EA40047F959 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 50 | 08558B8929D35EA40047F959 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 08558B8C29D35EA40047F959 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 52 | 08558B9729D35F890047F959 /* BalanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceView.swift; sourceTree = ""; }; 53 | 08558B9929D36D6D0047F959 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; 54 | 08558B9B29D37DDC0047F959 /* CategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryView.swift; sourceTree = ""; }; 55 | 08558B9D29D380920047F959 /* AddCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCategoryView.swift; sourceTree = ""; }; 56 | 08558B9F29D381F10047F959 /* ColorPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerView.swift; sourceTree = ""; }; 57 | 08558BA129D388080047F959 /* IconPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconPickerView.swift; sourceTree = ""; }; 58 | 08558BA529D394950047F959 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = ""; }; 59 | 086459882A201BB0006C28AB /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 60 | 0864598D2A20269C006C28AB /* CategoryItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryItemView.swift; sourceTree = ""; }; 61 | 08645FDB2A1E80460059BDCB /* CategoryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryModel.swift; sourceTree = ""; }; 62 | 08645FDD2A1E808E0059BDCB /* CategoryTypeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryTypeModel.swift; sourceTree = ""; }; 63 | 08645FDF2A1E80CF0059BDCB /* TransactionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionModel.swift; sourceTree = ""; }; 64 | 08645FE22A1E817D0059BDCB /* ExtensionFloat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionFloat.swift; sourceTree = ""; }; 65 | 08645FE42A1E81E60059BDCB /* CurrencyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyModel.swift; sourceTree = ""; }; 66 | 08645FE62A1E824B0059BDCB /* DefaultCategoriesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultCategoriesModel.swift; sourceTree = ""; }; 67 | 08645FE82A1E835D0059BDCB /* PlayFeedbackHaptic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayFeedbackHaptic.swift; sourceTree = ""; }; 68 | 08645FED2A1EA7280059BDCB /* CategoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryViewModel.swift; sourceTree = ""; }; 69 | 08645FEF2A1FBD030059BDCB /* TransactionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewModel.swift; sourceTree = ""; }; 70 | 086A5A1F29E34B0000D044F7 /* PreviewCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCardView.swift; sourceTree = ""; }; 71 | 0880C66E29E8682B00E088DB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 72 | 0880C67129E86A6500E088DB /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 73 | 0880C67429E8AB9200E088DB /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 74 | 08A2C2742A24C77400B8FA6D /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = ""; }; 75 | 08AE95FE29E3236600F8AFA7 /* TransactionCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCategoryView.swift; sourceTree = ""; }; 76 | 08D38C4F29D465640078C6C4 /* AddTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTransactionView.swift; sourceTree = ""; }; 77 | 08D38C5329D48FE00078C6C4 /* TransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionView.swift; sourceTree = ""; }; 78 | 08F15F192A2A95890009FC0A /* PickerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerCategoryView.swift; sourceTree = ""; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 08558B7F29D35EA40047F959 /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | 08558BAB29D394F40047F959 /* RealmSwift in Frameworks */, 87 | 08558BA929D394F40047F959 /* Realm in Frameworks */, 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 08558B7929D35EA40047F959 = { 95 | isa = PBXGroup; 96 | children = ( 97 | 08558B8429D35EA40047F959 /* iWallet */, 98 | 08558B8329D35EA40047F959 /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 08558B8329D35EA40047F959 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 08558B8229D35EA40047F959 /* iWallet.app */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | 08558B8429D35EA40047F959 /* iWallet */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 08645FEC2A1E84AF0059BDCB /* App */, 114 | 08558B9529D35EC70047F959 /* View */, 115 | 08D1C19D29E0A5E700B58CDE /* ViewModel */, 116 | 08558B9429D35EC00047F959 /* Model */, 117 | 0864598A2A201FCE006C28AB /* Resources */, 118 | 08645FE12A1E81730059BDCB /* Extension */, 119 | 08645FEA2A1E83C90059BDCB /* Other */, 120 | 08558B9329D35EB90047F959 /* Localizable */, 121 | 08558B8929D35EA40047F959 /* Assets.xcassets */, 122 | 08558B8B29D35EA40047F959 /* Preview Content */, 123 | ); 124 | path = iWallet; 125 | sourceTree = ""; 126 | }; 127 | 08558B8B29D35EA40047F959 /* Preview Content */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 08558B8C29D35EA40047F959 /* Preview Assets.xcassets */, 131 | ); 132 | path = "Preview Content"; 133 | sourceTree = ""; 134 | }; 135 | 08558B9329D35EB90047F959 /* Localizable */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 0880C66F29E8682B00E088DB /* Localizable.strings */, 139 | ); 140 | path = Localizable; 141 | sourceTree = ""; 142 | }; 143 | 08558B9429D35EC00047F959 /* Model */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 08645FDB2A1E80460059BDCB /* CategoryModel.swift */, 147 | 08645FDF2A1E80CF0059BDCB /* TransactionModel.swift */, 148 | 08645FDD2A1E808E0059BDCB /* CategoryTypeModel.swift */, 149 | 08645FE42A1E81E60059BDCB /* CurrencyModel.swift */, 150 | 08645FE62A1E824B0059BDCB /* DefaultCategoriesModel.swift */, 151 | ); 152 | path = Model; 153 | sourceTree = ""; 154 | }; 155 | 08558B9529D35EC70047F959 /* View */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 08645FEB2A1E84510059BDCB /* Welcome */, 159 | 08D1C19B29E0A3C000B58CDE /* Home */, 160 | 08D1C19A29E0A39200B58CDE /* Transaction */, 161 | 08D1C19929E0A38900B58CDE /* Category */, 162 | 08D1C19C29E0A3C800B58CDE /* Setting */, 163 | 08558B9629D35ED50047F959 /* Templates */, 164 | ); 165 | path = View; 166 | sourceTree = ""; 167 | }; 168 | 08558B9629D35ED50047F959 /* Templates */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 08558B9729D35F890047F959 /* BalanceView.swift */, 172 | 08558B9F29D381F10047F959 /* ColorPickerView.swift */, 173 | 08558BA129D388080047F959 /* IconPickerView.swift */, 174 | 086A5A1F29E34B0000D044F7 /* PreviewCardView.swift */, 175 | ); 176 | path = Templates; 177 | sourceTree = ""; 178 | }; 179 | 0864598A2A201FCE006C28AB /* Resources */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 086459882A201BB0006C28AB /* Colors.swift */, 183 | 08A2C2742A24C77400B8FA6D /* Icons.swift */, 184 | ); 185 | path = Resources; 186 | sourceTree = ""; 187 | }; 188 | 08645FE12A1E81730059BDCB /* Extension */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 08645FE22A1E817D0059BDCB /* ExtensionFloat.swift */, 192 | ); 193 | path = Extension; 194 | sourceTree = ""; 195 | }; 196 | 08645FEA2A1E83C90059BDCB /* Other */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 08645FE82A1E835D0059BDCB /* PlayFeedbackHaptic.swift */, 200 | ); 201 | path = Other; 202 | sourceTree = ""; 203 | }; 204 | 08645FEB2A1E84510059BDCB /* Welcome */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | 0880C67429E8AB9200E088DB /* WelcomeView.swift */, 208 | ); 209 | path = Welcome; 210 | sourceTree = ""; 211 | }; 212 | 08645FEC2A1E84AF0059BDCB /* App */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 08558B8529D35EA40047F959 /* iWalletApp.swift */, 216 | ); 217 | path = App; 218 | sourceTree = ""; 219 | }; 220 | 08D1C19929E0A38900B58CDE /* Category */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 08558B9B29D37DDC0047F959 /* CategoryView.swift */, 224 | 08558B9D29D380920047F959 /* AddCategoryView.swift */, 225 | 0864598D2A20269C006C28AB /* CategoryItemView.swift */, 226 | 08F15F192A2A95890009FC0A /* PickerCategoryView.swift */, 227 | ); 228 | path = Category; 229 | sourceTree = ""; 230 | }; 231 | 08D1C19A29E0A39200B58CDE /* Transaction */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | 08D38C4F29D465640078C6C4 /* AddTransactionView.swift */, 235 | 08D38C5329D48FE00078C6C4 /* TransactionView.swift */, 236 | 08AE95FE29E3236600F8AFA7 /* TransactionCategoryView.swift */, 237 | 0812FA472A39085A00764C0E /* EditTransactionView.swift */, 238 | ); 239 | path = Transaction; 240 | sourceTree = ""; 241 | }; 242 | 08D1C19B29E0A3C000B58CDE /* Home */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | 08558B8729D35EA40047F959 /* HomeView.swift */, 246 | ); 247 | path = Home; 248 | sourceTree = ""; 249 | }; 250 | 08D1C19C29E0A3C800B58CDE /* Setting */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | 08558B9929D36D6D0047F959 /* SettingView.swift */, 254 | ); 255 | path = Setting; 256 | sourceTree = ""; 257 | }; 258 | 08D1C19D29E0A5E700B58CDE /* ViewModel */ = { 259 | isa = PBXGroup; 260 | children = ( 261 | 08558BA529D394950047F959 /* AppViewModel.swift */, 262 | 08645FED2A1EA7280059BDCB /* CategoryViewModel.swift */, 263 | 08645FEF2A1FBD030059BDCB /* TransactionViewModel.swift */, 264 | ); 265 | path = ViewModel; 266 | sourceTree = ""; 267 | }; 268 | /* End PBXGroup section */ 269 | 270 | /* Begin PBXNativeTarget section */ 271 | 08558B8129D35EA40047F959 /* iWallet */ = { 272 | isa = PBXNativeTarget; 273 | buildConfigurationList = 08558B9029D35EA40047F959 /* Build configuration list for PBXNativeTarget "iWallet" */; 274 | buildPhases = ( 275 | 08558B7E29D35EA40047F959 /* Sources */, 276 | 08558B7F29D35EA40047F959 /* Frameworks */, 277 | 08558B8029D35EA40047F959 /* Resources */, 278 | ); 279 | buildRules = ( 280 | ); 281 | dependencies = ( 282 | ); 283 | name = iWallet; 284 | packageProductDependencies = ( 285 | 08558BA829D394F40047F959 /* Realm */, 286 | 08558BAA29D394F40047F959 /* RealmSwift */, 287 | ); 288 | productName = iWallet; 289 | productReference = 08558B8229D35EA40047F959 /* iWallet.app */; 290 | productType = "com.apple.product-type.application"; 291 | }; 292 | /* End PBXNativeTarget section */ 293 | 294 | /* Begin PBXProject section */ 295 | 08558B7A29D35EA40047F959 /* Project object */ = { 296 | isa = PBXProject; 297 | attributes = { 298 | BuildIndependentTargetsInParallel = 1; 299 | LastSwiftUpdateCheck = 1420; 300 | LastUpgradeCheck = 1430; 301 | TargetAttributes = { 302 | 08558B8129D35EA40047F959 = { 303 | CreatedOnToolsVersion = 14.2; 304 | }; 305 | }; 306 | }; 307 | buildConfigurationList = 08558B7D29D35EA40047F959 /* Build configuration list for PBXProject "iWallet" */; 308 | compatibilityVersion = "Xcode 14.0"; 309 | developmentRegion = en; 310 | hasScannedForEncodings = 0; 311 | knownRegions = ( 312 | Base, 313 | ru, 314 | en, 315 | ); 316 | mainGroup = 08558B7929D35EA40047F959; 317 | packageReferences = ( 318 | 08558BA729D394F40047F959 /* XCRemoteSwiftPackageReference "realm-swift" */, 319 | ); 320 | productRefGroup = 08558B8329D35EA40047F959 /* Products */; 321 | projectDirPath = ""; 322 | projectRoot = ""; 323 | targets = ( 324 | 08558B8129D35EA40047F959 /* iWallet */, 325 | ); 326 | }; 327 | /* End PBXProject section */ 328 | 329 | /* Begin PBXResourcesBuildPhase section */ 330 | 08558B8029D35EA40047F959 /* Resources */ = { 331 | isa = PBXResourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 08558B8D29D35EA40047F959 /* Preview Assets.xcassets in Resources */, 335 | 0880C66D29E8682B00E088DB /* Localizable.strings in Resources */, 336 | 08558B8A29D35EA40047F959 /* Assets.xcassets in Resources */, 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | }; 340 | /* End PBXResourcesBuildPhase section */ 341 | 342 | /* Begin PBXSourcesBuildPhase section */ 343 | 08558B7E29D35EA40047F959 /* Sources */ = { 344 | isa = PBXSourcesBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | 08A2C2752A24C77400B8FA6D /* Icons.swift in Sources */, 348 | 08645FE02A1E80CF0059BDCB /* TransactionModel.swift in Sources */, 349 | 086459892A201BB0006C28AB /* Colors.swift in Sources */, 350 | 08558BA629D394950047F959 /* AppViewModel.swift in Sources */, 351 | 08558B9829D35F890047F959 /* BalanceView.swift in Sources */, 352 | 08645FE92A1E835D0059BDCB /* PlayFeedbackHaptic.swift in Sources */, 353 | 08645FE32A1E817D0059BDCB /* ExtensionFloat.swift in Sources */, 354 | 086A5A2029E34B0000D044F7 /* PreviewCardView.swift in Sources */, 355 | 08AE95FF29E3236600F8AFA7 /* TransactionCategoryView.swift in Sources */, 356 | 08D38C5029D465640078C6C4 /* AddTransactionView.swift in Sources */, 357 | 08558BA029D381F10047F959 /* ColorPickerView.swift in Sources */, 358 | 08F15F1A2A2A95890009FC0A /* PickerCategoryView.swift in Sources */, 359 | 08645FDC2A1E80460059BDCB /* CategoryModel.swift in Sources */, 360 | 08645FF02A1FBD030059BDCB /* TransactionViewModel.swift in Sources */, 361 | 08D38C5429D48FE00078C6C4 /* TransactionView.swift in Sources */, 362 | 08558B9A29D36D6D0047F959 /* SettingView.swift in Sources */, 363 | 0880C67529E8AB9200E088DB /* WelcomeView.swift in Sources */, 364 | 08558B8829D35EA40047F959 /* HomeView.swift in Sources */, 365 | 08645FEE2A1EA7280059BDCB /* CategoryViewModel.swift in Sources */, 366 | 08645FE72A1E824B0059BDCB /* DefaultCategoriesModel.swift in Sources */, 367 | 08645FDE2A1E808E0059BDCB /* CategoryTypeModel.swift in Sources */, 368 | 0864598E2A20269C006C28AB /* CategoryItemView.swift in Sources */, 369 | 08645FE52A1E81E60059BDCB /* CurrencyModel.swift in Sources */, 370 | 0812FA482A39085A00764C0E /* EditTransactionView.swift in Sources */, 371 | 08558B9C29D37DDC0047F959 /* CategoryView.swift in Sources */, 372 | 08558BA229D388080047F959 /* IconPickerView.swift in Sources */, 373 | 08558B8629D35EA40047F959 /* iWalletApp.swift in Sources */, 374 | 08558B9E29D380920047F959 /* AddCategoryView.swift in Sources */, 375 | ); 376 | runOnlyForDeploymentPostprocessing = 0; 377 | }; 378 | /* End PBXSourcesBuildPhase section */ 379 | 380 | /* Begin PBXVariantGroup section */ 381 | 0880C66F29E8682B00E088DB /* Localizable.strings */ = { 382 | isa = PBXVariantGroup; 383 | children = ( 384 | 0880C66E29E8682B00E088DB /* en */, 385 | 0880C67129E86A6500E088DB /* ru */, 386 | ); 387 | name = Localizable.strings; 388 | sourceTree = ""; 389 | }; 390 | /* End PBXVariantGroup section */ 391 | 392 | /* Begin XCBuildConfiguration section */ 393 | 08558B8E29D35EA40047F959 /* Debug */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ALWAYS_SEARCH_USER_PATHS = NO; 397 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 398 | CLANG_ANALYZER_NONNULL = YES; 399 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_ENABLE_OBJC_WEAK = YES; 404 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_COMMA = YES; 407 | CLANG_WARN_CONSTANT_CONVERSION = YES; 408 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 409 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 410 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 411 | CLANG_WARN_EMPTY_BODY = YES; 412 | CLANG_WARN_ENUM_CONVERSION = YES; 413 | CLANG_WARN_INFINITE_RECURSION = YES; 414 | CLANG_WARN_INT_CONVERSION = YES; 415 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 417 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 419 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 420 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 421 | CLANG_WARN_STRICT_PROTOTYPES = YES; 422 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 423 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 424 | CLANG_WARN_UNREACHABLE_CODE = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | COPY_PHASE_STRIP = NO; 427 | DEBUG_INFORMATION_FORMAT = dwarf; 428 | ENABLE_STRICT_OBJC_MSGSEND = YES; 429 | ENABLE_TESTABILITY = YES; 430 | GCC_C_LANGUAGE_STANDARD = gnu11; 431 | GCC_DYNAMIC_NO_PIC = NO; 432 | GCC_NO_COMMON_BLOCKS = YES; 433 | GCC_OPTIMIZATION_LEVEL = 0; 434 | GCC_PREPROCESSOR_DEFINITIONS = ( 435 | "DEBUG=1", 436 | "$(inherited)", 437 | ); 438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 440 | GCC_WARN_UNDECLARED_SELECTOR = YES; 441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 442 | GCC_WARN_UNUSED_FUNCTION = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 445 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 446 | MTL_FAST_MATH = YES; 447 | ONLY_ACTIVE_ARCH = YES; 448 | SDKROOT = iphoneos; 449 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 450 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 451 | }; 452 | name = Debug; 453 | }; 454 | 08558B8F29D35EA40047F959 /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | ALWAYS_SEARCH_USER_PATHS = NO; 458 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 459 | CLANG_ANALYZER_NONNULL = YES; 460 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 462 | CLANG_ENABLE_MODULES = YES; 463 | CLANG_ENABLE_OBJC_ARC = YES; 464 | CLANG_ENABLE_OBJC_WEAK = YES; 465 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 466 | CLANG_WARN_BOOL_CONVERSION = YES; 467 | CLANG_WARN_COMMA = YES; 468 | CLANG_WARN_CONSTANT_CONVERSION = YES; 469 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 470 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 471 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 472 | CLANG_WARN_EMPTY_BODY = YES; 473 | CLANG_WARN_ENUM_CONVERSION = YES; 474 | CLANG_WARN_INFINITE_RECURSION = YES; 475 | CLANG_WARN_INT_CONVERSION = YES; 476 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 477 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 478 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 479 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 480 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 481 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 482 | CLANG_WARN_STRICT_PROTOTYPES = YES; 483 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 484 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 485 | CLANG_WARN_UNREACHABLE_CODE = YES; 486 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 487 | COPY_PHASE_STRIP = NO; 488 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 489 | ENABLE_NS_ASSERTIONS = NO; 490 | ENABLE_STRICT_OBJC_MSGSEND = YES; 491 | GCC_C_LANGUAGE_STANDARD = gnu11; 492 | GCC_NO_COMMON_BLOCKS = YES; 493 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 494 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 495 | GCC_WARN_UNDECLARED_SELECTOR = YES; 496 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 497 | GCC_WARN_UNUSED_FUNCTION = YES; 498 | GCC_WARN_UNUSED_VARIABLE = YES; 499 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 500 | MTL_ENABLE_DEBUG_INFO = NO; 501 | MTL_FAST_MATH = YES; 502 | SDKROOT = iphoneos; 503 | SWIFT_COMPILATION_MODE = wholemodule; 504 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 505 | VALIDATE_PRODUCT = YES; 506 | }; 507 | name = Release; 508 | }; 509 | 08558B9129D35EA40047F959 /* Debug */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 513 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 514 | CODE_SIGN_STYLE = Automatic; 515 | CURRENT_PROJECT_VERSION = 1; 516 | DEVELOPMENT_ASSET_PATHS = "\"iWallet/Preview Content\""; 517 | DEVELOPMENT_TEAM = 4243BL8MP3; 518 | ENABLE_PREVIEWS = YES; 519 | GENERATE_INFOPLIST_FILE = YES; 520 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; 521 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 522 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 523 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 524 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 525 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 526 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 527 | LD_RUNPATH_SEARCH_PATHS = ( 528 | "$(inherited)", 529 | "@executable_path/Frameworks", 530 | ); 531 | MARKETING_VERSION = 1.1.8; 532 | PRODUCT_BUNDLE_IDENTIFIER = com.idevnva.iwallet; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 535 | SUPPORTS_MACCATALYST = NO; 536 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 537 | SWIFT_EMIT_LOC_STRINGS = YES; 538 | SWIFT_VERSION = 5.0; 539 | TARGETED_DEVICE_FAMILY = 1; 540 | }; 541 | name = Debug; 542 | }; 543 | 08558B9229D35EA40047F959 /* Release */ = { 544 | isa = XCBuildConfiguration; 545 | buildSettings = { 546 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 547 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 548 | CODE_SIGN_STYLE = Automatic; 549 | CURRENT_PROJECT_VERSION = 1; 550 | DEVELOPMENT_ASSET_PATHS = "\"iWallet/Preview Content\""; 551 | DEVELOPMENT_TEAM = 4243BL8MP3; 552 | ENABLE_PREVIEWS = YES; 553 | GENERATE_INFOPLIST_FILE = YES; 554 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; 555 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 556 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 557 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 558 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 559 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 560 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 561 | LD_RUNPATH_SEARCH_PATHS = ( 562 | "$(inherited)", 563 | "@executable_path/Frameworks", 564 | ); 565 | MARKETING_VERSION = 1.1.8; 566 | PRODUCT_BUNDLE_IDENTIFIER = com.idevnva.iwallet; 567 | PRODUCT_NAME = "$(TARGET_NAME)"; 568 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 569 | SUPPORTS_MACCATALYST = NO; 570 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 571 | SWIFT_EMIT_LOC_STRINGS = YES; 572 | SWIFT_VERSION = 5.0; 573 | TARGETED_DEVICE_FAMILY = 1; 574 | }; 575 | name = Release; 576 | }; 577 | /* End XCBuildConfiguration section */ 578 | 579 | /* Begin XCConfigurationList section */ 580 | 08558B7D29D35EA40047F959 /* Build configuration list for PBXProject "iWallet" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 08558B8E29D35EA40047F959 /* Debug */, 584 | 08558B8F29D35EA40047F959 /* Release */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | 08558B9029D35EA40047F959 /* Build configuration list for PBXNativeTarget "iWallet" */ = { 590 | isa = XCConfigurationList; 591 | buildConfigurations = ( 592 | 08558B9129D35EA40047F959 /* Debug */, 593 | 08558B9229D35EA40047F959 /* Release */, 594 | ); 595 | defaultConfigurationIsVisible = 0; 596 | defaultConfigurationName = Release; 597 | }; 598 | /* End XCConfigurationList section */ 599 | 600 | /* Begin XCRemoteSwiftPackageReference section */ 601 | 08558BA729D394F40047F959 /* XCRemoteSwiftPackageReference "realm-swift" */ = { 602 | isa = XCRemoteSwiftPackageReference; 603 | repositoryURL = "https://github.com/realm/realm-swift.git"; 604 | requirement = { 605 | branch = master; 606 | kind = branch; 607 | }; 608 | }; 609 | /* End XCRemoteSwiftPackageReference section */ 610 | 611 | /* Begin XCSwiftPackageProductDependency section */ 612 | 08558BA829D394F40047F959 /* Realm */ = { 613 | isa = XCSwiftPackageProductDependency; 614 | package = 08558BA729D394F40047F959 /* XCRemoteSwiftPackageReference "realm-swift" */; 615 | productName = Realm; 616 | }; 617 | 08558BAA29D394F40047F959 /* RealmSwift */ = { 618 | isa = XCSwiftPackageProductDependency; 619 | package = 08558BA729D394F40047F959 /* XCRemoteSwiftPackageReference "realm-swift" */; 620 | productName = RealmSwift; 621 | }; 622 | /* End XCSwiftPackageProductDependency section */ 623 | }; 624 | rootObject = 08558B7A29D35EA40047F959 /* Project object */; 625 | } 626 | --------------------------------------------------------------------------------