├── Lannister ├── Assets.xcassets │ ├── Contents.json │ ├── logo.imageset │ │ ├── logo.png │ │ ├── logo@2x.png │ │ ├── logo@3x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── AppIcon-2.png │ │ ├── AppIcon-3.png │ │ ├── AppIcon.png │ │ ├── AppIcon copy-1.png │ │ ├── AppIcon copy-2.png │ │ ├── AppIcon copy-3.png │ │ ├── AppIcon copy-4.png │ │ ├── AppIcon copy-5.png │ │ ├── AppIcon copy-6.png │ │ ├── AppIcon copy-7.png │ │ ├── AppIcon copy-8.png │ │ ├── AppIcon copy-9.png │ │ ├── AppIcon copy.png │ │ ├── AppIcon copy-10.png │ │ ├── AppIcon copy-11.png │ │ ├── AppIcon copy-12.png │ │ ├── AppIcon copy-13.png │ │ ├── AppIcon copy-14.png │ │ └── Contents.json │ ├── arrow-up.imageset │ │ ├── arrow-up.png │ │ ├── arrow-up@2x.png │ │ ├── arrow-up@3x.png │ │ └── Contents.json │ ├── checkmark.imageset │ │ ├── checkmark.png │ │ ├── checkmark@2x.png │ │ ├── checkmark@3x.png │ │ └── Contents.json │ ├── donations.imageset │ │ ├── donations.png │ │ ├── donations@2x.png │ │ ├── donations@3x.png │ │ └── Contents.json │ ├── hand-down.imageset │ │ ├── hand-down.png │ │ ├── hand-down@2x.png │ │ ├── hand-down@3x.png │ │ └── Contents.json │ ├── settings.imageset │ │ ├── settings.png │ │ ├── settings@2x.png │ │ ├── settings@3x.png │ │ └── Contents.json │ ├── arrow-down.imageset │ │ ├── arrow-down.png │ │ ├── arrow-down@2x.png │ │ ├── arrow-down@3x.png │ │ └── Contents.json │ ├── arrow-sort.imageset │ │ ├── arrow-sort.png │ │ ├── arrow-sort@2x.png │ │ ├── arrow-sort@3x.png │ │ └── Contents.json │ ├── scan-square.imageset │ │ ├── scan-square.png │ │ ├── scan-square@2x.png │ │ ├── scan-square@3x.png │ │ └── Contents.json │ ├── cell-dropdown.imageset │ │ ├── cell-dropdown.png │ │ ├── cell-dropdown@2x.png │ │ ├── cell-dropdown@3x.png │ │ └── Contents.json │ ├── cell-eth-scan.imageset │ │ ├── cell-eth-scan.png │ │ ├── cell-eth-scan@2x.png │ │ ├── cell-eth-scan@3x.png │ │ └── Contents.json │ ├── cell-indicator.imageset │ │ ├── cell-indicator.png │ │ ├── cell-indicator@2x.png │ │ ├── cell-indicator@3x.png │ │ └── Contents.json │ ├── settings-sync.imageset │ │ ├── settings-sync.png │ │ ├── settings-sync@2x.png │ │ ├── settings-sync@3x.png │ │ └── Contents.json │ ├── settings-export.imageset │ │ ├── settings-export.png │ │ ├── settings-export@2x.png │ │ ├── settings-export@3x.png │ │ └── Contents.json │ ├── settings-github.imageset │ │ ├── settings-github.png │ │ ├── settings-github@2x.png │ │ ├── settings-github@3x.png │ │ └── Contents.json │ ├── settings-discord.imageset │ │ ├── settings-discord.png │ │ ├── settings-discord@2x.png │ │ ├── settings-discord@3x.png │ │ └── Contents.json │ ├── settings-twitter.imageset │ │ ├── settings-twitter.png │ │ ├── settings-twitter@2x.png │ │ ├── settings-twitter@3x.png │ │ └── Contents.json │ ├── scan-close-button.imageset │ │ ├── scan-close-button.png │ │ ├── scan-close-button@2x.png │ │ ├── scan-close-button@3x.png │ │ └── Contents.json │ ├── settings-currency.imageset │ │ ├── settings-currency.png │ │ ├── settings-currency@2x.png │ │ ├── settings-currency@3x.png │ │ └── Contents.json │ ├── settings-passcode.imageset │ │ ├── settings-passcode.png │ │ ├── settings-passcode@2x.png │ │ ├── settings-passcode@3x.png │ │ └── Contents.json │ ├── cell-eth-clipboard.imageset │ │ ├── cell-eth-clipboard.png │ │ ├── cell-eth-clipboard@2x.png │ │ ├── cell-eth-clipboard@3x.png │ │ └── Contents.json │ └── settings-fingerprint.imageset │ │ ├── settings-fingerprint.png │ │ ├── settings-fingerprint@2x.png │ │ ├── settings-fingerprint@3x.png │ │ └── Contents.json ├── Data │ ├── Database │ │ ├── Models │ │ │ └── Generated │ │ │ │ ├── CurrencyManagedObject.swift │ │ │ │ ├── _HoldingManagedObject.swift │ │ │ │ ├── _TransactionManagedObject.swift │ │ │ │ ├── __TransactionManagedObject.swift │ │ │ │ ├── _CurrencyManagedObject.swift │ │ │ │ └── __HoldingManagedObject.swift │ │ └── db.xcdatamodeld │ │ │ ├── .xccurrentversion │ │ │ ├── db 2.xcdatamodel │ │ │ └── contents │ │ │ └── db.xcdatamodel │ │ │ └── contents │ ├── Utils │ │ └── ERC20ContractsList.swift │ ├── API │ │ ├── Services │ │ │ ├── BaseApiService.swift │ │ │ ├── EtherScanApiService.swift │ │ │ ├── CurrencyApiService.swift │ │ │ └── BlockstackApiService.swift │ │ └── APIManager.swift │ ├── Mappers │ │ ├── CurrencyDto.swift │ │ ├── TransactionDto.swift │ │ ├── TokenDto.swift │ │ └── HoldingDto.swift │ ├── Repositories Implementations │ │ ├── PortfolioRepositoryImpl.swift │ │ ├── HoldingsRepositoryImpl.swift │ │ └── WalletRepositoryImpl.swift │ └── User defaults │ │ └── CurrencyUserDefaults.swift ├── Domain │ ├── Repositories │ │ ├── Repository.swift │ │ ├── PortfolioRepository.swift │ │ ├── HoldingsRepository.swift │ │ └── WalletRepository.swift │ ├── Entities │ │ ├── Currency.swift │ │ ├── Transaction.swift │ │ ├── Token.swift │ │ └── Holding.swift │ └── Use cases │ │ ├── PortfolioUseCase.swift │ │ ├── HoldingsUseCase.swift │ │ └── WalletUseCase.swift ├── App │ ├── Views │ │ ├── ETHCell.swift │ │ ├── HoldingNameCell.swift │ │ ├── HoldingHeaderView.swift │ │ ├── TransactionTypeCell.swift │ │ ├── TransactionNameCell.swift │ │ ├── ColorCodeCell.swift │ │ ├── HoldingTopCell.swift │ │ ├── CurrencyCell.swift │ │ ├── DashboardTopCell.swift │ │ ├── DonationCell.swift │ │ ├── TransactionCell.swift │ │ ├── ColorCodePresetCell.swift │ │ ├── ColorCodeCustomCell.swift │ │ ├── HoldingCell.swift │ │ ├── DashboardHeaderView.swift │ │ ├── SettingsCell.swift │ │ └── TotalValueCell.swift │ ├── Controllers │ │ ├── LaunchController.swift │ │ ├── RootController.swift │ │ ├── CustomQRCodeController.swift │ │ ├── CurrenciesController.swift │ │ ├── DonationsController.swift │ │ ├── AuthController.swift │ │ ├── ETHCreateHoldingController.swift │ │ ├── ColorCodesController.swift │ │ ├── CreateTransactionController.swift │ │ └── HoldingController.swift │ └── Utilities │ │ ├── NumberFormatter.swift │ │ ├── Parser.swift │ │ ├── Currencies.swift │ │ ├── Colors.swift │ │ └── BioAccessController.swift ├── Info.plist ├── Info-dev.plist └── Base.lproj │ └── LaunchScreen.storyboard ├── Lannister.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── andresousa.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── xcshareddata │ └── xcschemes │ └── Lannister Beta.xcscheme ├── .gitignore ├── LannisterTests ├── Info.plist └── LannisterTests.swift ├── LannisterUITests ├── Info.plist └── LannisterUITests.swift ├── Podfile ├── README.md └── Podfile.lock /Lannister/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/logo.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/logo.imageset/logo.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/logo.imageset/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/logo.imageset/logo@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/logo.imageset/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/logo.imageset/logo@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon-2.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon-3.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-up.imageset/arrow-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-up.imageset/arrow-up.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/checkmark.imageset/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/checkmark.imageset/checkmark.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/donations.imageset/donations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/donations.imageset/donations.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/hand-down.imageset/hand-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/hand-down.imageset/hand-down.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings.imageset/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings.imageset/settings.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-down.imageset/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-down.imageset/arrow-down.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-sort.imageset/arrow-sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-sort.imageset/arrow-sort.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-up.imageset/arrow-up@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-up.imageset/arrow-up@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-up.imageset/arrow-up@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-up.imageset/arrow-up@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings.imageset/settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings.imageset/settings@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings.imageset/settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings.imageset/settings@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-1.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-2.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-3.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-4.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-5.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-6.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-7.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-8.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-9.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-down.imageset/arrow-down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-down.imageset/arrow-down@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-down.imageset/arrow-down@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-down.imageset/arrow-down@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-sort.imageset/arrow-sort@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-sort.imageset/arrow-sort@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-sort.imageset/arrow-sort@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/arrow-sort.imageset/arrow-sort@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/checkmark.imageset/checkmark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/checkmark.imageset/checkmark@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/checkmark.imageset/checkmark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/checkmark.imageset/checkmark@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/donations.imageset/donations@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/donations.imageset/donations@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/donations.imageset/donations@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/donations.imageset/donations@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/hand-down.imageset/hand-down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/hand-down.imageset/hand-down@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/hand-down.imageset/hand-down@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/hand-down.imageset/hand-down@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-square.imageset/scan-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/scan-square.imageset/scan-square.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-10.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-11.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-12.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-13.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/AppIcon.appiconset/AppIcon copy-14.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-square.imageset/scan-square@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/scan-square.imageset/scan-square@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-square.imageset/scan-square@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/scan-square.imageset/scan-square@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-dropdown.imageset/cell-dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-dropdown.imageset/cell-dropdown.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-scan.imageset/cell-eth-scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-eth-scan.imageset/cell-eth-scan.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-indicator.imageset/cell-indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-indicator.imageset/cell-indicator.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-sync.imageset/settings-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-sync.imageset/settings-sync.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-dropdown.imageset/cell-dropdown@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-dropdown.imageset/cell-dropdown@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-dropdown.imageset/cell-dropdown@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-dropdown.imageset/cell-dropdown@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-scan.imageset/cell-eth-scan@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-eth-scan.imageset/cell-eth-scan@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-scan.imageset/cell-eth-scan@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-eth-scan.imageset/cell-eth-scan@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-export.imageset/settings-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-export.imageset/settings-export.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-github.imageset/settings-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-github.imageset/settings-github.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-sync.imageset/settings-sync@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-sync.imageset/settings-sync@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-sync.imageset/settings-sync@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-sync.imageset/settings-sync@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-indicator.imageset/cell-indicator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-indicator.imageset/cell-indicator@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-indicator.imageset/cell-indicator@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-indicator.imageset/cell-indicator@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-discord.imageset/settings-discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-discord.imageset/settings-discord.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-export.imageset/settings-export@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-export.imageset/settings-export@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-export.imageset/settings-export@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-export.imageset/settings-export@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-github.imageset/settings-github@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-github.imageset/settings-github@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-github.imageset/settings-github@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-github.imageset/settings-github@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-twitter.imageset/settings-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-twitter.imageset/settings-twitter.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-close-button.imageset/scan-close-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/scan-close-button.imageset/scan-close-button.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-currency.imageset/settings-currency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-currency.imageset/settings-currency.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-discord.imageset/settings-discord@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-discord.imageset/settings-discord@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-discord.imageset/settings-discord@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-discord.imageset/settings-discord@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-passcode.imageset/settings-passcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-passcode.imageset/settings-passcode.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-twitter.imageset/settings-twitter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-twitter.imageset/settings-twitter@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-twitter.imageset/settings-twitter@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-twitter.imageset/settings-twitter@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-clipboard.imageset/cell-eth-clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-eth-clipboard.imageset/cell-eth-clipboard.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-close-button.imageset/scan-close-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/scan-close-button.imageset/scan-close-button@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-close-button.imageset/scan-close-button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/scan-close-button.imageset/scan-close-button@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-currency.imageset/settings-currency@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-currency.imageset/settings-currency@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-currency.imageset/settings-currency@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-currency.imageset/settings-currency@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-passcode.imageset/settings-passcode@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-passcode.imageset/settings-passcode@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-passcode.imageset/settings-passcode@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-passcode.imageset/settings-passcode@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-clipboard.imageset/cell-eth-clipboard@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-eth-clipboard.imageset/cell-eth-clipboard@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-clipboard.imageset/cell-eth-clipboard@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/cell-eth-clipboard.imageset/cell-eth-clipboard@3x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-fingerprint.imageset/settings-fingerprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-fingerprint.imageset/settings-fingerprint.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-fingerprint.imageset/settings-fingerprint@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-fingerprint.imageset/settings-fingerprint@2x.png -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-fingerprint.imageset/settings-fingerprint@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lannister-capital/lannister-ios/HEAD/Lannister/Assets.xcassets/settings-fingerprint.imageset/settings-fingerprint@3x.png -------------------------------------------------------------------------------- /Lannister/Data/Database/Models/Generated/CurrencyManagedObject.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(CurrencyManagedObject) 4 | open class CurrencyManagedObject: _CurrencyManagedObject { 5 | // Custom logic goes here. 6 | } 7 | -------------------------------------------------------------------------------- /Lannister/Data/Database/Models/Generated/_HoldingManagedObject.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(.HoldingManagedObject) 4 | open class .HoldingManagedObject: _.HoldingManagedObject { 5 | // Custom logic goes here. 6 | } 7 | -------------------------------------------------------------------------------- /Lannister.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lannister/Data/Database/Models/Generated/_TransactionManagedObject.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(.TransactionManagedObject) 4 | open class .TransactionManagedObject: _.TransactionManagedObject { 5 | // Custom logic goes here. 6 | } 7 | -------------------------------------------------------------------------------- /Lannister/Domain/Repositories/Repository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Repository.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 24/06/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Repository { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Lannister.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Lannister/Data/Database/db.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | db 2.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Lannister/App/Views/ETHCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ETHCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ETHCell: UITableViewCell { 12 | 13 | @IBOutlet weak var addressLabel : UILabel! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Lannister/App/Views/HoldingNameCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingNameCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 29/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HoldingNameCell: UITableViewCell { 12 | 13 | @IBOutlet weak var holdingNameTextField : UITextField! 14 | } 15 | -------------------------------------------------------------------------------- /Lannister/Domain/Repositories/PortfolioRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PortfolioRepository.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 24/06/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol PortfolioRepository : Repository { 12 | 13 | func getEuroTotalValue() -> Double 14 | } 15 | -------------------------------------------------------------------------------- /Lannister/App/Views/HoldingHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingHeaderView.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 01/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HoldingHeaderView: UICollectionReusableView { 12 | 13 | @IBOutlet weak var valueLabel : UILabel! 14 | } 15 | -------------------------------------------------------------------------------- /Lannister/App/Views/TransactionTypeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransactionTypeCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransactionTypeCell: UITableViewCell { 12 | 13 | @IBOutlet weak var transactionTypeTextField : UITextField! 14 | } 15 | -------------------------------------------------------------------------------- /Lannister/App/Views/TransactionNameCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransactionNameCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 06/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransactionNameCell: UITableViewCell { 12 | 13 | @IBOutlet weak var transactionNameTextField : UITextField! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Lannister/App/Views/ColorCodeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCodeCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 29/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ColorCodeCell: UITableViewCell { 12 | 13 | @IBOutlet weak var colorCodeView : UIView! 14 | @IBOutlet weak var colorCodeLabel : UILabel! 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/App/Views/HoldingTopCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingTopCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 14/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HoldingTopCell: UITableViewCell { 12 | 13 | @IBOutlet weak var addButton : UIButton! 14 | @IBOutlet weak var poweredByLabel : UILabel! 15 | } 16 | -------------------------------------------------------------------------------- /Lannister/App/Views/CurrencyCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyCell.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 09/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CurrencyCell: UITableViewCell { 12 | 13 | @IBOutlet weak var currencyNameLabel : UILabel! 14 | @IBOutlet weak var currencySymbolLabel : UILabel! 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/Data/Utils/ERC20ContractsList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ERC20List.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 11/11/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum ERC20ContractsList : String { 12 | case dai = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" 13 | case cDai = "0xf5dce57282a584d2746faf1593d3121fcac444dc" 14 | } 15 | -------------------------------------------------------------------------------- /Lannister/App/Views/DashboardTopCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DashboardTopCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 11/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DashboardTopCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var sortButton : UIButton! 14 | @IBOutlet weak var addButton : UIButton! 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/App/Views/DonationCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DonationCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 14/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DonationCell: UITableViewCell { 12 | 13 | @IBOutlet weak var currencyLabel : UILabel! 14 | @IBOutlet weak var addressLabel : UILabel! 15 | @IBOutlet weak var copyLabel : UILabel! 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/App/Views/TransactionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransactionCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 01/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransactionCell: UITableViewCell { 12 | 13 | @IBOutlet weak var colorView : UIView! 14 | @IBOutlet weak var nameLabel : UILabel! 15 | @IBOutlet weak var valueLabel : UILabel! 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/App/Views/ColorCodePresetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCodePresetCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 01/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ColorCodePresetCell: UITableViewCell { 12 | 13 | @IBOutlet weak var colorCodeView : UIView! 14 | @IBOutlet weak var colorCodeLabel : UILabel! 15 | @IBOutlet weak var checkmarkImageView : UIImageView! 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/Domain/Repositories/HoldingsRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingsRepository.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 18/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol HoldingsRepository: Repository { 12 | 13 | func updateHoldingsWithComputedProperties(holdings: [Holding]) -> [Holding] 14 | func updateHoldingWithComputedProperties(holding: Holding) -> Holding 15 | } 16 | -------------------------------------------------------------------------------- /Lannister/App/Views/ColorCodeCustomCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCodeCustomCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 01/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ColorCodeCustomCell: UITableViewCell { 12 | 13 | @IBOutlet weak var colorCodeView : UIView! 14 | @IBOutlet weak var colorCodeTextField : UITextField! 15 | @IBOutlet weak var checkmarkImageView : UIImageView! 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/App/Views/HoldingCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 27/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HoldingCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var colorView : UIView! 14 | @IBOutlet weak var nameLabel : UILabel! 15 | @IBOutlet weak var valueLabel : UILabel! 16 | @IBOutlet weak var percentageLabel : UILabel! 17 | } 18 | -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "logo@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "logo@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Data/API/Services/BaseApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseApiService.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | import CoreData 12 | import Groot 13 | 14 | class BaseApiService: NSObject { 15 | 16 | let currencyBaseUrl = "https://api.exchangeratesapi.io" 17 | let currencyCryptoBaseUrl = "https://api.cryptonator.com/api/ticker" 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Lannister/Domain/Entities/Currency.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Currency.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Currency { 12 | 13 | var code : String! 14 | var name : String! 15 | var symbol : String! 16 | var euroRate : Double! 17 | var holdings : [Holding]! 18 | 19 | init(with dictionary: [String : Any]?) { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Lannister/App/Views/DashboardHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DashboardHeaderView.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 27/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Charts 11 | 12 | class DashboardHeaderView: UICollectionReusableView { 13 | 14 | @IBOutlet weak var numberOfHoldingsLabel : UILabel! 15 | @IBOutlet weak var totalValueLabel : UILabel! 16 | @IBOutlet weak var pieChartView : PieChartView! 17 | } 18 | -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-up.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "arrow-up@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "arrow-up@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/checkmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "checkmark.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "checkmark@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "checkmark@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/donations.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "donations.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "donations@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "donations@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/hand-down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "hand-down.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "hand-down@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "hand-down@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-down.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "arrow-down@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "arrow-down@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/arrow-sort.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-sort.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "arrow-sort@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "arrow-sort@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-square.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "scan-square.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "scan-square@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "scan-square@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-dropdown.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cell-dropdown.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cell-dropdown@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "cell-dropdown@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-scan.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cell-eth-scan.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cell-eth-scan@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "cell-eth-scan@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-indicator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cell-indicator.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cell-indicator@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "cell-indicator@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-sync.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-sync.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-sync@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-sync@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-export.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-export.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-export@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-export@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-github.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-github.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-github@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-github@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-discord.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-discord.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-discord@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-discord@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-twitter.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-twitter@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-twitter@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/scan-close-button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "scan-close-button.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "scan-close-button@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "scan-close-button@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-currency.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-currency.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-currency@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-currency@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-passcode.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-passcode.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-passcode@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-passcode@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/Domain/Entities/Transaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transaction.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 05/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Transaction { 12 | 13 | var identifier : String! 14 | var name : String! 15 | var type : String! 16 | var value : Double! 17 | var holding : Holding? 18 | var token : Token? 19 | 20 | init(with dictionary: [String : Any]?) { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/cell-eth-clipboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cell-eth-clipboard.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cell-eth-clipboard@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "cell-eth-clipboard@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister/App/Views/SettingsCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 02/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SettingsCell: UITableViewCell { 12 | 13 | @IBOutlet weak var logo : UIImageView! 14 | @IBOutlet weak var nameLabel : UILabel! 15 | @IBOutlet weak var cellIndicator : UIImageView! 16 | @IBOutlet weak var currencyLabel : UILabel! 17 | @IBOutlet weak var bioAccessSwitch : UISwitch! 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/settings-fingerprint.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-fingerprint.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings-fingerprint@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings-fingerprint@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Lannister.xcodeproj/xcuserdata/andresousa.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Lannister Beta.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | Lannister.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 27 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Lannister/Domain/Entities/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 15/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Token { 12 | 13 | // attributes 14 | var address : String! 15 | var name : String? 16 | var code : String? 17 | var value : Double! 18 | 19 | var currency : Currency! 20 | var transactions : [Transaction]? 21 | 22 | // relationships 23 | init(with dictionary: [String : Any]?) { 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Lannister/Domain/Use cases/PortfolioUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PortfolioUseCase.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 24/06/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class PortfolioUseCase : NSObject { 12 | 13 | var repository: Repository? 14 | 15 | convenience init(with repository: Repository) { 16 | self.init() 17 | self.repository = repository 18 | } 19 | 20 | func getEuroTotalValue() -> Double { 21 | 22 | let repo = self.repository as! PortfolioRepository 23 | return repo.getEuroTotalValue() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Lannister/App/Views/TotalValueCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TotalValueCell.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 29/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TotalValueCell: UITableViewCell { 12 | 13 | @IBOutlet weak var titleLabel : UILabel! 14 | @IBOutlet weak var totalValueTextField : UITextField! 15 | @IBOutlet weak var percentageLabel : UILabel! 16 | @IBOutlet weak var currencyLabel : UILabel! 17 | @IBOutlet weak var changeLabel : UILabel! 18 | @IBOutlet weak var cellIndicatorImageView : UIImageView! 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | ## User settings 4 | xcuserdata 5 | 6 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 7 | *.xcscmblueprint 8 | *.xccheckout 9 | 10 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 11 | build/ 12 | DerivedData/ 13 | *.xcworkspace 14 | *.xcuserstate 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1 19 | *.mode1v3 20 | *.mode2v3 21 | !default.mode1v3 22 | *.mode2v3 23 | !default.mode2v3 24 | *.perspective 25 | *.perspectivev3 26 | !default.perspectivev3 27 | 28 | # Exclude OS X folder attributes 29 | .DS_Store 30 | 31 | # Exclude temp nibs and swap files 32 | *~.nib 33 | *.swp 34 | 35 | Pods/ -------------------------------------------------------------------------------- /Lannister/Data/API/APIManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIManager.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 17/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | 12 | class APIManager: SessionManager { 13 | 14 | class var sharedManager: APIManager { 15 | struct Static { 16 | static var sharedInstance = APIManager(configuration: URLSessionConfiguration.default, 17 | delegate: SessionDelegate.init(), 18 | serverTrustPolicyManager: nil) 19 | } 20 | return Static.sharedInstance 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Lannister/Domain/Repositories/WalletRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WalletRepository.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 12/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol WalletRepository : Repository { 12 | 13 | func getBalance(address: String, success: @escaping(Double) -> Void, failure: @escaping(_ error: Error) -> Void) 14 | func getBalanceOfToken(address: String, erc20TokenAddress: String, success: @escaping(Double) -> Void, failure: @escaping(_ error: Error) -> Void) 15 | func getTransactions(address: String, success: @escaping(_ transactions: Array?) -> Void, failure: @escaping(_ error: Error) -> Void) 16 | } 17 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/LaunchController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LaunchController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | if (UserDefaults.standard.object(forKey: "Auth") == nil) { 17 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { 18 | AppDelegate.shared.rootViewController.switchToAuthScreen() 19 | }) 20 | } else { 21 | AppDelegate.shared.rootViewController.switchToMainScreen() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Lannister/App/Utilities/NumberFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberFormatter.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 09/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | static let numberFormatter = NumberFormatter() 13 | var doubleValue: Double? { 14 | String.numberFormatter.decimalSeparator = "." 15 | if let result = String.numberFormatter.number(from: self) { 16 | return result.doubleValue 17 | } else { 18 | String.numberFormatter.decimalSeparator = "," 19 | if let result = String.numberFormatter.number(from: self) { 20 | return result.doubleValue 21 | } 22 | } 23 | return nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LannisterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Lannister/Domain/Entities/Holding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Holding.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 05/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Holding { 12 | 13 | // attributes 14 | var address : String? 15 | var hexColor : String! 16 | var name : String! 17 | var value : Double? 18 | 19 | // relationships 20 | var currency : Currency? 21 | var transactions : [Transaction]? 22 | 23 | // computed properties 24 | var representiveValue : Double? 25 | var representiveCurrency : Currency? 26 | var totalEuroValue : Double? 27 | 28 | init(with dictionary: [String : Any]?) { 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LannisterUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Lannister/Data/API/Services/EtherScanApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EtherScanApiService.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 17/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | 12 | class EtherScanApiService: NSObject { 13 | 14 | internal typealias Response = DataResponse? 15 | 16 | var sharedManager = APIManager.sharedManager 17 | 18 | let baseURL = "https://api.etherscan.io/api" 19 | let apiKey = "GP7SEQH5PPAX47AGETTCU3ZHCMD3WRUP3F" 20 | 21 | func getTransactions(params: Parameters?, returns: @escaping (Response) -> Void) { 22 | sharedManager.request("\(baseURL)", method: .get, parameters: params).validate().responseJSON { response in returns(response) } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '11.0' 3 | 4 | def all_pods 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | pod 'Alamofire', '4.8.0' 8 | pod 'Blockstack', '1.0.1' 9 | pod 'Charts' 10 | pod 'Groot', '3.0.1' 11 | pod 'MagicalRecord', :git => 'https://github.com/magicalpanda/MagicalRecord.git', :tag => 'v2.3.3' 12 | pod 'BiometricAuthentication' 13 | pod 'SVProgressHUD' 14 | pod 'QRCodeReader.swift', '~> 10.0.0' 15 | pod 'web3swift' 16 | end 17 | 18 | target 'Lannister' do 19 | all_pods 20 | end 21 | 22 | target 'Lannister Beta' do 23 | all_pods 24 | end 25 | 26 | target 'LannisterTests' do 27 | inherit! :search_paths 28 | # Pods for testing 29 | end 30 | 31 | target 'LannisterUITests' do 32 | inherit! :search_paths 33 | # Pods for testing 34 | end -------------------------------------------------------------------------------- /Lannister/App/Utilities/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 29/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | 13 | var parseJSONString: AnyObject? { 14 | 15 | let data = self.data(using: String.Encoding.utf8, allowLossyConversion: false) 16 | 17 | if let jsonData = data { 18 | // Will return an object or nil if JSON decoding fails 19 | do { 20 | return try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject 21 | } catch { 22 | print(error) 23 | return nil 24 | } 25 | } else { 26 | // Lossless conversion of the string was not possible 27 | return nil 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Lannister/Domain/Use cases/HoldingsUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingUseCase.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 18/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HoldingsUseCase: NSObject { 12 | 13 | var repository: Repository? 14 | 15 | convenience init(with repository: Repository) { 16 | self.init() 17 | self.repository = repository 18 | } 19 | 20 | func updateHoldingsWithComputedProperties(holdings: [Holding]) -> [Holding] { 21 | 22 | let repo = self.repository as! HoldingsRepository 23 | return repo.updateHoldingsWithComputedProperties(holdings: holdings) 24 | } 25 | 26 | func updateHoldingWithComputedProperties(holding: Holding) -> Holding { 27 | 28 | let repo = self.repository as! HoldingsRepository 29 | return repo.updateHoldingWithComputedProperties(holding: holding) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Lannister/Data/Mappers/CurrencyDto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyDto.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CurrencyDto: NSObject { 12 | 13 | func currency(from managedObject: CurrencyManagedObject) -> Currency { 14 | 15 | var currency = Currency(with: nil) 16 | currency.code = managedObject.code 17 | currency.name = managedObject.name 18 | currency.euroRate = managedObject.euro_rate 19 | currency.symbol = managedObject.symbol 20 | return currency 21 | } 22 | 23 | func currencies(from managedObjects: [CurrencyManagedObject]) -> [Currency] { 24 | 25 | var currencies = Array() 26 | for managedObject in managedObjects { 27 | currencies.append(currency(from: managedObject)) 28 | } 29 | return currencies 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Lannister/Data/Mappers/TransactionDto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransactionDto.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 06/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | 12 | class TransactionDto : NSObject { 13 | 14 | func transaction(from managedObject: TransactionManagedObject) -> Transaction { 15 | 16 | var transaction = Transaction(with: nil) 17 | transaction.identifier = managedObject.id 18 | transaction.name = managedObject.name 19 | transaction.type = managedObject.type 20 | transaction.value = managedObject.value 21 | return transaction 22 | } 23 | 24 | func transactions(from managedObjects: [TransactionManagedObject]) -> [Transaction] { 25 | 26 | var transactions = Array() 27 | for managedObject in managedObjects { 28 | transactions.append(transaction(from: managedObject)) 29 | } 30 | return transactions 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LannisterTests/LannisterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LannisterTests.swift 3 | // LannisterTests 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Lannister 11 | 12 | class LannisterTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Lannister/Data/Repositories Implementations/PortfolioRepositoryImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PortfolioRepositoryImpl.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 24/06/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MagicalRecord 11 | import CoreData 12 | 13 | class PortfolioRepositoryImpl: PortfolioRepository { 14 | 15 | func getEuroTotalValue() -> Double { 16 | 17 | let holdingsManagedObjects = HoldingManagedObject.mr_findAll(in: NSManagedObjectContext.mr_default()) 18 | var holdings = HoldingDto().holdings(from: holdingsManagedObjects as! [HoldingManagedObject]) 19 | holdings = HoldingsUseCase(with: HoldingsRepositoryImpl()).updateHoldingsWithComputedProperties(holdings: holdings) 20 | 21 | var euroTotalValue : Double = 0 22 | for holding in holdings { 23 | euroTotalValue += Currencies.getEuroValue(value: holding.representiveValue!, currency: holding.representiveCurrency!) 24 | } 25 | 26 | return euroTotalValue 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Lannister/Data/User defaults/CurrencyUserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyUserDefaults.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 10/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CurrencyUserDefaults: NSObject { 12 | 13 | func setDefaultCurrency(name: String) { 14 | UserDefaults.standard.set(name, forKey: "defaultCurrency") 15 | UserDefaults.standard.synchronize() 16 | } 17 | 18 | func getDefaultCurrencyName() -> String? { 19 | if let currency = UserDefaults.standard.value(forKey: "defaultCurrency") as? String { 20 | return currency 21 | } 22 | return nil 23 | } 24 | 25 | func setDefaultCurrency(code: String) { 26 | UserDefaults.standard.set(code, forKey: "defaultCurrencyCode") 27 | UserDefaults.standard.synchronize() 28 | } 29 | 30 | func getDefaultCurrencyCode() -> String? { 31 | if let currency = UserDefaults.standard.value(forKey: "defaultCurrencyCode") as? String { 32 | return currency 33 | } 34 | return nil 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /LannisterUITests/LannisterUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LannisterUITests.swift 3 | // LannisterUITests 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class LannisterUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Lannister/Data/API/Services/CurrencyApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyAPIService.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | 12 | class CurrencyApiService: BaseApiService { 13 | 14 | internal typealias Response = DataResponse? 15 | 16 | override init() { 17 | super.init() 18 | } 19 | 20 | var sharedManager = APIManager.sharedManager 21 | 22 | func getCurrencies(returns: @escaping(Response) -> Void) { 23 | sharedManager.request("\(currencyBaseUrl)/latest", method: .get, parameters: nil) 24 | .validate() 25 | .responseJSON { (response) in returns(response) } 26 | } 27 | 28 | func getBTCfromEur(returns: @escaping(Response) -> Void) { 29 | sharedManager.request("\(currencyCryptoBaseUrl)/eur-btc", method: .get, parameters: nil) 30 | .validate() 31 | .responseJSON { (response) in returns(response) } 32 | } 33 | 34 | func getETHfromEur(returns: @escaping(Response) -> Void) { 35 | sharedManager.request("\(currencyCryptoBaseUrl)/eur-eth", method: .get, parameters: nil) 36 | .validate() 37 | .responseJSON { (response) in returns(response) } 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Lannister/App/Utilities/Currencies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Currencies.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 10/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | 12 | struct Currencies { 13 | 14 | static func getDefaultCurrencySymbol() -> String { 15 | 16 | let currencyCode = CurrencyUserDefaults().getDefaultCurrencyCode()! 17 | let currencyManagedObject = CurrencyManagedObject.mr_findFirst(byAttribute: "code", withValue: currencyCode, in: NSManagedObjectContext.mr_default())! 18 | let currency = CurrencyDto().currency(from: currencyManagedObject) 19 | 20 | return currency.symbol 21 | } 22 | 23 | static func getDefaultCurrencyEuroRate() -> Double { 24 | 25 | let currencyCode = CurrencyUserDefaults().getDefaultCurrencyCode()! 26 | let currencyManagedObject = CurrencyManagedObject.mr_findFirst(byAttribute: "code", withValue: currencyCode, in: NSManagedObjectContext.mr_default())! 27 | let currency = CurrencyDto().currency(from: currencyManagedObject) 28 | 29 | return currency.euroRate 30 | } 31 | 32 | static func getEuroValue(value: Double, currency: Currency) -> Double { 33 | 34 | return value / currency.euroRate 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Lannister/Domain/Use cases/WalletUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WalletUseCase.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 12/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class WalletUseCase : NSObject { 12 | 13 | var repository: Repository? 14 | 15 | convenience init(with repository: Repository) { 16 | self.init() 17 | self.repository = repository 18 | } 19 | 20 | func getBalance(address: String, success: @escaping(Double) -> Void, failure: @escaping(_ error: Error) -> Void) { 21 | 22 | let repo = self.repository as! WalletRepository 23 | return repo.getBalance(address: address, success: success, failure: failure) 24 | } 25 | 26 | func getBalanceOfToken(address: String, erc20TokenAddress: String, success: @escaping(Double) -> Void, failure: @escaping(_ error: Error) -> Void) { 27 | let repo = self.repository as! WalletRepository 28 | return repo.getBalanceOfToken(address: address, erc20TokenAddress: erc20TokenAddress, success: success, failure: failure) 29 | } 30 | 31 | func getTransactions(address: String, success: @escaping(_ transactions: Array?) -> Void, failure: @escaping(_ error: Error) -> Void) { 32 | 33 | let repo = self.repository as! WalletRepository 34 | return repo.getTransactions(address: address, success: success, failure: failure) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Lannister/Data/Mappers/TokenDto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenDto.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 15/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TokenDto: NSObject { 12 | 13 | func token(from managedObject: TokenManagedObject) -> Token { 14 | 15 | var token = Token(with: nil) 16 | token.address = managedObject.address 17 | if managedObject.name != nil { 18 | token.name = managedObject.name 19 | } 20 | if managedObject.code != nil { 21 | token.code = managedObject.code 22 | } 23 | token.value = managedObject.value 24 | if managedObject.currency != nil { 25 | token.currency = CurrencyDto().currency(from: managedObject.currency!) 26 | } 27 | 28 | if managedObject.transactions != nil { 29 | if (managedObject.transactions?.allObjects.count)! > 0 { 30 | let transactionsManagedObjects = managedObject.transactions?.allObjects as! [TransactionManagedObject] 31 | let transactions = TransactionDto().transactions(from: transactionsManagedObjects) 32 | token.transactions = transactions 33 | } 34 | } 35 | return token 36 | } 37 | 38 | func tokens(from managedObjects: [TokenManagedObject]) -> [Token] { 39 | 40 | var tokens = Array() 41 | for managedObject in managedObjects { 42 | tokens.append(token(from: managedObject)) 43 | } 44 | return tokens 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Lannister/Data/Mappers/HoldingDto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingDto.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 05/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | 12 | class HoldingDto : NSObject { 13 | 14 | func holding(from managedObject: HoldingManagedObject) -> Holding { 15 | 16 | var holding = Holding(with: nil) 17 | if(managedObject.address != nil) { 18 | holding.address = managedObject.address 19 | } 20 | holding.name = managedObject.name 21 | holding.value = managedObject.value?.doubleValue 22 | holding.hexColor = managedObject.hex_color 23 | if managedObject.currency != nil { 24 | holding.currency = CurrencyDto().currency(from: managedObject.currency!) 25 | } 26 | if managedObject.transactions != nil { 27 | if (managedObject.transactions?.allObjects.count)! > 0 { 28 | let transactionsManagedObjects = managedObject.transactions?.allObjects as! [TransactionManagedObject] 29 | let transactions = TransactionDto().transactions(from: transactionsManagedObjects) 30 | holding.transactions = transactions 31 | } 32 | } 33 | return holding 34 | } 35 | 36 | func holdings(from managedObjects: [HoldingManagedObject]) -> [Holding] { 37 | 38 | var holdings = Array() 39 | for managedObject in managedObjects { 40 | holdings.append(holding(from: managedObject)) 41 | } 42 | return holdings 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Lannister/App/Utilities/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 05/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Colors { 12 | 13 | static func hexStringToUIColor(hex:String) -> UIColor { 14 | var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 15 | 16 | if (cString.hasPrefix("#")) { 17 | cString.remove(at: cString.startIndex) 18 | } 19 | 20 | if ((cString.count) != 6) { 21 | return UIColor.gray 22 | } 23 | 24 | var rgbValue:UInt32 = 0 25 | Scanner(string: cString).scanHexInt32(&rgbValue) 26 | 27 | return UIColor( 28 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 29 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 30 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 31 | alpha: CGFloat(1.0) 32 | ) 33 | } 34 | 35 | static func isCustomColor(hex: String) -> Bool { 36 | 37 | var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 38 | 39 | if (cString.hasPrefix("#")) { 40 | cString.remove(at: cString.startIndex) 41 | } 42 | 43 | if cString == "FFBF00" { 44 | return false 45 | } else if cString == "7C8288" { 46 | return false 47 | } else if cString == "E51522" { 48 | return false 49 | } else if cString == "00B382" { 50 | return false 51 | } else if cString == "1538C0" { 52 | return false 53 | } else if cString == "6F0DBE" { 54 | return false 55 | } 56 | 57 | return true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/RootController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RootController: UIViewController { 12 | 13 | private var current: UIViewController 14 | 15 | required init?(coder aDecoder: NSCoder) { 16 | self.current = LaunchController() 17 | super.init(coder: aDecoder) 18 | } 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | self.current = storyboard?.instantiateViewController(withIdentifier: "launchVC") as! LaunchController 24 | addChild(current) 25 | current.view.frame = view.bounds 26 | view.addSubview(current.view) 27 | current.didMove(toParent: self) 28 | } 29 | 30 | func switchToMainScreen() { 31 | let mainController = self.storyboard?.instantiateViewController(withIdentifier: "dashboardNavVC") as! UINavigationController 32 | animateFadeTransition(to: mainController) 33 | } 34 | 35 | func switchToAuthScreen() { 36 | let authController = self.storyboard?.instantiateViewController(withIdentifier: "authVC") as! AuthController 37 | animateFadeTransition(to: authController) 38 | } 39 | 40 | private func animateFadeTransition(to new: UIViewController, completion: (() -> Void)? = nil) { 41 | current.willMove(toParent: nil) 42 | addChild(new) 43 | self.view.addSubview(new.view) 44 | new.view.alpha = 0 45 | new.view.layoutIfNeeded() 46 | 47 | UIView.animate(withDuration: 0.5, animations: { 48 | new.view.alpha = 1 49 | self.current.view.alpha = 0 50 | }) { (finished) in 51 | self.current.view.removeFromSuperview() 52 | self.current.removeFromParent() 53 | new.didMove(toParent: self) 54 | self.current = new 55 | completion?() 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Lannister/Data/Database/Models/Generated/__TransactionManagedObject.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is machine-generated and constantly overwritten. 2 | // Make changes to TransactionManagedObject.swift instead. 3 | 4 | import Foundation 5 | import CoreData 6 | 7 | public enum TransactionManagedObjectAttributes: String { 8 | case id = "id" 9 | case name = "name" 10 | case type = "type" 11 | case value = "value" 12 | } 13 | 14 | public enum TransactionManagedObjectRelationships: String { 15 | case holding = "holding" 16 | } 17 | 18 | open class _TransactionManagedObject: NSManagedObject { 19 | 20 | // MARK: - Class methods 21 | 22 | open class func entityName () -> String { 23 | return "TransactionManagedObject" 24 | } 25 | 26 | open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { 27 | return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext) 28 | } 29 | 30 | @nonobjc 31 | open class func fetchRequest() -> NSFetchRequest { 32 | return NSFetchRequest(entityName: self.entityName()) 33 | } 34 | 35 | // MARK: - Life cycle methods 36 | 37 | public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { 38 | super.init(entity: entity, insertInto: context) 39 | } 40 | 41 | public convenience init?(managedObjectContext: NSManagedObjectContext) { 42 | guard let entity = _TransactionManagedObject.entity(managedObjectContext: managedObjectContext) else { return nil } 43 | self.init(entity: entity, insertInto: managedObjectContext) 44 | } 45 | 46 | // MARK: - Properties 47 | 48 | @NSManaged open 49 | var id: String? 50 | 51 | @NSManaged open 52 | var name: String! 53 | 54 | @NSManaged open 55 | var type: String! 56 | 57 | @NSManaged open 58 | var value: Double 59 | 60 | // MARK: - Relationships 61 | 62 | @NSManaged open 63 | var holding: HoldingManagedObject 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Lannister/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.4 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleTypeRole 23 | Editor 24 | CFBundleURLName 25 | Lannister 26 | CFBundleURLSchemes 27 | 28 | lannister 29 | 30 | 31 | 32 | CFBundleVersion 33 | $(CURRENT_PROJECT_VERSION) 34 | LSRequiresIPhoneOS 35 | 36 | NSCameraUsageDescription 37 | Allow access to camera to scan a QR code. 38 | NSFaceIDUsageDescription 39 | Keep your holdings information access secure with biometric authentication. 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIMainStoryboardFile 43 | Main 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Lannister/Info-dev.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.4 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleTypeRole 23 | Editor 24 | CFBundleURLName 25 | Lannister 26 | CFBundleURLSchemes 27 | 28 | lannister 29 | 30 | 31 | 32 | CFBundleVersion 33 | $(CURRENT_PROJECT_VERSION) 34 | LSRequiresIPhoneOS 35 | 36 | NSCameraUsageDescription 37 | Allow access to camera to scan a QR code. 38 | NSFaceIDUsageDescription 39 | Keep your holdings information access secure with biometric authentication. 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIMainStoryboardFile 43 | Main 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/CustomQRCodeController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomQRCodeController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 09/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QRCodeReader 11 | 12 | protocol CustomQRCodeControllerDelegate { 13 | func scanned(result: QRCodeReaderResult?) 14 | } 15 | 16 | class CustomQRCodeController: UIViewController { 17 | 18 | var delegate : CustomQRCodeControllerDelegate! 19 | @IBOutlet weak var closeButton : UIButton! 20 | @IBOutlet weak var scannerOverlay : UIImageView! 21 | 22 | lazy var readerVC: QRCodeReaderViewController = { 23 | let builder = QRCodeReaderViewControllerBuilder { 24 | $0.reader = QRCodeReader(metadataObjectTypes: [.qr], captureDevicePosition: .back) 25 | 26 | // Configure the view controller (optional) 27 | $0.showTorchButton = false 28 | $0.showSwitchCameraButton = false 29 | $0.showCancelButton = false 30 | $0.showOverlayView = false 31 | $0.rectOfInterest = CGRect(x: 0.2, y: 0.2, width: 0.6, height: 0.6) 32 | } 33 | 34 | return QRCodeReaderViewController(builder: builder) 35 | }() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | readerVC.completionBlock = { (result: QRCodeReaderResult?) in 41 | 42 | self.dismiss(animated: true, completion: { 43 | self.delegate.scanned(result: result) 44 | }) 45 | } 46 | 47 | self.addChild(readerVC) 48 | readerVC.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: UIScreen.main.bounds.size.height-183) 49 | view.addSubview(readerVC.view) 50 | readerVC.didMove(toParent: self) 51 | 52 | view.bringSubviewToFront(closeButton) 53 | view.bringSubviewToFront(scannerOverlay) 54 | } 55 | 56 | @IBAction func cancel() { 57 | 58 | self.dismiss(animated: true, completion: nil) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lannister iOS 👑 2 | 3 | Lannister allows anyone to keep track and analyze the distribution of their holdings both in crypto and fiat (banks, deposits, savings, etc). 4 | 5 | ![Lannister Showcase](https://user-images.githubusercontent.com/407470/59254807-c8fb3e80-8c28-11e9-8a14-dbe686d236c5.jpg) 6 | 7 | 💭 Inspiration 8 | We started building Lannister because we needed a way to keep track of our holdings both in crypto and fiat (banks, deposits, savings, etc). It was also fundamental to have security and privacy in mind. No central server that could compromise our identity and financial data. 9 | 10 | 📊 What it does 11 | Lannister allows anyone to keep track of their holdings, follow its progress and easily analyze their distribution. It's simple to use for anyone who wants to take control of their financial life. 12 | 13 | ⭐️ Unique features 14 | Convenient: crypto and fiat, all-in-one place (supporting now 12 fiat and 2 crypto currencies with more to come) 15 | Secure: runs locally on your phone, supports biometric access, optionally syncs with secure end-to-end encryption and decentralized storage via Blockstack infrastructure 16 | Open: completely open-source 17 | 18 | 💻 Platforms 19 | We're building Lannister for iOS and web, though only iOS is available at the moment. Both platforms will be able to sync information via Blockstack's infrastructure and keep information securely stored. Android is also coming in the next months. 20 | 21 | 🔮 Future plans 22 | Here's a list of some of the features we're planning: 23 | 24 | - Predictions 25 | - Retrospective analysis 26 | - Financial goals and recommendations 27 | 28 | ## Roadmap 29 | 30 | To get an overview of the current development status head over to [our roadmap](https://github.com/lannister-capital/lannister-ios/projects/1). 31 | 32 | ## Contribute 33 | 34 | ### Setup 35 | 36 | Clone the project and install the required dependencies via cocoapods with: 37 | 38 | `pod install` 39 | 40 | Open `Lannister.xcworkspace` and you're ready 🎉 41 | 42 | ### Guidelines 43 | 44 | Work in progress 🏗 45 | 46 | ## License 47 | 48 | lannister-ios is under the GPLv3 and the MPLv2 license. 49 | 50 | See [LICENSE](https://github.com/lannister-capital/lannister-ios/blob/master/LICENSE.md) for more license info. 51 | 52 | --- 53 | 54 | Made at [Done Sunday](http://donesunday.com/) 🌞 by [@alvesjtiago](https://twitter.com/alvesjtiago) and [@\_andre_sousa](https://twitter.com/_andre_sousa). 55 | 56 | Join us on [Discord](https://discord.gg/JpCBs2X) to help shape Lannister's future. 57 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/CurrenciesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrenciesController.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 09/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | 12 | protocol CurrenciesDelegate { 13 | func selectedCurrency(currency: Currency) 14 | } 15 | 16 | class CurrenciesController: UIViewController { 17 | 18 | @IBOutlet weak var tableView : UITableView! 19 | var currencies : [Currency]! 20 | var delegate : CurrenciesDelegate! 21 | var shouldSetGlobalCurrency = false 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | navigationController!.navigationBar.titleTextAttributes = 27 | [NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 18)!, 28 | NSAttributedString.Key.foregroundColor : UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)] 29 | navigationController?.navigationBar.tintColor = UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1) 30 | 31 | navigationItem.title = "Currencies" 32 | 33 | getCurrencies() 34 | } 35 | 36 | func getCurrencies() { 37 | 38 | let currenciesManagedObjects = CurrencyManagedObject.mr_findAll(in: NSManagedObjectContext.mr_default()) 39 | currencies = CurrencyDto().currencies(from: currenciesManagedObjects as! [CurrencyManagedObject]) 40 | currencies = currencies.sorted(by: { $0.name < $1.name }) 41 | tableView.reloadData() 42 | } 43 | 44 | } 45 | 46 | extension CurrenciesController : UITableViewDataSource { 47 | 48 | func numberOfSections(in tableView: UITableView) -> Int { 49 | return 1 50 | } 51 | 52 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | return currencies.count 54 | } 55 | 56 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | let cell = tableView.dequeueReusableCell(withIdentifier: "currencyCellId", for: indexPath) as! CurrencyCell 58 | let currency = currencies[indexPath.row] 59 | cell.currencyNameLabel.text = currency.name.capitalized 60 | cell.currencySymbolLabel.text = currency.symbol 61 | return cell 62 | } 63 | } 64 | 65 | extension CurrenciesController : UITableViewDelegate { 66 | 67 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 68 | 69 | let currency = currencies[indexPath.row] 70 | 71 | if shouldSetGlobalCurrency { 72 | CurrencyUserDefaults().setDefaultCurrency(code: currency.code) 73 | } 74 | 75 | delegate.selectedCurrency(currency: currency) 76 | navigationController?.popViewController(animated: true) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Lannister/Data/Repositories Implementations/HoldingsRepositoryImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingsRepositoryImpl.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 18/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | 12 | class HoldingsRepositoryImpl: HoldingsRepository { 13 | 14 | func updateHoldingsWithComputedProperties(holdings: [Holding]) -> [Holding] { 15 | 16 | var newHoldings : [Holding] = [] 17 | for holding in holdings { 18 | let updatedHolding = updateHoldingWithComputedProperties(holding: holding) 19 | newHoldings.append(updatedHolding) 20 | } 21 | 22 | return newHoldings 23 | } 24 | 25 | func updateHoldingWithComputedProperties(holding: Holding) -> Holding { 26 | 27 | var updatedHolding = holding 28 | if holding.address != nil { 29 | // get tokens (only eth for now) 30 | let predicateAddress = NSPredicate(format: "address == %@", holding.address!) 31 | let predicateCode = NSPredicate(format: "code == %@", "ETH") 32 | let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateAddress, predicateCode]) 33 | 34 | var tokenManagedObjects = TokenManagedObject.mr_findAll(with: compoundPredicate, in: NSManagedObjectContext.mr_default()) as! [TokenManagedObject] 35 | if tokenManagedObjects.count == 0 { 36 | let tokenManagedObject = TokenManagedObject(context: NSManagedObjectContext.mr_default()) 37 | tokenManagedObject.address = holding.address 38 | tokenManagedObject.code = "ETH" 39 | let currencyManagedObject = CurrencyManagedObject.mr_findFirst(byAttribute: "code", withValue: "ETH", in: NSManagedObjectContext.mr_default()) 40 | tokenManagedObject.currency = currencyManagedObject 41 | NSManagedObjectContext.mr_default().mr_saveToPersistentStoreAndWait() 42 | tokenManagedObjects = TokenManagedObject.mr_findAll(with: compoundPredicate, in: NSManagedObjectContext.mr_default()) as! [TokenManagedObject] 43 | } 44 | let tokens = TokenDto().tokens(from: tokenManagedObjects) 45 | updatedHolding.representiveValue = tokens[0].value 46 | updatedHolding.representiveCurrency = tokens[0].currency 47 | for token in tokens { 48 | updatedHolding.totalEuroValue = Currencies.getEuroValue(value: token.value, currency: token.currency) 49 | } 50 | } else { 51 | updatedHolding.representiveValue = holding.value! 52 | updatedHolding.representiveCurrency = holding.currency! 53 | updatedHolding.totalEuroValue = Currencies.getEuroValue(value: holding.value!, currency: holding.currency!) 54 | } 55 | return updatedHolding 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/DonationsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DonationsController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 14/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DonationsController: UIViewController { 12 | 13 | @IBOutlet weak var tableView : UITableView! 14 | var btcAddress = "3HbYALZzQYCzeNHvSFkW7JG5dEGHWbnV2j" 15 | var ethAddress = "0x291268FcF2c6c686fd542376Bf6Ea926fCA63C91" 16 | var daiAddress = "0x394a29F426F6505d40854ABb730D1c8DE29C8C87" 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | 22 | navigationController!.navigationBar.titleTextAttributes = 23 | [NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 18)!, 24 | NSAttributedString.Key.foregroundColor : UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)] 25 | navigationController?.navigationBar.tintColor = UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1) 26 | 27 | navigationItem.title = "Donate" 28 | } 29 | 30 | 31 | } 32 | 33 | extension DonationsController : UITableViewDataSource { 34 | 35 | func numberOfSections(in tableView: UITableView) -> Int { 36 | return 1 37 | } 38 | 39 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return 3 41 | } 42 | 43 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 44 | let cell = tableView.dequeueReusableCell(withIdentifier: "donationsCellId", for: indexPath) as! DonationCell 45 | if indexPath.row == 0 { 46 | cell.currencyLabel.text = "BTC address" 47 | cell.addressLabel.text = btcAddress 48 | } else if indexPath.row == 1 { 49 | cell.currencyLabel.text = "ETH address" 50 | cell.addressLabel.text = ethAddress 51 | } else { 52 | cell.currencyLabel.text = "DAI address" 53 | cell.addressLabel.text = daiAddress 54 | } 55 | return cell 56 | } 57 | } 58 | 59 | extension DonationsController : UITableViewDelegate { 60 | 61 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 62 | 63 | tableView.deselectRow(at: indexPath, animated: true) 64 | 65 | if indexPath.row == 0 { 66 | UIPasteboard.general.string = btcAddress 67 | } else if indexPath.row == 1 { 68 | UIPasteboard.general.string = ethAddress 69 | } else { 70 | UIPasteboard.general.string = daiAddress 71 | } 72 | 73 | let cell = tableView.cellForRow(at: indexPath) as! DonationCell 74 | cell.copyLabel.text = "Copied" 75 | 76 | DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { 77 | cell.copyLabel.text = "Copy" 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Lannister/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon copy-12.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon copy-7.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon copy-9.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "AppIcon copy-3.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "AppIcon copy-4.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon copy-2.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "AppIcon-3.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "AppIcon-2.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "AppIcon copy-14.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "AppIcon copy-10.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "AppIcon copy-13.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "AppIcon copy-8.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "AppIcon copy-11.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "AppIcon copy-5.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "AppIcon copy-6.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "AppIcon copy-1.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "AppIcon copy.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "AppIcon.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /Lannister/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Lannister/Data/Database/Models/Generated/_CurrencyManagedObject.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is machine-generated and constantly overwritten. 2 | // Make changes to CurrencyManagedObject.swift instead. 3 | 4 | import Foundation 5 | import CoreData 6 | 7 | public enum CurrencyManagedObjectAttributes: String { 8 | case code = "code" 9 | case euro_rate = "euro_rate" 10 | case name = "name" 11 | case symbol = "symbol" 12 | } 13 | 14 | public enum CurrencyManagedObjectRelationships: String { 15 | case holdings = "holdings" 16 | } 17 | 18 | public enum CurrencyManagedObjectUserInfo: String { 19 | case identityAttributes = "identityAttributes" 20 | } 21 | 22 | open class _CurrencyManagedObject: NSManagedObject { 23 | 24 | // MARK: - Class methods 25 | 26 | open class func entityName () -> String { 27 | return "CurrencyManagedObject" 28 | } 29 | 30 | open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { 31 | return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext) 32 | } 33 | 34 | @nonobjc 35 | open class func fetchRequest() -> NSFetchRequest { 36 | return NSFetchRequest(entityName: self.entityName()) 37 | } 38 | 39 | // MARK: - Life cycle methods 40 | 41 | public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { 42 | super.init(entity: entity, insertInto: context) 43 | } 44 | 45 | public convenience init?(managedObjectContext: NSManagedObjectContext) { 46 | guard let entity = _CurrencyManagedObject.entity(managedObjectContext: managedObjectContext) else { return nil } 47 | self.init(entity: entity, insertInto: managedObjectContext) 48 | } 49 | 50 | // MARK: - Properties 51 | 52 | @NSManaged open 53 | var code: String! 54 | 55 | @NSManaged open 56 | var euro_rate: Double // Optional scalars not supported 57 | 58 | @NSManaged open 59 | var name: String! 60 | 61 | @NSManaged open 62 | var symbol: String! 63 | 64 | // MARK: - Relationships 65 | 66 | @NSManaged open 67 | var holdings: NSSet 68 | 69 | open func holdingsSet() -> NSMutableSet { 70 | return self.holdings.mutableCopy() as! NSMutableSet 71 | } 72 | 73 | } 74 | 75 | extension _CurrencyManagedObject { 76 | 77 | open func addHoldings(_ objects: NSSet) { 78 | let mutable = self.holdings.mutableCopy() as! NSMutableSet 79 | mutable.union(objects as Set) 80 | self.holdings = mutable.copy() as! NSSet 81 | } 82 | 83 | open func removeHoldings(_ objects: NSSet) { 84 | let mutable = self.holdings.mutableCopy() as! NSMutableSet 85 | mutable.minus(objects as Set) 86 | self.holdings = mutable.copy() as! NSSet 87 | } 88 | 89 | open func addHoldingsObject(_ value: HoldingManagedObject) { 90 | let mutable = self.holdings.mutableCopy() as! NSMutableSet 91 | mutable.add(value) 92 | self.holdings = mutable.copy() as! NSSet 93 | } 94 | 95 | open func removeHoldingsObject(_ value: HoldingManagedObject) { 96 | let mutable = self.holdings.mutableCopy() as! NSMutableSet 97 | mutable.remove(value) 98 | self.holdings = mutable.copy() as! NSSet 99 | } 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Lannister/Data/Database/Models/Generated/__HoldingManagedObject.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is machine-generated and constantly overwritten. 2 | // Make changes to HoldingManagedObject.swift instead. 3 | 4 | import Foundation 5 | import CoreData 6 | 7 | public enum HoldingManagedObjectAttributes: String { 8 | case hex_color = "hex_color" 9 | case id = "id" 10 | case name = "name" 11 | case value = "value" 12 | } 13 | 14 | public enum HoldingManagedObjectRelationships: String { 15 | case currency = "currency" 16 | case transactions = "transactions" 17 | } 18 | 19 | open class _HoldingManagedObject: NSManagedObject { 20 | 21 | // MARK: - Class methods 22 | 23 | open class func entityName () -> String { 24 | return "HoldingManagedObject" 25 | } 26 | 27 | open class func entity(managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { 28 | return NSEntityDescription.entity(forEntityName: self.entityName(), in: managedObjectContext) 29 | } 30 | 31 | @nonobjc 32 | open class func fetchRequest() -> NSFetchRequest { 33 | return NSFetchRequest(entityName: self.entityName()) 34 | } 35 | 36 | // MARK: - Life cycle methods 37 | 38 | public override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { 39 | super.init(entity: entity, insertInto: context) 40 | } 41 | 42 | public convenience init?(managedObjectContext: NSManagedObjectContext) { 43 | guard let entity = _HoldingManagedObject.entity(managedObjectContext: managedObjectContext) else { return nil } 44 | self.init(entity: entity, insertInto: managedObjectContext) 45 | } 46 | 47 | // MARK: - Properties 48 | 49 | @NSManaged open 50 | var hex_color: String? 51 | 52 | @NSManaged open 53 | var id: String? 54 | 55 | @NSManaged open 56 | var name: String! 57 | 58 | @NSManaged open 59 | var value: Double // Optional scalars not supported 60 | 61 | // MARK: - Relationships 62 | 63 | @NSManaged open 64 | var currency: CurrencyManagedObject 65 | 66 | @NSManaged open 67 | var transactions: NSSet 68 | 69 | open func transactionsSet() -> NSMutableSet { 70 | return self.transactions.mutableCopy() as! NSMutableSet 71 | } 72 | 73 | } 74 | 75 | extension _HoldingManagedObject { 76 | 77 | open func addTransactions(_ objects: NSSet) { 78 | let mutable = self.transactions.mutableCopy() as! NSMutableSet 79 | mutable.union(objects as Set) 80 | self.transactions = mutable.copy() as! NSSet 81 | } 82 | 83 | open func removeTransactions(_ objects: NSSet) { 84 | let mutable = self.transactions.mutableCopy() as! NSMutableSet 85 | mutable.minus(objects as Set) 86 | self.transactions = mutable.copy() as! NSSet 87 | } 88 | 89 | open func addTransactionsObject(_ value: TransactionManagedObject) { 90 | let mutable = self.transactions.mutableCopy() as! NSMutableSet 91 | mutable.add(value) 92 | self.transactions = mutable.copy() as! NSSet 93 | } 94 | 95 | open func removeTransactionsObject(_ value: TransactionManagedObject) { 96 | let mutable = self.transactions.mutableCopy() as! NSMutableSet 97 | mutable.remove(value) 98 | self.transactions = mutable.copy() as! NSSet 99 | } 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Lannister.xcodeproj/xcshareddata/xcschemes/Lannister Beta.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.8.0) 3 | - BigInt (3.1.0): 4 | - SipHash (~> 1.2) 5 | - BiometricAuthentication (3.0) 6 | - Blockstack (1.0.1): 7 | - CryptoSwift (= 0.15.0) 8 | - PromisesSwift 9 | - STRegex 10 | - Charts (3.4.0): 11 | - Charts/Core (= 3.4.0) 12 | - Charts/Core (3.4.0) 13 | - CryptoSwift (0.15.0) 14 | - EthereumABI (1.1.1): 15 | - BigInt (~> 3.1) 16 | - CryptoSwift (~> 0.13) 17 | - EthereumAddress (~> 1.0.0) 18 | - EthereumAddress (1.0.0): 19 | - CryptoSwift (~> 0.13) 20 | - Groot (3.0.1): 21 | - Groot/Swift (= 3.0.1) 22 | - Groot/Swift (3.0.1) 23 | - MagicalRecord (2.3.3): 24 | - MagicalRecord/Core (= 2.3.3) 25 | - MagicalRecord/Core (2.3.3) 26 | - PromiseKit (6.4.1): 27 | - PromiseKit/CorePromise (= 6.4.1) 28 | - PromiseKit/Foundation (= 6.4.1) 29 | - PromiseKit/UIKit (= 6.4.1) 30 | - PromiseKit/CorePromise (6.4.1) 31 | - PromiseKit/Foundation (6.4.1): 32 | - PromiseKit/CorePromise 33 | - PromiseKit/UIKit (6.4.1): 34 | - PromiseKit/CorePromise 35 | - PromisesObjC (1.2.8) 36 | - PromisesSwift (1.2.8): 37 | - PromisesObjC (= 1.2.8) 38 | - QRCodeReader.swift (10.0.0) 39 | - secp256k1_swift (1.0.3) 40 | - SipHash (1.2.2) 41 | - Starscream (3.1.0) 42 | - STRegex (2.1.0) 43 | - SVProgressHUD (2.2.5) 44 | - SwiftRLP (1.1): 45 | - BigInt (~> 3.1) 46 | - web3swift (2.1.3): 47 | - BigInt (= 3.1) 48 | - CryptoSwift (= 0.15.0) 49 | - EthereumABI (= 1.1.1) 50 | - EthereumAddress (= 1.0.0) 51 | - PromiseKit (= 6.4.1) 52 | - secp256k1_swift (= 1.0.3) 53 | - Starscream (= 3.1.0) 54 | - SwiftRLP (= 1.1) 55 | 56 | DEPENDENCIES: 57 | - Alamofire (= 4.8.0) 58 | - BiometricAuthentication 59 | - Blockstack (= 1.0.1) 60 | - Charts 61 | - Groot (= 3.0.1) 62 | - MagicalRecord (from `https://github.com/magicalpanda/MagicalRecord.git`, tag `v2.3.3`) 63 | - QRCodeReader.swift (~> 10.0.0) 64 | - SVProgressHUD 65 | - web3swift 66 | 67 | SPEC REPOS: 68 | trunk: 69 | - Alamofire 70 | - BigInt 71 | - BiometricAuthentication 72 | - Blockstack 73 | - Charts 74 | - CryptoSwift 75 | - EthereumABI 76 | - EthereumAddress 77 | - Groot 78 | - PromiseKit 79 | - PromisesObjC 80 | - PromisesSwift 81 | - QRCodeReader.swift 82 | - secp256k1_swift 83 | - SipHash 84 | - Starscream 85 | - STRegex 86 | - SVProgressHUD 87 | - SwiftRLP 88 | - web3swift 89 | 90 | EXTERNAL SOURCES: 91 | MagicalRecord: 92 | :git: https://github.com/magicalpanda/MagicalRecord.git 93 | :tag: v2.3.3 94 | 95 | CHECKOUT OPTIONS: 96 | MagicalRecord: 97 | :git: https://github.com/magicalpanda/MagicalRecord.git 98 | :tag: v2.3.3 99 | 100 | SPEC CHECKSUMS: 101 | Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 102 | BigInt: 76b5dfdfa3e2e478d4ffdf161aeede5502e2742f 103 | BiometricAuthentication: e39b1a55040d679d5dbc4fa90d76e067c432de7f 104 | Blockstack: 8dadf3fb26408d2a102690a8e45fc1068b403ab5 105 | Charts: 74c9f256eaf0460c0c416522d1cf8c634ea6b286 106 | CryptoSwift: 769f58a9e89f64e8796c2e59ce5f002dc81a2438 107 | EthereumABI: f040f5429e5a4366d028c88b88d9441e137593af 108 | EthereumAddress: f476e1320dca3a0024431e713ede7a09c7eb7796 109 | Groot: a668afbcf0be88d76c0a26c714cfa4638ceaca66 110 | MagicalRecord: e3dfdee87c234c6d460dbd5d8a32e3cb4de223aa 111 | PromiseKit: 4c76a6506638034e3d7bede97b2ff7743f7bd2dc 112 | PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 113 | PromisesSwift: 37bad6f4daddb02f7c9c531efe91e8b21c13ee2f 114 | QRCodeReader.swift: 74d0378eb0c7807828552b49626bbc8307495aee 115 | secp256k1_swift: 4fc5c4b2d2c6d21ee8ccb868cdc92da12f38bed9 116 | SipHash: fad90a4683e420c52ef28063063dbbce248ea6d4 117 | Starscream: 08172b481e145289c4930cb567230fb55897cfa4 118 | STRegex: dfa420d93d8c1402956233b3879ec1fc14b45fbe 119 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 120 | SwiftRLP: 5512899925f1a9e1c78c902ed3bf857880e814a0 121 | web3swift: 23cc365dd3b28e9b990813965af4bd031e108f64 122 | 123 | PODFILE CHECKSUM: 72dee91fbd733d689e133975f6f7a74082763356 124 | 125 | COCOAPODS: 1.8.1 126 | -------------------------------------------------------------------------------- /Lannister/App/Utilities/BioAccessController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BioAccessController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 07/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import BiometricAuthentication 12 | 13 | let CancelTitle = "Cancel" 14 | let OKTitle = "OK" 15 | typealias AlertViewController = UIAlertController 16 | 17 | struct AlertAction { 18 | 19 | var title: String = "" 20 | var type: UIAlertAction.Style? = .default 21 | var enable: Bool? = true 22 | var selected: Bool? = false 23 | 24 | init(title: String, type: UIAlertAction.Style? = .default, enable: Bool? = true, selected: Bool? = false) { 25 | self.title = title 26 | self.type = type 27 | self.enable = enable 28 | self.selected = selected 29 | } 30 | } 31 | 32 | extension UIAlertController { 33 | } 34 | 35 | extension UIViewController { 36 | 37 | // Show Alert or Action sheet 38 | func getAlertViewController(type: UIAlertController.Style, with title: String?, message: String?, actions:[AlertAction], showCancel: Bool , actionHandler:@escaping ((_ title: String) -> ())) -> AlertViewController { 39 | 40 | let alertController = UIAlertController(title: title, message: message, preferredStyle: type) 41 | 42 | // items 43 | var actionItems: [UIAlertAction] = [] 44 | 45 | // add actions 46 | for (index, action) in actions.enumerated() { 47 | 48 | let actionButton = UIAlertAction(title: action.title, style: action.type!, handler: { (actionButton) in 49 | actionHandler(actionButton.title ?? "") 50 | }) 51 | 52 | actionButton.isEnabled = action.enable! 53 | if type == .actionSheet { actionButton.setValue(action.selected, forKey: "checked") } 54 | actionButton.setAssociated(object: index) 55 | 56 | actionItems.append(actionButton) 57 | alertController.addAction(actionButton) 58 | } 59 | 60 | // add cancel button 61 | if showCancel { 62 | let cancelAction = UIAlertAction(title: CancelTitle, style: .cancel, handler: { (action) in 63 | actionHandler(action.title!) 64 | }) 65 | alertController.addAction(cancelAction) 66 | } 67 | return alertController 68 | } 69 | 70 | 71 | func showAlert(title: String, message: String) { 72 | 73 | let okAction = AlertAction(title: OKTitle) 74 | let alertController = getAlertViewController(type: .alert, with: title, message: message, actions: [okAction], showCancel: false) { (button) in 75 | } 76 | present(alertController, animated: true, completion: nil) 77 | } 78 | 79 | func showLoginSucessAlert() { 80 | showAlert(title: "Success", message: "Login successful") 81 | } 82 | 83 | func showErrorAlert(message: String) { 84 | showAlert(title: "Error", message: message) 85 | } 86 | 87 | func showGotoSettingsAlert(message: String) { 88 | let settingsAction = AlertAction(title: "Go to settings") 89 | 90 | let alertController = getAlertViewController(type: .alert, with: "Error", message: message, actions: [settingsAction], showCancel: true, actionHandler: { (buttonText) in 91 | if buttonText == CancelTitle { return } 92 | 93 | // open settings 94 | let url = URL(string: UIApplication.openSettingsURLString) 95 | if UIApplication.shared.canOpenURL(url!) { 96 | UIApplication.shared.open(url!, options: [:]) 97 | } 98 | 99 | }) 100 | present(alertController, animated: true, completion: nil) 101 | } 102 | 103 | // show passcode authentication 104 | func showPasscodeAuthentication(message: String) { 105 | 106 | BioMetricAuthenticator.authenticateWithPasscode(reason: message) { [weak self] (result) in 107 | switch result { 108 | case .success( _): 109 | self?.showLoginSucessAlert() // passcode authentication success 110 | case .failure(let error): 111 | print(error.message()) 112 | } 113 | } 114 | } 115 | 116 | } 117 | 118 | /// NSObject associated object 119 | public extension NSObject { 120 | 121 | /// keys 122 | private struct AssociatedKeys { 123 | static var descriptiveName = "associatedObject" 124 | } 125 | 126 | /// set associated object 127 | @objc func setAssociated(object: Any) { 128 | objc_setAssociatedObject(self, &AssociatedKeys.descriptiveName, object, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 129 | } 130 | 131 | /// get associated object 132 | @objc func associatedObject() -> Any? { 133 | return objc_getAssociatedObject(self, &AssociatedKeys.descriptiveName) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Lannister/Data/Repositories Implementations/WalletRepositoryImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WalletRepositoryImpl.swift 3 | // Lannister 4 | // 5 | // Created by Andre Sousa on 12/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | import CoreData 12 | import Web3swift 13 | import EthereumAddress 14 | import BigInt 15 | 16 | class WalletRepositoryImpl: WalletRepository { 17 | 18 | func getBalance(address: String, success: @escaping(Double) -> Void, failure: @escaping(_ error: Error) -> Void) { 19 | 20 | print("getBalance address \(address)") 21 | DispatchQueue.global(qos: .background).async { 22 | let walletAddress = EthereumAddress(address, ignoreChecksum: true)! 23 | let web3 = Web3.InfuraMainnetWeb3(accessToken: "c7b6351e2ba84d3e94d1b33c14bb9a16") // Mainnet Infura Endpoint Provider 24 | let balanceResult = try! web3.eth.getBalance(address: walletAddress) 25 | let balanceString = Web3.Utils.formatToEthereumUnits(balanceResult, toUnits: .eth, decimals: 3)! 26 | 27 | print("balanceString \(balanceString)") 28 | success(balanceString.doubleValue!) 29 | } 30 | } 31 | 32 | func getBalanceOfToken(address: String, erc20TokenAddress: String, success: @escaping(Double) -> Void, failure: @escaping(_ error: Error) -> Void) { 33 | 34 | DispatchQueue.global(qos: .background).async { 35 | let walletAddress = EthereumAddress(address)! // Your wallet address 36 | let erc20ContractAddress = EthereumAddress(erc20TokenAddress)! 37 | let web3 = Web3.InfuraMainnetWeb3(accessToken: "c7b6351e2ba84d3e94d1b33c14bb9a16") // Mainnet Infura Endpoint Provider 38 | let contract = web3.contract(Web3.Utils.erc20ABI, at: erc20ContractAddress, abiVersion: 2)! 39 | var options = TransactionOptions.defaultOptions 40 | options.from = walletAddress 41 | options.gasPrice = .automatic 42 | options.gasLimit = .automatic 43 | let method = "balanceOf" 44 | let tx = contract.read( 45 | method, 46 | parameters: [walletAddress] as [AnyObject], 47 | extraData: Data(), 48 | transactionOptions: options)! 49 | let tokenBalance = try! tx.call() 50 | let balanceBigUInt = tokenBalance["0"] as! BigUInt 51 | let balanceString = Web3.Utils.formatToEthereumUnits(balanceBigUInt, toUnits: .eth, decimals: 3)! 52 | success(balanceString.doubleValue!) 53 | } 54 | } 55 | 56 | func getTransactions(address: String, success: @escaping(_ transactions: Array?) -> Void, failure: @escaping(_ error: Error) -> Void) { 57 | 58 | let service = EtherScanApiService() 59 | 60 | let params = ["module": "account", 61 | "action": "txlist", 62 | "address": address, 63 | "startblock": 0, 64 | "block": 99999999, 65 | "sort": "asc", 66 | "apikey": service.apiKey] as [String : Any] 67 | 68 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 69 | 70 | service.getTransactions(params: params) { response in 71 | 72 | UIApplication.shared.isNetworkActivityIndicatorVisible = false 73 | 74 | switch (response!.result) { 75 | case .success(let JSON): 76 | print("got transactions: \(JSON)") 77 | 78 | if let status = (JSON as AnyObject).object(forKey: "status") as? String { 79 | if status == "1" { 80 | let transactionsJSON = (JSON as AnyObject).object(forKey: "result") as! NSArray 81 | var transactions : [Transaction] = [] 82 | for transactionJSON in transactionsJSON { 83 | if let transactionDic = transactionJSON as? [String: Any] { 84 | var transaction = Transaction(with: transactionDic) 85 | transaction.identifier = transactionDic["blockHash"] as? String 86 | transaction.value = ((transactionDic["value"] as? String)?.doubleValue)! / 1000000000000000000.0 87 | if (transactionDic["from"] as! String).lowercased() == address.lowercased() { 88 | transaction.type = "debit" 89 | transaction.name = transactionDic["to"] as? String 90 | } else { 91 | transaction.type = "credit" 92 | transaction.name = transactionDic["from"] as? String 93 | } 94 | transactions.append(transaction) 95 | } 96 | } 97 | success(transactions) 98 | } else { 99 | success(nil) 100 | } 101 | } else { 102 | success(nil) 103 | } 104 | 105 | case .failure(let error): 106 | print("error getAppointments - > \n \(error.localizedDescription) \n") 107 | failure(error) 108 | } 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/AuthController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Blockstack 11 | 12 | class AuthController: UIViewController { 13 | 14 | @IBOutlet weak var signInButton : UIButton! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | signInButton.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor 20 | signInButton.layer.shadowOffset = CGSize(width: 0.0, height: 2.0) 21 | signInButton.layer.shadowOpacity = 0.3 22 | signInButton.layer.shadowRadius = 0.0 23 | signInButton.layer.masksToBounds = false 24 | } 25 | 26 | @IBAction func signIn() { 27 | 28 | DispatchQueue.main.asyncAfter(deadline: .now(), execute: { 29 | 30 | UserDefaults.standard.setValue("auth", forKey: "Auth") 31 | let writeAuthScope = AuthScope(rawValue: "store_write") 32 | let publishDataAuthScope = AuthScope(rawValue: "publish_data") 33 | 34 | Blockstack.shared.signIn(redirectURI: URL(string: "https://lannister.capital/redirect-mobile.html")!, 35 | appDomain: URL(string: "https://lannister.capital")!, 36 | manifestURI: nil, 37 | scopes: [writeAuthScope!, publishDataAuthScope!]) { authResult in 38 | switch authResult { 39 | case .success(let userData): 40 | print("Sign in SUCCESS", userData.profile?.name as Any) 41 | DispatchQueue.main.asyncAfter(deadline: .now(), execute: { 42 | AppDelegate.shared.updateCurrencies() 43 | self.checkUserData() 44 | }) 45 | case .cancelled: 46 | print("Sign in CANCELLED") 47 | case .failed(let error): 48 | print("Sign in FAILED, error: ", error ?? "n/a") 49 | } 50 | } 51 | }) 52 | } 53 | 54 | @IBAction func syncLater() { 55 | 56 | DispatchQueue.main.asyncAfter(deadline: .now(), execute: { 57 | 58 | UserDefaults.standard.setValue("skippedAuth", forKey: "Auth") 59 | UserDefaults.standard.synchronize() 60 | AppDelegate.shared.updateCurrencies() 61 | AppDelegate.shared.rootViewController.switchToMainScreen() 62 | }) 63 | } 64 | 65 | func checkUserData() { 66 | 67 | BlockstackApiService().checkUserData { hasData in 68 | if hasData { 69 | DispatchQueue.main.asyncAfter(deadline: .now(), execute: { 70 | let alert = UIAlertController(title: "You already have holdings on your Blockstack account.", 71 | message: "You have to choose between keeping the ones you already have on your Blockstack account or overwrite them with the ones you have locally on the iPhone app right now.", 72 | preferredStyle: .alert) 73 | alert.addAction(UIAlertAction(title: "Use local data", style: UIAlertAction.Style.default, handler: { _ in 74 | self.writeNewData() 75 | })) 76 | alert.addAction(UIAlertAction(title: "Use data from Blockstack", style: UIAlertAction.Style.default, handler: { _ in 77 | self.readData() 78 | })) 79 | self.present(alert, animated: true, completion: nil) 80 | }) 81 | } else { 82 | self.writeNewData() 83 | } 84 | } 85 | } 86 | 87 | func writeNewData() { 88 | 89 | BlockstackApiService().send(returns: { errorMessage in 90 | DispatchQueue.main.asyncAfter(deadline: .now(), execute: { 91 | if errorMessage != nil { 92 | let alert = UIAlertController(title: "Error", 93 | message: errorMessage, 94 | preferredStyle: .alert) 95 | alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) 96 | self.present(alert, animated: true, completion: nil) 97 | } 98 | AppDelegate.shared.rootViewController.switchToMainScreen() 99 | }) 100 | }) 101 | } 102 | 103 | func readData() { 104 | BlockstackApiService().sync(returns: { errorMessage in 105 | DispatchQueue.main.asyncAfter(deadline: .now(), execute: { 106 | if errorMessage != nil { 107 | let alert = UIAlertController(title: "Error", 108 | message: errorMessage, 109 | preferredStyle: .alert) 110 | alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) 111 | self.present(alert, animated: true, completion: nil) 112 | } 113 | AppDelegate.shared.rootViewController.switchToMainScreen() 114 | }) 115 | }) 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /Lannister/Data/API/Services/BlockstackApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlockstackApiService.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 14/05/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Blockstack 11 | import MagicalRecord 12 | import Groot 13 | 14 | class BlockstackApiService: BaseApiService { 15 | 16 | let dbVersion = "1.0.4" 17 | 18 | override init() { 19 | super.init() 20 | } 21 | 22 | func send(returns: @escaping(String?) -> Void) { 23 | 24 | let holdingsManagedObjects = HoldingManagedObject.mr_findAll(in: NSManagedObjectContext.mr_default()) as! [HoldingManagedObject] 25 | var holdingsArray : [Any] = [] 26 | for holding in holdingsManagedObjects { 27 | var holdingDic = 28 | ["hex_color" : holding.hex_color!, 29 | "id": holding.id!, 30 | "name": holding.name!] as [String : Any] 31 | if holding.value != nil { 32 | holdingDic["value"] = holding.value!.doubleValue 33 | } 34 | if holding.currency?.code != nil { 35 | holdingDic["currency_code"] = holding.currency!.code! 36 | } 37 | if holding.address != nil { 38 | holdingDic["address"] = holding.address! 39 | } 40 | holdingsArray.append(holdingDic) 41 | } 42 | let versionString = dbVersion 43 | let lannisterDictionary = ["db_version": versionString, "holdings": holdingsArray] as [String : Any] 44 | guard let data = try? JSONSerialization.data(withJSONObject: lannisterDictionary, options: []) else { 45 | return 46 | } 47 | let jsonString = String(data: data, encoding: String.Encoding.utf8)! 48 | Blockstack.shared.putFile(to: "db.json", text: jsonString, encrypt: true, completion: { (file, error) in 49 | print("overwrite db json") 50 | if error != nil { 51 | print("error \(String(describing: error?.localizedDescription))") 52 | returns(error?.localizedDescription) 53 | } else { 54 | returns(nil) 55 | } 56 | }) 57 | } 58 | 59 | func sync(returns: @escaping(String?) -> Void) { 60 | 61 | Blockstack.shared.getFile(at: "db.json", decrypt: true) { (response, error) in 62 | if let decryptedResponse = response as? DecryptedValue { 63 | let responseString = decryptedResponse.plainText 64 | let versionString = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String 65 | if let parsedResponse = responseString!.parseJSONString as? [String: Any] { 66 | if let version = parsedResponse["db_version"] as? String { 67 | if version != versionString { 68 | let errorMessage = "You're using on old version of the app. Please update the app on the App Store to be able to sync your holdings." 69 | returns(errorMessage) 70 | } else { 71 | if let parsedHoldings = parsedResponse["holdings"] as? Array { 72 | let localHoldings = HoldingManagedObject.mr_findAll(in: NSManagedObjectContext.mr_default()) as! [HoldingManagedObject] 73 | let remoteHoldings = parsedHoldings as! Array 74 | let remoteHoldingsIds = remoteHoldings.map({ (holding: JSONDictionary) -> String in 75 | holding["id"] as! String 76 | }) 77 | for holding in localHoldings { 78 | if holding.address == nil && !remoteHoldingsIds.contains(holding.id!) { 79 | holding.mr_deleteEntity(in: NSManagedObjectContext.mr_default()) 80 | } 81 | } 82 | _ = self.parseHoldings(holdings: parsedHoldings as NSArray) 83 | NSManagedObjectContext.mr_default().mr_saveToPersistentStoreAndWait() 84 | returns(nil) 85 | } 86 | } 87 | } 88 | } 89 | print("error \(String(describing: error?.localizedDescription))") 90 | returns(error?.localizedDescription) 91 | } 92 | if error != nil { 93 | print("error \(String(describing: error?.localizedDescription))") 94 | } 95 | } 96 | } 97 | 98 | func checkUserData(returns: @escaping(Bool) -> Void) { 99 | 100 | Blockstack.shared.getFile(at: "db.json", decrypt: true) { (response, error) in 101 | if (response as? DecryptedValue) != nil { 102 | returns(true) 103 | } else { 104 | returns(false) 105 | } 106 | } 107 | } 108 | 109 | internal func parseHoldings(holdings: NSArray) -> Array? { 110 | 111 | var newHoldings : Array? = Array() 112 | for holding in holdings { 113 | let holdingJson : JSONDictionary = holding as! JSONDictionary 114 | var newHolding = HoldingManagedObject.mr_findFirst(byAttribute: "id", withValue: holdingJson["id"]!, in: NSManagedObjectContext.mr_default()) 115 | if newHolding == nil { 116 | newHolding = HoldingManagedObject(context: NSManagedObjectContext.mr_default()) 117 | } 118 | if let address = holdingJson["address"] as? String { 119 | newHolding!.address = address 120 | } 121 | newHolding!.id = holdingJson["id"] as? String 122 | newHolding!.hex_color = holdingJson["hex_color"] as? String 123 | newHolding!.name = holdingJson["name"] as? String 124 | newHolding!.value = holdingJson["value"] as? NSNumber 125 | if holdingJson["currency_code"] != nil { 126 | let currency = CurrencyManagedObject.mr_findFirst(byAttribute: "code", withValue: holdingJson["currency_code"]!, in: NSManagedObjectContext.mr_default()) 127 | newHolding!.currency = currency 128 | } 129 | 130 | newHoldings?.append(newHolding!) 131 | } 132 | return newHoldings 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /Lannister/Data/Database/db.xcdatamodeld/db 2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Lannister/Data/Database/db.xcdatamodeld/db.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/ETHCreateHoldingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ETHCreateHoldingController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 08/07/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QRCodeReader 11 | 12 | protocol ETHDelegate { 13 | func importedAddress(address: String) 14 | func insertedValue(value: Double) 15 | } 16 | 17 | class ETHCreateHoldingController: UIViewController { 18 | 19 | @IBOutlet weak var valueTextField : UITextField! 20 | @IBOutlet weak var tableView : UITableView! 21 | var toolBar = UIToolbar() 22 | var delegate : ETHDelegate! 23 | var holdingValue : Double! 24 | 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | navigationItem.title = "ETH" 30 | 31 | navigationController!.navigationBar.titleTextAttributes = 32 | [NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 18)!, 33 | NSAttributedString.Key.foregroundColor : UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)] 34 | navigationController?.navigationBar.tintColor = UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1) 35 | 36 | navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) 37 | navigationController?.navigationBar.shadowImage = UIImage() 38 | 39 | if holdingValue != nil { 40 | valueTextField.text = "\(holdingValue!)" 41 | } 42 | 43 | let btnDone = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(saveValue)) 44 | let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 45 | let clearButton = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(clearValue)) 46 | 47 | toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 44)) 48 | toolBar.isUserInteractionEnabled = true 49 | toolBar.barStyle = .default 50 | toolBar.isTranslucent = false 51 | toolBar.items = [clearButton, spaceButton, btnDone] 52 | 53 | valueTextField.inputAccessoryView = toolBar 54 | 55 | let tap = UITapGestureRecognizer(target: self, action: #selector(removeKeyboard)) 56 | tap.cancelsTouchesInView = false 57 | view.addGestureRecognizer(tap) 58 | } 59 | 60 | @objc func removeKeyboard() { 61 | clearValue() 62 | self.view.endEditing(true) 63 | } 64 | 65 | @objc func clearValue() { 66 | valueTextField.text = "" 67 | } 68 | 69 | @objc func saveValue() { 70 | if let newValue = valueTextField.text?.doubleValue { 71 | delegate.insertedValue(value: newValue) 72 | navigationController?.popViewController(animated: true) 73 | } 74 | } 75 | 76 | func showInvalidAddressWarning() { 77 | 78 | DispatchQueue.main.async { 79 | let msg = "Invalid address" 80 | let alert = UIAlertController(title: "Error", 81 | message: msg, 82 | preferredStyle: .alert) 83 | alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) 84 | self.present(alert, animated: true, completion: nil) 85 | } 86 | } 87 | 88 | func scanAction() { 89 | 90 | let customQRCodeController = storyboard?.instantiateViewController(withIdentifier: "customQRCodeVC") as! CustomQRCodeController 91 | customQRCodeController.delegate = self 92 | self.present(customQRCodeController, animated: true, completion: nil) 93 | } 94 | } 95 | 96 | extension ETHCreateHoldingController : UITableViewDataSource { 97 | 98 | func numberOfSections(in tableView: UITableView) -> Int { 99 | return 1 100 | } 101 | 102 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 103 | return 2 104 | } 105 | 106 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 107 | 108 | if indexPath.row == 0 { 109 | let cell = tableView.dequeueReusableCell(withIdentifier: "clipboardCellId", for: indexPath) as! ETHCell 110 | 111 | if let pasteboardString = UIPasteboard.general.string { 112 | let first2 = String(pasteboardString.prefix(2)) 113 | if first2 == "0x" { 114 | cell.addressLabel.text = UIPasteboard.general.string 115 | } 116 | } 117 | return cell 118 | } else { 119 | let cell = tableView.dequeueReusableCell(withIdentifier: "scanCellId", for: indexPath) 120 | return cell 121 | } 122 | } 123 | } 124 | 125 | extension ETHCreateHoldingController : UITableViewDelegate { 126 | 127 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 128 | 129 | tableView.deselectRow(at: indexPath, animated: true) 130 | 131 | if indexPath.row == 0 { 132 | if let pasteboardString = UIPasteboard.general.string { 133 | let first2 = String(pasteboardString.prefix(2)) 134 | if first2 == "0x" { 135 | delegate.importedAddress(address: pasteboardString) 136 | navigationController?.popViewController(animated: true) 137 | return 138 | } 139 | } 140 | showInvalidAddressWarning() 141 | 142 | } else { 143 | scanAction() 144 | } 145 | } 146 | } 147 | 148 | extension ETHCreateHoldingController : CustomQRCodeControllerDelegate { 149 | 150 | func scanned(result: QRCodeReaderResult?) { 151 | 152 | if let resultString = result?.value { 153 | let first2 = String(resultString.prefix(2)) 154 | let ethereumUrl = String(resultString.prefix(9)) 155 | if first2 == "0x" { 156 | DispatchQueue.main.async { 157 | self.delegate.importedAddress(address: resultString) 158 | self.navigationController?.popViewController(animated: true) 159 | } 160 | } else if ethereumUrl == "ethereum:" { 161 | DispatchQueue.main.async { 162 | let range = resultString.range(of: ethereumUrl) 163 | self.delegate.importedAddress(address: String(resultString[range!.upperBound...])) 164 | self.navigationController?.popViewController(animated: true) 165 | } 166 | } else { 167 | showInvalidAddressWarning() 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/ColorCodesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCodesController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ColorCodesDelegate { 12 | func newColorCode(hex: String) 13 | } 14 | 15 | class ColorCodesController: UIViewController { 16 | 17 | @IBOutlet weak var tableView : UITableView! 18 | var delegate : ColorCodesDelegate! 19 | var activeField : UITextField? 20 | var hexString : String? 21 | let selection = UISelectionFeedbackGenerator() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | // Do any additional setup after loading the view. 27 | navigationItem.title = "Color Codes" 28 | navigationController!.navigationBar.titleTextAttributes = 29 | [NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 18)!, 30 | NSAttributedString.Key.foregroundColor : UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)] 31 | navigationController?.navigationBar.tintColor = UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1) 32 | 33 | registerForKeyboardNotifications() 34 | } 35 | 36 | override func viewDidAppear(_ animated: Bool) { 37 | super.viewDidAppear(animated) 38 | 39 | if hexString != nil { 40 | if Colors.isCustomColor(hex: hexString!) { 41 | if let cell = tableView.cellForRow(at: IndexPath(row: 6, section: 0)) as? ColorCodeCustomCell { 42 | cell.colorCodeTextField.becomeFirstResponder() 43 | } 44 | } 45 | } 46 | } 47 | 48 | func registerForKeyboardNotifications() { 49 | NotificationCenter.default.addObserver(self, 50 | selector: #selector(keyboardWasShown(aNotification:)), 51 | name: UIResponder.keyboardDidShowNotification, 52 | object: nil) 53 | NotificationCenter.default.addObserver(self, 54 | selector: #selector(keyboardWillBeHidden(aNotification:)), 55 | name: UIResponder.keyboardWillHideNotification, 56 | object: nil) 57 | } 58 | 59 | @objc func keyboardWasShown(aNotification: NSNotification) { 60 | let info = aNotification.userInfo as! [String: AnyObject], 61 | kbSize = (info[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue.size, 62 | contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0) 63 | 64 | self.tableView.contentInset = contentInsets 65 | self.tableView.scrollIndicatorInsets = contentInsets 66 | 67 | // If active text field is hidden by keyboard, scroll it so it's visible 68 | // Your app might not need or want this behavior. 69 | var aRect = self.view.frame 70 | aRect.size.height -= kbSize.height 71 | 72 | if !aRect.contains(activeField!.frame.origin) { 73 | self.tableView.scrollRectToVisible(activeField!.frame, animated: true) 74 | } 75 | } 76 | 77 | @objc func keyboardWillBeHidden(aNotification: NSNotification) { 78 | let contentInsets = UIEdgeInsets.zero 79 | self.tableView.contentInset = contentInsets 80 | self.tableView.scrollIndicatorInsets = contentInsets 81 | } 82 | 83 | } 84 | 85 | 86 | extension ColorCodesController : UITableViewDataSource { 87 | 88 | func numberOfSections(in tableView: UITableView) -> Int { 89 | return 1 90 | } 91 | 92 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 93 | return 7 94 | } 95 | 96 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 97 | 98 | if indexPath.row == 6 { 99 | let cell = tableView.dequeueReusableCell(withIdentifier: "colorCodeCustomCellId", for: indexPath) as! ColorCodeCustomCell 100 | cell.selectionStyle = .none 101 | cell.colorCodeView.layer.borderWidth = 1 102 | cell.colorCodeView.layer.borderColor = UIColor(red: 151/255, green: 151/255, blue: 151/255, alpha: 1).cgColor 103 | if hexString != nil { 104 | if Colors.isCustomColor(hex: hexString!) { 105 | var hex = hexString! 106 | if (hex.hasPrefix("#")) { 107 | hex.remove(at: hex.startIndex) 108 | } 109 | cell.colorCodeTextField.text = "#\(hex)" 110 | cell.colorCodeView.backgroundColor = Colors.hexStringToUIColor(hex: hex) 111 | } 112 | } 113 | return cell 114 | 115 | } else { 116 | let cell = tableView.dequeueReusableCell(withIdentifier: "colorCodePresetCellId", for: indexPath) as! ColorCodePresetCell 117 | if indexPath.row == 0 { 118 | cell.colorCodeLabel.text = "Gold - #FFBF00" 119 | cell.colorCodeView.backgroundColor = UIColor(red: 255/255, green: 191/255, blue: 0, alpha: 1) 120 | } else if indexPath.row == 1 { 121 | cell.colorCodeLabel.text = "Silver - #7C8288" 122 | cell.colorCodeView.backgroundColor = UIColor(red: 124/255, green: 130/255, blue: 136/255, alpha: 1) 123 | } else if indexPath.row == 2 { 124 | cell.colorCodeLabel.text = "Ruby - #E51522" 125 | cell.colorCodeView.backgroundColor = UIColor(red: 229/255, green: 21/255, blue: 34/255, alpha: 1) 126 | } else if indexPath.row == 3 { 127 | cell.colorCodeLabel.text = "Emmerald - #00B382" 128 | cell.colorCodeView.backgroundColor = UIColor(red: 0, green: 179/255, blue: 130/255, alpha: 1) 129 | } else if indexPath.row == 4 { 130 | cell.colorCodeLabel.text = "Saphire - #1538C0" 131 | cell.colorCodeView.backgroundColor = UIColor(red: 21/255, green: 56/255, blue: 192/255, alpha: 1) 132 | } else if indexPath.row == 5 { 133 | cell.colorCodeLabel.text = "Amethyst - #6F0DBE" 134 | cell.colorCodeView.backgroundColor = UIColor(red: 111/255, green: 13/255, blue: 190/255, alpha: 1) 135 | } 136 | return cell 137 | 138 | } 139 | } 140 | } 141 | 142 | extension ColorCodesController : UITableViewDelegate { 143 | 144 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 145 | 146 | if indexPath.row < 6 { 147 | tableView.deselectRow(at: indexPath, animated: true) 148 | let cell = tableView.cellForRow(at: indexPath) as! ColorCodePresetCell 149 | selection.selectionChanged() 150 | delegate.newColorCode(hex: cell.colorCodeLabel.text!) 151 | navigationController?.popViewController(animated: true) 152 | } else { 153 | let cell = tableView.cellForRow(at: indexPath) as! ColorCodeCustomCell 154 | cell.colorCodeTextField.becomeFirstResponder() 155 | } 156 | } 157 | } 158 | 159 | extension ColorCodesController : UITextFieldDelegate { 160 | 161 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 162 | view.endEditing(true) 163 | if textField.text! != "" { 164 | selection.selectionChanged() 165 | delegate.newColorCode(hex: textField.text!) 166 | navigationController?.popViewController(animated: true) 167 | } 168 | return true 169 | } 170 | 171 | func textFieldDidBeginEditing(_ textField: UITextField) { 172 | self.activeField = textField 173 | } 174 | 175 | func textFieldDidEndEditing(_ textField: UITextField) { 176 | self.activeField = nil 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/CreateTransactionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateTransactionController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | import Blockstack 12 | 13 | protocol CreateTransactionDelegate { 14 | func newTransaction(newHolding: Holding) 15 | } 16 | 17 | class CreateTransactionController: UIViewController { 18 | 19 | @IBOutlet weak var tableView : UITableView! 20 | var transaction : Transaction! 21 | var holding : Holding! 22 | var transactionType = ["Credit", "Debit"] 23 | var selectedTransaction : String! = "Credit" 24 | var transactionPickerView : UIPickerView! 25 | var toolBar = UIToolbar() 26 | var delegate : CreateTransactionDelegate! 27 | let impact = UIImpactFeedbackGenerator() 28 | let selection = UISelectionFeedbackGenerator() 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | navigationController!.navigationBar.titleTextAttributes = 34 | [NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 18)!, 35 | NSAttributedString.Key.foregroundColor : UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)] 36 | navigationController?.navigationBar.tintColor = UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1) 37 | 38 | transactionPickerView = UIPickerView(frame: CGRect(x: 0, y: view.frame.size.height, width: view.frame.size.width, height: 200)) 39 | transactionPickerView.delegate = self 40 | transactionPickerView.dataSource = self as UIPickerViewDataSource 41 | transactionPickerView.showsSelectionIndicator = true 42 | 43 | let btnDone = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(removeKeyboard)) 44 | let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 45 | 46 | toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 44)) 47 | toolBar.isUserInteractionEnabled = true 48 | toolBar.barStyle = .default 49 | toolBar.isTranslucent = false 50 | toolBar.items = [spaceButton, btnDone] 51 | } 52 | 53 | @objc func removeKeyboard() { 54 | self.view.endEditing(true) 55 | } 56 | 57 | @IBAction func save() { 58 | 59 | let indexPath = IndexPath(row: 0, section: 0) 60 | let cell = tableView.cellForRow(at: indexPath) as! TransactionNameCell 61 | let transactionNameTextField = cell.transactionNameTextField 62 | 63 | if transactionNameTextField!.text == "" { 64 | impact.impactOccurred() 65 | let alert = UIAlertController(title: "Oops!", message: "Enter a name for this transaction.", preferredStyle: .alert) 66 | alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .default, handler: nil)) 67 | self.present(alert, animated: true, completion: nil) 68 | return 69 | } 70 | 71 | let indexPathValue = IndexPath(row: 2, section: 0) 72 | let cellForValue = tableView.cellForRow(at: indexPathValue) as! TotalValueCell 73 | let valueTextField = cellForValue.totalValueTextField 74 | if valueTextField?.text?.doubleValue == nil || valueTextField?.text?.doubleValue == 0 { 75 | impact.impactOccurred() 76 | let alert = UIAlertController(title: "Oops!", message: "Invalid value.", preferredStyle: .alert) 77 | alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .default, handler: nil)) 78 | self.present(alert, animated: true, completion: nil) 79 | return 80 | } 81 | 82 | print("save") 83 | 84 | var newTransaction : TransactionManagedObject 85 | if transaction == nil { 86 | newTransaction = TransactionManagedObject(context: NSManagedObjectContext.mr_default()) 87 | newTransaction.id = newTransaction.objectID.uriRepresentation().lastPathComponent 88 | let transactionDate = Date() 89 | let dateFormatter = DateFormatter() 90 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 91 | let dateString = dateFormatter.string(from: transactionDate) 92 | newTransaction.date = dateString 93 | } else { 94 | newTransaction = TransactionManagedObject.mr_findFirst(byAttribute: "id", withValue: transaction.identifier!, in: NSManagedObjectContext.mr_default())! 95 | } 96 | 97 | newTransaction.name = transactionNameTextField!.text 98 | 99 | let holdingManagedObject = HoldingManagedObject.mr_findFirst(byAttribute: "name", withValue: holding.name!, in: NSManagedObjectContext.mr_default()) 100 | newTransaction.holding = holdingManagedObject 101 | 102 | if selectedTransaction == "Credit" { 103 | newTransaction.type = "credit" 104 | } else { 105 | newTransaction.type = "debit" 106 | } 107 | 108 | if let totalValue = valueTextField?.text?.doubleValue { 109 | newTransaction.value = totalValue 110 | if selectedTransaction == "Credit" { 111 | print("totalValue \(totalValue)") 112 | if transaction == nil { 113 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue + totalValue as NSNumber 114 | } else { 115 | if transaction.type == "credit" { 116 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue + (totalValue-transaction.value!) as NSNumber 117 | } else { 118 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue + (totalValue+transaction.value) as NSNumber 119 | } 120 | } 121 | } else { 122 | if transaction == nil { 123 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue - totalValue as NSNumber 124 | } else { 125 | if transaction.type == "debit" { 126 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue - (totalValue-transaction.value!) as NSNumber 127 | } else { 128 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue - (totalValue+transaction.value!) as NSNumber 129 | } 130 | } 131 | } 132 | 133 | } 134 | 135 | NSManagedObjectContext.mr_default().mr_saveToPersistentStoreAndWait() 136 | selection.selectionChanged() 137 | 138 | if Blockstack.shared.isUserSignedIn() { 139 | BlockstackApiService().send { errorMessage in 140 | if errorMessage != nil { 141 | self.impact.impactOccurred() 142 | let alert = UIAlertController(title: "Error", 143 | message: errorMessage, 144 | preferredStyle: .alert) 145 | alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) 146 | self.present(alert, animated: true, completion: nil) 147 | } 148 | } 149 | } 150 | 151 | if delegate != nil { 152 | var newHolding = HoldingDto().holding(from: holdingManagedObject!) 153 | newHolding = HoldingsUseCase(with: HoldingsRepositoryImpl()).updateHoldingWithComputedProperties(holding: newHolding) 154 | delegate.newTransaction(newHolding: newHolding) 155 | } 156 | 157 | navigationController?.popViewController(animated: true) 158 | } 159 | } 160 | 161 | extension CreateTransactionController : UITableViewDataSource { 162 | 163 | func numberOfSections(in tableView: UITableView) -> Int { 164 | return 1 165 | } 166 | 167 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 168 | return 3 169 | } 170 | 171 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 172 | 173 | if indexPath.row == 0 { 174 | let cell = tableView.dequeueReusableCell(withIdentifier: "transactionNameCellId", for: indexPath) as! TransactionNameCell 175 | if transaction != nil { 176 | cell.transactionNameTextField.text = transaction.name 177 | } 178 | return cell 179 | 180 | } else if indexPath.row == 1 { 181 | let cell = tableView.dequeueReusableCell(withIdentifier: "typeCellId", for: indexPath) as! TransactionTypeCell 182 | if transaction?.type == "debit" { 183 | cell.transactionTypeTextField.text = "Debit" 184 | } 185 | cell.transactionTypeTextField.inputView = transactionPickerView 186 | cell.transactionTypeTextField.inputAccessoryView = toolBar 187 | return cell 188 | 189 | } else { 190 | let cell = tableView.dequeueReusableCell(withIdentifier: "valueCellId", for: indexPath) as! TotalValueCell 191 | cell.currencyLabel.text = holding.currency!.symbol 192 | if transaction != nil { 193 | cell.totalValueTextField.text = "\(transaction.value!)" 194 | } 195 | return cell 196 | } 197 | } 198 | } 199 | 200 | extension CreateTransactionController : UITableViewDelegate { 201 | 202 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 203 | 204 | tableView.deselectRow(at: indexPath, animated: true) 205 | if indexPath.row == 1 { 206 | let cell = tableView.cellForRow(at: indexPath) as! TransactionTypeCell 207 | cell.transactionTypeTextField.becomeFirstResponder() 208 | } else { 209 | removeKeyboard() 210 | } 211 | } 212 | } 213 | 214 | extension CreateTransactionController : UITextFieldDelegate { 215 | 216 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 217 | view.endEditing(true) 218 | return true 219 | } 220 | } 221 | 222 | extension CreateTransactionController : UIPickerViewDataSource { 223 | 224 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 225 | return 1 226 | } 227 | 228 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 229 | return transactionType.count 230 | } 231 | 232 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 233 | return transactionType[row] 234 | } 235 | } 236 | 237 | extension CreateTransactionController : UIPickerViewDelegate { 238 | 239 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 240 | let cell = tableView.cellForRow(at: IndexPath(row: 1, section: 0)) as! TransactionTypeCell 241 | cell.transactionTypeTextField.text = transactionType[row] 242 | selectedTransaction = transactionType[row] 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /Lannister/App/Controllers/HoldingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HoldingController.swift 3 | // Lannister 4 | // 5 | // Created by André Sousa on 26/04/2019. 6 | // Copyright © 2019 André Sousa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MagicalRecord 11 | import Charts 12 | import Blockstack 13 | import Web3swift 14 | 15 | class HoldingController: UIViewController { 16 | 17 | var euroTotalValue : Double! 18 | @IBOutlet weak var barView : UIView! 19 | @IBOutlet weak var valueLabel : UILabel! 20 | @IBOutlet weak var defaultCurrencyValueLabel : UILabel! 21 | @IBOutlet weak var pieChartViewTopConstraint : NSLayoutConstraint! 22 | @IBOutlet weak var pieChartView : PieChartView! 23 | @IBOutlet weak var tableView : UITableView! 24 | var holding : Holding! 25 | var currency : Currency! 26 | var transactions : [Transaction]! = [] 27 | var pieChartDataEntries = [PieChartDataEntry]() 28 | var pieChartDataColors = [UIColor]() 29 | var numberFormatter = NumberFormatter() 30 | var percentFormatter = NumberFormatter() 31 | 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | navigationItem.title = "Holding" 37 | 38 | // Set number formatter display 39 | numberFormatter.numberStyle = .decimal 40 | numberFormatter.minimumFractionDigits = 2 41 | numberFormatter.maximumFractionDigits = 2 42 | 43 | percentFormatter.numberStyle = .decimal 44 | percentFormatter.minimumFractionDigits = 0 45 | percentFormatter.maximumFractionDigits = 2 46 | 47 | navigationController!.navigationBar.titleTextAttributes = 48 | [NSAttributedString.Key.font: UIFont(name: "AvenirNext-Medium", size: 18)!, 49 | NSAttributedString.Key.foregroundColor : UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)] 50 | navigationController?.navigationBar.tintColor = UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1) 51 | 52 | navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) 53 | navigationController?.navigationBar.shadowImage = UIImage() 54 | 55 | let editButton = UIBarButtonItem() 56 | editButton.title = "Edit" 57 | editButton.target = self 58 | editButton.action = #selector(editHolding) 59 | navigationItem.rightBarButtonItem = editButton 60 | 61 | let backItem = UIBarButtonItem() 62 | backItem.title = "" 63 | navigationItem.backBarButtonItem = backItem 64 | 65 | updateHolding() 66 | updatePieChart() 67 | } 68 | 69 | override func viewWillAppear(_ animated: Bool) { 70 | super.viewWillAppear(animated) 71 | 72 | updateTransactions() 73 | } 74 | 75 | func updateHolding() { 76 | 77 | navigationItem.title = holding.name 78 | barView.backgroundColor = Colors.hexStringToUIColor(hex: holding.hexColor) 79 | euroTotalValue = PortfolioUseCase(with: PortfolioRepositoryImpl()).getEuroTotalValue() 80 | barView.backgroundColor = Colors.hexStringToUIColor(hex: holding.hexColor) 81 | navigationItem.title = holding.name 82 | 83 | var holdingCurrency = holding.currency 84 | var holdingValue : Double? = holding.value 85 | if holding.address != nil { 86 | // Get ETH 87 | let token = getToken() 88 | holdingValue = token.value 89 | holdingCurrency = token.currency 90 | currency = holdingCurrency 91 | } else { 92 | currency = holding.currency 93 | } 94 | 95 | let formattedNumber = numberFormatter.string(for: NSNumber(value: holdingValue!)) 96 | valueLabel.text = String(format: "%@%@", holdingCurrency!.symbol, formattedNumber ?? "--") 97 | 98 | if holdingCurrency!.symbol == Currencies.getDefaultCurrencySymbol() { 99 | // remove label 100 | if defaultCurrencyValueLabel != nil { 101 | defaultCurrencyValueLabel.removeFromSuperview() 102 | defaultCurrencyValueLabel = nil 103 | } 104 | pieChartViewTopConstraint.constant = 8 105 | } else { 106 | let euroValue = Currencies.getEuroValue(value: holdingValue!, currency: holdingCurrency!) 107 | let currencyValue = euroValue * Currencies.getDefaultCurrencyEuroRate() 108 | let formattedNumber = numberFormatter.string(for: NSNumber(value: currencyValue)) 109 | defaultCurrencyValueLabel.text = String(format: "%@%@", Currencies.getDefaultCurrencySymbol(), formattedNumber ?? "--") 110 | } 111 | } 112 | 113 | func updatePieChart() { 114 | 115 | pieChartDataEntries.removeAll() 116 | pieChartDataColors.removeAll() 117 | 118 | var holdingCurrency = holding.currency 119 | var holdingValue : Double? = holding.value 120 | if holding.address != nil { 121 | // Get ETH 122 | let token = getToken() 123 | holdingValue = token.value 124 | holdingCurrency = token.currency 125 | } 126 | if holdingValue! < 0 { 127 | holdingValue = 0 128 | } 129 | 130 | let euroValue = Currencies.getEuroValue(value: holdingValue!, currency: holdingCurrency!) 131 | 132 | let pieChartDataEntry = PieChartDataEntry(value: euroValue, label: nil) 133 | pieChartDataEntries.append(pieChartDataEntry) 134 | pieChartDataColors.append(Colors.hexStringToUIColor(hex: holding.hexColor)) 135 | 136 | let secondPieChartDataEntry = PieChartDataEntry(value: euroTotalValue-euroValue, label: nil) 137 | pieChartDataEntries.append(secondPieChartDataEntry) 138 | pieChartDataColors.append(UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 0.7)) 139 | 140 | pieChartView.chartDescription?.text = "" 141 | let percentage = percentFormatter.string(for: NSNumber(value: euroValue/euroTotalValue*100)) 142 | let attributedString = NSAttributedString(string: "\(percentage ?? "")%", 143 | attributes: [ NSAttributedString.Key.font: UIFont(name: "AvenirNext-DemiBold", size: 20)!, 144 | NSAttributedString.Key.foregroundColor: UIColor(red: 118/255, green: 134/255, blue: 162/255, alpha: 1)]) 145 | pieChartView.centerAttributedText = attributedString 146 | pieChartView.drawHoleEnabled = true 147 | pieChartView.holeColor = UIColor.clear 148 | pieChartView.drawEntryLabelsEnabled = false 149 | pieChartView.holeRadiusPercent = 0.80 150 | pieChartView.legend.enabled = false 151 | 152 | let chartDataSet = PieChartDataSet(entries: pieChartDataEntries, label: nil) 153 | chartDataSet.colors = pieChartDataColors 154 | chartDataSet.drawValuesEnabled = false 155 | let chartData = PieChartData(dataSet: chartDataSet) 156 | pieChartView.data = chartData 157 | } 158 | 159 | func updateTransactions() { 160 | 161 | if holding.address == nil { 162 | transactions = holding.transactions 163 | tableView.reloadData() 164 | } else { 165 | // Get ETH token 166 | let token = getToken() 167 | 168 | if token.transactions != nil { 169 | self.transactions = token.transactions 170 | self.tableView.reloadData() 171 | } else { 172 | getTransactions() 173 | } 174 | } 175 | } 176 | 177 | func getTransactions() { 178 | 179 | WalletUseCase(with: WalletRepositoryImpl()).getTransactions(address: holding.address!, success: { transactionsObjects in 180 | DispatchQueue.main.async { 181 | if transactionsObjects != nil { 182 | self.transactions = transactionsObjects 183 | self.tableView.reloadData() 184 | 185 | // save new transactions to local db 186 | let oldTransactions = self.getTokenManagedObject().transactions 187 | if (oldTransactions?.count)! > 0 { 188 | for transaction in oldTransactions! { 189 | (transaction as! TransactionManagedObject).mr_deleteEntity(in: NSManagedObjectContext.mr_default()) 190 | } 191 | } 192 | for transaction in self.transactions { 193 | let newTransaction = TransactionManagedObject(context: NSManagedObjectContext.mr_default()) 194 | newTransaction.name = transaction.name 195 | newTransaction.value = transaction.value 196 | newTransaction.type = transaction.type 197 | newTransaction.id = transaction.identifier 198 | } 199 | NSManagedObjectContext.mr_default().mr_saveToPersistentStoreAndWait() 200 | 201 | } else { 202 | let token = self.getToken() 203 | if token.transactions == nil { 204 | // Display warning to user 205 | let alert = UIAlertController(title: "Error", 206 | message: "Unable to fetch transactions this time. Try again by tapping on the \"Refresh\" button.", 207 | preferredStyle: .alert) 208 | alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) 209 | self.present(alert, animated: true, completion: nil) 210 | } 211 | } 212 | } 213 | }) { error in 214 | print("could not get transactions") 215 | } 216 | } 217 | 218 | func getTokenManagedObject() -> TokenManagedObject { 219 | 220 | let predicateAddress = NSPredicate(format: "address == %@", holding.address!) 221 | let predicateCode = NSPredicate(format: "code == %@", "ETH") 222 | let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateAddress, predicateCode]) 223 | 224 | let tokenManagedObject = TokenManagedObject.mr_findFirst(with: compoundPredicate, in: NSManagedObjectContext.mr_default())! 225 | 226 | return tokenManagedObject 227 | } 228 | 229 | func getToken() -> Token { 230 | 231 | let token = TokenDto().token(from: getTokenManagedObject()) 232 | 233 | return token 234 | } 235 | 236 | @IBAction func createTransaction() { 237 | let createTransactionVC = storyboard?.instantiateViewController(withIdentifier: "transactionVC") as! CreateTransactionController 238 | createTransactionVC.holding = holding 239 | createTransactionVC.delegate = self 240 | navigationController?.pushViewController(createTransactionVC, animated: true) 241 | } 242 | 243 | @IBAction func refreshTransactions() { 244 | 245 | getTransactions() 246 | } 247 | 248 | @objc func editHolding() { 249 | 250 | let createHoldingVC = storyboard?.instantiateViewController(withIdentifier: "createHoldingVC") as! CreateHoldingController 251 | createHoldingVC.holding = holding 252 | createHoldingVC.euroTotalValue = euroTotalValue 253 | createHoldingVC.delegate = self 254 | navigationController?.pushViewController(createHoldingVC, animated: true) 255 | } 256 | 257 | } 258 | 259 | extension HoldingController : UITableViewDataSource { 260 | 261 | func numberOfSections(in tableView: UITableView) -> Int { 262 | return 1 263 | } 264 | 265 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 266 | if transactions == nil || transactions?.count == 0 { 267 | return 1 268 | } 269 | return transactions.count+1 270 | } 271 | 272 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 273 | if indexPath.row == 0 && holding.address != nil { 274 | return 54 275 | } 276 | return 44 277 | } 278 | 279 | func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 280 | if holding.address == nil { 281 | return true 282 | } 283 | return false 284 | } 285 | 286 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 287 | if editingStyle == .delete { 288 | 289 | let transaction = transactions[indexPath.row-1] 290 | 291 | let holdingManagedObject = HoldingManagedObject.mr_findFirst(byAttribute: "name", withValue: holding.name!, in: NSManagedObjectContext.mr_default()) 292 | if transaction.type == "credit" { 293 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue - transaction.value! as NSNumber 294 | } else { 295 | holdingManagedObject!.value = holdingManagedObject!.value!.doubleValue + transaction.value! as NSNumber 296 | } 297 | 298 | let transactionManagedObject = TransactionManagedObject.mr_findFirst(byAttribute: "id", withValue: transaction.identifier!, in: NSManagedObjectContext.mr_default()) 299 | transactionManagedObject?.mr_deleteEntity(in: NSManagedObjectContext.mr_default()) 300 | NSManagedObjectContext.mr_default().mr_saveToPersistentStoreAndWait() 301 | 302 | if Blockstack.shared.isUserSignedIn() { 303 | BlockstackApiService().send { errorMessage in 304 | if errorMessage != nil { 305 | let alert = UIAlertController(title: "Error", 306 | message: errorMessage, 307 | preferredStyle: .alert) 308 | alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) 309 | self.present(alert, animated: true, completion: nil) 310 | } 311 | } 312 | } 313 | 314 | transactions.remove(at: indexPath.row-1) 315 | 316 | holding = HoldingDto().holding(from: holdingManagedObject!) 317 | 318 | euroTotalValue = PortfolioUseCase(with: PortfolioRepositoryImpl()).getEuroTotalValue() 319 | updateHolding() 320 | updatePieChart() 321 | 322 | tableView.deleteRows(at: [indexPath], with: .fade) 323 | } 324 | 325 | } 326 | 327 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 328 | if indexPath.row == 0 { 329 | let cell = tableView.dequeueReusableCell(withIdentifier: "topCellId", for: indexPath) as! HoldingTopCell 330 | cell.addButton.layer.cornerRadius = 4 331 | if holding.address == nil { 332 | if cell.poweredByLabel != nil { 333 | cell.poweredByLabel.removeFromSuperview() 334 | } 335 | } else { 336 | cell.addButton.setTitle("Refresh Transactions", for: .normal) 337 | cell.addButton.removeTarget(self, action: #selector(createTransaction), for: .touchUpInside) 338 | cell.addButton.addTarget(self, action: #selector(refreshTransactions), for: .touchUpInside) 339 | } 340 | return cell 341 | } else { 342 | let cell = tableView.dequeueReusableCell(withIdentifier: "transactionCellId", for: indexPath) as! TransactionCell 343 | let transaction = transactions[indexPath.row-1] 344 | cell.nameLabel.text = transaction.name 345 | let formattedNumber = numberFormatter.string(for: NSNumber(value: transaction.value!)) 346 | if(transaction.type == "credit") { 347 | cell.valueLabel.text = "+ \(currency!.symbol!)\(formattedNumber ?? "--")" 348 | cell.colorView.backgroundColor = Colors.hexStringToUIColor(hex: "00B382") 349 | cell.valueLabel.textColor = Colors.hexStringToUIColor(hex: "00B382") 350 | } else { 351 | cell.valueLabel.text = "- \(currency!.symbol!)\(formattedNumber ?? "--")" 352 | cell.colorView.backgroundColor = Colors.hexStringToUIColor(hex: "E60243") 353 | cell.valueLabel.textColor = Colors.hexStringToUIColor(hex: "E60243") 354 | } 355 | return cell 356 | } 357 | } 358 | 359 | } 360 | 361 | extension HoldingController : UITableViewDelegate { 362 | 363 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 364 | 365 | if indexPath.row > 0 && holding.address == nil { 366 | tableView.deselectRow(at: indexPath, animated: true) 367 | 368 | let transaction = transactions[indexPath.row-1] 369 | 370 | let transactionVC = storyboard?.instantiateViewController(withIdentifier: "transactionVC") as! CreateTransactionController 371 | transactionVC.transaction = transaction 372 | transactionVC.holding = holding 373 | transactionVC.delegate = self 374 | navigationController?.pushViewController(transactionVC, animated: true) 375 | } 376 | } 377 | } 378 | 379 | extension HoldingController : EditHoldingDelegate { 380 | 381 | func updateHolding(newHolding: Holding) { 382 | 383 | holding = newHolding 384 | updateHolding() 385 | updatePieChart() 386 | } 387 | } 388 | 389 | extension HoldingController : CreateTransactionDelegate { 390 | 391 | func newTransaction(newHolding: Holding) { 392 | 393 | holding = newHolding 394 | updateHolding() 395 | updatePieChart() 396 | } 397 | } 398 | --------------------------------------------------------------------------------