├── Assets
├── PSD
│ ├── nova_logo.psd
│ ├── nova_github_header.psd
│ └── nova_logo_transparent.psd
└── PNG
│ ├── nova_screenshot.png
│ ├── nova_github_header.png
│ ├── nova_logo_transparent.png
│ └── nova_logo_transparent_dark.png
├── Nova
├── support
│ ├── pdf
│ │ ├── license.pdf
│ │ └── acknowledgements.pdf
│ ├── Schedulers.swift
│ ├── EventMonitor.swift
│ ├── Constants.swift
│ ├── Extensions.swift
│ ├── Prefs.swift
│ └── PriceFormatter.swift
├── Assets.xcassets
│ ├── Contents.json
│ ├── ic_eth.imageset
│ │ ├── ic_eth.png
│ │ └── Contents.json
│ ├── ic_neo.imageset
│ │ ├── ic_neo.png
│ │ └── Contents.json
│ ├── ic_mail.imageset
│ │ ├── ic_mail.png
│ │ └── Contents.json
│ ├── ic_menu.imageset
│ │ ├── ic_menu.png
│ │ └── Contents.json
│ ├── ic_github.imageset
│ │ ├── ic_github.png
│ │ └── Contents.json
│ ├── ic_phone.imageset
│ │ ├── ic_phone.png
│ │ └── Contents.json
│ ├── ic_search.imageset
│ │ ├── ic_search.png
│ │ └── Contents.json
│ ├── nova_logo.imageset
│ │ ├── nova_logo.png
│ │ └── Contents.json
│ ├── ic_bitcoin.imageset
│ │ ├── ic_bitcoin.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── nov_logo_512-1.png
│ │ ├── nov_logo_512.png
│ │ ├── nova_logo_1024.png
│ │ ├── nova_logo_128.png
│ │ ├── nova_logo_16.png
│ │ ├── nova_logo_256.png
│ │ ├── nova_logo_32-1.png
│ │ ├── nova_logo_32.png
│ │ ├── nova_logo_64.png
│ │ ├── nova_logo_256-1.png
│ │ └── Contents.json
│ ├── ic_ethereum.imageset
│ │ ├── ic_ethereum.png
│ │ └── Contents.json
│ ├── ic_linkedin.imageset
│ │ ├── ic_linkedin.png
│ │ └── Contents.json
│ ├── ic_pin.imageset
│ │ ├── icons8-Push Pin.png
│ │ └── Contents.json
│ ├── ic_settings.imageset
│ │ ├── icons8-Settings-50.png
│ │ └── Contents.json
│ ├── ic_restart.imageset
│ │ ├── icons8-Restart Filled.png
│ │ └── Contents.json
│ ├── ic_checkmark.imageset
│ │ ├── icons8-Checkmark Filled.png
│ │ └── Contents.json
│ ├── ic_pin_filled.imageset
│ │ ├── icons8-Push Pin Filled.png
│ │ └── Contents.json
│ ├── ic_checkmark_light.imageset
│ │ ├── icons8-Checkmark Filled.png
│ │ └── Contents.json
│ ├── nova_logo_transparent.imageset
│ │ ├── nova_logo_transparent.png
│ │ └── Contents.json
│ └── nova_logo_transparent_dark.imageset
│ │ ├── nova_logo_transparent_dark.png
│ │ └── Contents.json
├── Nova.entitlements
├── view
│ ├── tickerlist
│ │ ├── TickerListCellView.swift
│ │ ├── TickerListViewModel.swift
│ │ └── TickerListViewController.swift
│ ├── DonateViewController.swift
│ ├── ContactViewController.swift
│ └── menubar
│ │ ├── MenuBarViewModel.swift
│ │ └── MenuBarView.swift
├── Info.plist
├── data
│ ├── firebase
│ │ ├── FirebaseProvider.swift
│ │ └── News.swift
│ ├── cryptocompare
│ │ └── CryptoCompareProvider.swift
│ └── coinmarketcap
│ │ ├── CoinMarketCapProvider.swift
│ │ └── Ticker.swift
├── AppDelegate.swift
├── repository
│ ├── RemoteDataSource.swift
│ ├── LocalDataSource.swift
│ └── DataRepository.swift
└── Injector.swift
├── Nova.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── Nova.xcworkspace
└── contents.xcworkspacedata
├── Podfile
├── NovaTests
├── Info.plist
└── NovaTests.swift
├── README.md
├── .gitignore
├── Podfile.lock
└── LICENSE
/Assets/PSD/nova_logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PSD/nova_logo.psd
--------------------------------------------------------------------------------
/Nova/support/pdf/license.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/support/pdf/license.pdf
--------------------------------------------------------------------------------
/Assets/PNG/nova_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PNG/nova_screenshot.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Assets/PNG/nova_github_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PNG/nova_github_header.png
--------------------------------------------------------------------------------
/Assets/PSD/nova_github_header.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PSD/nova_github_header.psd
--------------------------------------------------------------------------------
/Assets/PNG/nova_logo_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PNG/nova_logo_transparent.png
--------------------------------------------------------------------------------
/Assets/PSD/nova_logo_transparent.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PSD/nova_logo_transparent.psd
--------------------------------------------------------------------------------
/Nova/support/pdf/acknowledgements.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/support/pdf/acknowledgements.pdf
--------------------------------------------------------------------------------
/Assets/PNG/nova_logo_transparent_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Assets/PNG/nova_logo_transparent_dark.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_eth.imageset/ic_eth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_eth.imageset/ic_eth.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_neo.imageset/ic_neo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_neo.imageset/ic_neo.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_mail.imageset/ic_mail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_mail.imageset/ic_mail.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_menu.imageset/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_menu.imageset/ic_menu.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_github.imageset/ic_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_github.imageset/ic_github.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_phone.imageset/ic_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_phone.imageset/ic_phone.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_search.imageset/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_search.imageset/ic_search.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/nova_logo.imageset/nova_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/nova_logo.imageset/nova_logo.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_bitcoin.imageset/ic_bitcoin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_bitcoin.imageset/ic_bitcoin.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nov_logo_512-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nov_logo_512-1.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nov_logo_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nov_logo_512.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_1024.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_128.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_16.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_256.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_32-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_32-1.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_32.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_64.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_ethereum.imageset/ic_ethereum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_ethereum.imageset/ic_ethereum.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_linkedin.imageset/ic_linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_linkedin.imageset/ic_linkedin.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_pin.imageset/icons8-Push Pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_pin.imageset/icons8-Push Pin.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_256-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/AppIcon.appiconset/nova_logo_256-1.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_settings.imageset/icons8-Settings-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_settings.imageset/icons8-Settings-50.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_restart.imageset/icons8-Restart Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_restart.imageset/icons8-Restart Filled.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_checkmark.imageset/icons8-Checkmark Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_checkmark.imageset/icons8-Checkmark Filled.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_pin_filled.imageset/icons8-Push Pin Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_pin_filled.imageset/icons8-Push Pin Filled.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_checkmark_light.imageset/icons8-Checkmark Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/ic_checkmark_light.imageset/icons8-Checkmark Filled.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/nova_logo_transparent.imageset/nova_logo_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/nova_logo_transparent.imageset/nova_logo_transparent.png
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/nova_logo_transparent_dark.imageset/nova_logo_transparent_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndrejJurkin/nova-osx/HEAD/Nova/Assets.xcassets/nova_logo_transparent_dark.imageset/nova_logo_transparent_dark.png
--------------------------------------------------------------------------------
/Nova.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Nova.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Nova/Nova.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_eth.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_eth.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_mail.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_mail.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_menu.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_menu.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_neo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_neo.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_phone.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_phone.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_bitcoin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_bitcoin.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_github.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_github.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_pin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icons8-Push Pin.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_search.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_search.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/nova_logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "nova_logo.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_ethereum.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_ethereum.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_linkedin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_linkedin.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_restart.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icons8-Restart Filled.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_settings.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icons8-Settings-50.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_checkmark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icons8-Checkmark Filled.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_pin_filled.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icons8-Push Pin Filled.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/ic_checkmark_light.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icons8-Checkmark Filled.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/nova_logo_transparent.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "nova_logo_transparent.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/nova_logo_transparent_dark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "nova_logo_transparent_dark.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :osx, '10.11'
3 |
4 | target 'Nova' do
5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | pod 'Alamofire'
9 | pod 'RxSwift'
10 | pod 'RxCocoa'
11 | pod 'RxAlamofire'
12 | pod 'Moya/RxSwift'
13 | pod 'RxOptional'
14 | pod 'Kingfisher', '~> 3.13.1'
15 | pod 'RealmSwift'
16 | pod 'RxRealm'
17 | pod 'Swinject'
18 | pod 'SwinjectAutoregistration'
19 | pod 'ObjectMapper'
20 | pod 'Moya-ObjectMapper', '~> 2.3.2'
21 | pod 'Moya-ObjectMapper/RxSwift', '~> 2.3.2'
22 | end
23 |
--------------------------------------------------------------------------------
/NovaTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 |
--------------------------------------------------------------------------------
/NovaTests/NovaTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NovaTests.swift
3 | // NovaTests
4 | //
5 | // Created by Andrej Jurkin on 9/2/17.
6 | // Copyright © 2017 Andrej Jurkin. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Nova
11 |
12 | class NovaTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Nova/view/tickerlist/TickerListCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // TickerListCellView.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 |
22 | import Foundation
23 | import Cocoa
24 |
25 | class TickerListCellView: NSTableCellView {
26 |
27 | @IBOutlet weak var currencyImageView: NSImageView!
28 |
29 | @IBOutlet weak var currencyName: NSTextField!
30 |
31 | @IBOutlet weak var currencyPrice: NSTextField!
32 |
33 | @IBOutlet weak var pinButton: NSButton!
34 | }
35 |
--------------------------------------------------------------------------------
/Nova/view/DonateViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | // DonateViewController.swift
17 | // Nova
18 | //
19 | // Created by Andrej Jurkin on 26/09/17.
20 | //
21 |
22 | import Cocoa
23 |
24 | class DonateViewController: NSViewController {
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 | // Do view setup here.
29 | }
30 |
31 | override func viewDidAppear() {
32 | super.viewDidAppear()
33 | self.setTransparentTitle()
34 | AppDelegate.shared().menuBarView?.hidePopover()
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [DOWNLOAD FROM APP STORE](https://itunes.apple.com/us/app/nova-menu-bar-ticker/id1290181512)
4 |
5 | Nova is a tiny menu bar widget that displays fresh crypto-currency prices (tickers). Simply select coins you are interested in and watch the prices change in your menu bar!
6 |
7 | Nova supports all major coins as well as over 1000 altcoins.
8 |
9 | Update interval is 15 seconds, it will be possible to configure this interval in the future.
10 |
11 | If you have any feedback or suggestions, please do not hesitate to contact me at andrej.jurkin@gmail.com.
12 |
13 | Data for the app is provided by [CryptoCompare](https://www.cryptocompare.com/api/) and [CoinMarketCap](https://coinmarketcap.com/api/), thanks!
14 |
15 | Enjoy!
16 |
17 | 
18 |
19 | ## Donate
20 |
21 | Nova is free and always will be! Your donation directly supports the development of this and other great tools.
22 |
23 | ##### BTC
24 | 1KXnhHv7JMgqzrefcbDhYXqR1cSfGJhR6x
25 |
26 | ##### ETH, ERC20
27 | 0xA88A216A1F9a1900721413314775f982816C040c
28 |
29 | ##### NEO
30 | APdpeDMqXHCx1q4x2N8t7xRvnUABjxQe6o
31 |
--------------------------------------------------------------------------------
/Nova/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 0.1.6
21 | CFBundleVersion
22 | 11
23 | LSApplicationCategoryType
24 | public.app-category.finance
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | LSUIElement
28 |
29 | NSHumanReadableCopyright
30 | Copyright © 2017 Andrej Jurkin. All rights reserved.
31 | NSMainStoryboardFile
32 | Main
33 | NSPrincipalClass
34 | NSApplication
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Nova/support/Schedulers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // Schedulers.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/22/17.
21 | //
22 |
23 |
24 | import Foundation
25 | import RxSwift
26 |
27 | class Schedulers {
28 |
29 | static var main: MainScheduler {
30 | return MainScheduler.instance
31 | }
32 |
33 | static var backgroundInteractive: ConcurrentDispatchQueueScheduler {
34 | return ConcurrentDispatchQueueScheduler(qos: .userInteractive)
35 | }
36 |
37 | static var background: ConcurrentDispatchQueueScheduler {
38 | return ConcurrentDispatchQueueScheduler(qos: .background)
39 | }
40 |
41 | static var backgroundDefault: ConcurrentDispatchQueueScheduler {
42 | return ConcurrentDispatchQueueScheduler(qos: .default)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Nova/support/EventMonitor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // EventMonitor.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/23/17.
21 | //
22 |
23 | import Foundation
24 | import Cocoa
25 |
26 | public class EventMonitor {
27 |
28 | private var monitor: Any?
29 |
30 | private let mask: NSEventMask
31 |
32 | private let handler: (NSEvent) -> ()
33 |
34 | public init(mask: NSEventMask, handler: @escaping (NSEvent) -> ()) {
35 | self.mask = mask
36 | self.handler = handler
37 | }
38 |
39 | deinit {
40 | self.stop()
41 | }
42 |
43 | func start() {
44 | self.monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)
45 | }
46 |
47 | func stop() {
48 | if let monitor = self.monitor {
49 | NSEvent.removeMonitor(monitor)
50 | self.monitor = nil
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build generated
2 | build/
3 | DerivedData/
4 |
5 | ## Various settings
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 |
16 | ## Other
17 | *.moved-aside
18 | *.xccheckout
19 | *.xcscmblueprint
20 |
21 | ## Obj-C/Swift specific
22 | *.hmap
23 | *.ipa
24 | *.dSYM.zip
25 | *.dSYM
26 |
27 | ## Playgrounds
28 | timeline.xctimeline
29 | playground.xcworkspace
30 |
31 | # Swift Package Manager
32 | #
33 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
34 | # Packages/
35 | # Package.pins
36 | # Package.resolved
37 | .build/
38 |
39 | # CocoaPods
40 | #
41 | # We recommend against adding the Pods directory to your .gitignore. However
42 | # you should judge for yourself, the pros and cons are mentioned at:
43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
44 | #
45 | Pods/
46 |
47 | # Carthage
48 | #
49 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
50 | # Carthage/Checkouts
51 |
52 | Carthage/Build
53 |
54 | # fastlane
55 | #
56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
57 | # screenshots whenever they are needed.
58 | # For more information about the recommended setup visit:
59 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
60 |
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots
64 | fastlane/test_output
--------------------------------------------------------------------------------
/Nova/data/firebase/FirebaseProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // FirebaseProvider.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 12/29/17.
21 | //
22 |
23 | import Foundation
24 | import Moya
25 |
26 | enum FirebaseProvider {
27 |
28 | case news
29 | }
30 |
31 | extension FirebaseProvider: TargetType {
32 | var baseURL: URL {
33 | return C.firebaseBaseUrl
34 | }
35 |
36 | var path: String {
37 | switch self {
38 | case .news:
39 | return "/news.json"
40 | }
41 | }
42 |
43 | var method: Moya.Method {
44 | return .get
45 | }
46 |
47 | var parameters: [String : Any]? {
48 | return nil
49 | }
50 |
51 | var parameterEncoding: ParameterEncoding {
52 | return URLEncoding.default
53 | }
54 |
55 | var task: Task {
56 | return .request
57 | }
58 |
59 | var sampleData: Data {
60 | return Data()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Nova/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "nova_logo_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "nova_logo_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "nova_logo_32-1.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "nova_logo_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "nova_logo_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "nova_logo_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "nova_logo_256-1.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "nov_logo_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "nov_logo_512-1.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "nova_logo_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Nova/view/ContactViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | // ContactViewController.swift
17 | // Nova
18 | //
19 | // Created by Andrej Jurkin on 26/09/17.
20 | //
21 |
22 | import Cocoa
23 |
24 | class ContactViewController: NSViewController {
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 | // Do view setup here.
29 |
30 | }
31 |
32 | override func viewDidAppear() {
33 | super.viewDidAppear()
34 | self.setTransparentTitle()
35 | AppDelegate.shared().menuBarView?.hidePopover()
36 | }
37 |
38 | @IBAction func onLinkedinClick(_ sender: Any) {
39 | if let url = URL(string: "https://www.linkedin.com/in/andrej-jurkin-9691379a/") {
40 | NSWorkspace.shared().open(url)
41 | }
42 | }
43 |
44 | @IBAction func onGithubClick(_ sender: Any) {
45 | if let url = URL(string: "https://github.com/andrejjurkin") {
46 | NSWorkspace.shared().open(url)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Nova/support/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // Constants.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 | import Foundation
23 | import Cocoa
24 |
25 | /// Project constants
26 | struct C {
27 |
28 | static let coinMarketCapBaseUrl = URL(string: "https://api.coinmarketcap.com/v1/")!
29 |
30 | static let firebaseBaseUrl = URL(string: "https://nova-48c48.firebaseio.com/")!
31 | }
32 |
33 | /// Resource constants
34 | struct R {
35 |
36 | struct String {
37 | static let appName = "N O V A"
38 | }
39 |
40 | struct Color {
41 | static let primary = NSColor(rgb: 0x193C56)
42 | static let primaryLight = NSColor(rgb: 0xEEEEEB)
43 | static let placeholderLight = NSColor(rgb: 0xCCCCCC)
44 | }
45 |
46 | struct Id {
47 |
48 | }
49 |
50 | struct Font {
51 |
52 | static let menuBarTitleAttributes = [NSFontAttributeName: NSFont(name: "Avenir", size: 12.0)!]
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Nova/data/firebase/News.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // News.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 12/30/17.
21 | //
22 |
23 | import Foundation
24 | import RealmSwift
25 | import ObjectMapper
26 |
27 | class News: RealmObject, Mappable {
28 |
29 | dynamic var id: Int = 0
30 | dynamic var title: String = ""
31 | dynamic var subtitle: String = ""
32 | dynamic var link: String = ""
33 | dynamic var imageUrl: String = ""
34 | dynamic var buttonTitle: String = ""
35 | dynamic var hidden: Bool = false
36 |
37 | required convenience init?(map: Map) {
38 | self.init()
39 | }
40 |
41 | func mapping(map: Map) {
42 | self.id <- map["id"]
43 | self.title <- map["title"]
44 | self.subtitle <- map["subtitle"]
45 | self.link <- map["link"]
46 | self.imageUrl <- map["imageUrl"]
47 | self.buttonTitle <- map["buttonTitle"]
48 | self.hidden <- map["hidden"]
49 | }
50 |
51 | override static func primaryKey() -> String? {
52 | return "id"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Nova/data/cryptocompare/CryptoCompareProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // CryptoCompareProvider.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/22/17.
21 | //
22 |
23 | import Foundation
24 | import Moya
25 |
26 | enum CryptoCompareProvider {
27 |
28 | case priceMulti(fromSymbols: [String], toSymbols: [String])
29 | }
30 |
31 | extension CryptoCompareProvider: TargetType {
32 |
33 | var baseURL: URL {
34 | return URL(string: "https://min-api.cryptocompare.com/")!
35 | }
36 |
37 | var path: String {
38 | switch self {
39 | case .priceMulti:
40 | return "data/pricemulti"
41 | }
42 | }
43 |
44 | var method: Moya.Method {
45 | switch self {
46 | default:
47 | return .get
48 | }
49 | }
50 |
51 | var parameters: [String : Any]? {
52 | switch self {
53 | case .priceMulti(let fromSymbols, let toSymbols):
54 |
55 | return [
56 | "fsyms": toArrayParam(data: fromSymbols),
57 | "tsyms": toArrayParam(data: toSymbols)
58 | ]
59 | }
60 | }
61 |
62 | var parameterEncoding: ParameterEncoding {
63 | return URLEncoding.queryString
64 | }
65 |
66 | var sampleData: Data {
67 | return Data()
68 | }
69 |
70 | var task: Task {
71 | return .request
72 | }
73 |
74 | /// Workaround for CryptoCompare api, it does not accept a standard notation
75 | func toArrayParam(data: [String]) -> String {
76 | return data.joined(separator: ",")
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Nova/data/coinmarketcap/CoinMarketCapProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // CoinMarketCapProvider.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Foundation
24 | import Moya
25 |
26 | enum CoinMarketCapProvider {
27 |
28 | case allTickers
29 |
30 | case topTickers(limit: Int)
31 |
32 | case ticker(currencyName: String)
33 | }
34 |
35 | extension CoinMarketCapProvider: TargetType {
36 |
37 | var baseURL: URL {
38 | return C.coinMarketCapBaseUrl
39 | }
40 |
41 | var path: String {
42 | switch self {
43 | case .allTickers:
44 | return "ticker"
45 | case .topTickers:
46 | return "ticker"
47 | case .ticker(let currencyName):
48 | return "ticker/\(currencyName)"
49 | }
50 | }
51 |
52 | var method: Moya.Method {
53 | switch self {
54 | default:
55 | return .get
56 | }
57 | }
58 |
59 | var parameters: [String : Any]? {
60 | switch self {
61 | case .allTickers:
62 | //TODO: pass target currency as a parameter
63 | return ["limit": 0, "convert": Prefs.shared.targetCurrency]
64 | case .topTickers(let limit):
65 | return ["limit": limit, "convert": Prefs.shared.targetCurrency]
66 | default:
67 | return nil
68 | }
69 | }
70 |
71 | var parameterEncoding: ParameterEncoding {
72 | return URLEncoding.default
73 | }
74 |
75 | var sampleData: Data {
76 | return Data()
77 | }
78 |
79 | var task: Task {
80 | return .request
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Nova/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // AppDelegate.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 | import Cocoa
23 | import RxSwift
24 | import RealmSwift
25 |
26 | @NSApplicationMain
27 | class AppDelegate: NSObject, NSApplicationDelegate {
28 |
29 | var menuBarView: MenuBarView?
30 |
31 | static func shared() -> AppDelegate {
32 | return NSApp.delegate as! AppDelegate
33 | }
34 |
35 | func applicationDidFinishLaunching(_ aNotification: Notification) {
36 | self.initRealm()
37 | self.menuBarView = MenuBarView()
38 | self.fileNotifications()
39 | }
40 |
41 | func initRealm() {
42 | let realmConfig = Realm.Configuration(deleteRealmIfMigrationNeeded: true)
43 | Realm.Configuration.defaultConfiguration = realmConfig
44 | }
45 |
46 | func clearRealm() {
47 | let realm = try! Realm()
48 |
49 | try! realm.write {
50 | realm.deleteAll()
51 | }
52 | }
53 |
54 | func onWakeNote(note: NSNotification) {
55 | self.menuBarView?.resume()
56 | }
57 |
58 | func onSleepNote(note: NSNotification) {
59 | self.menuBarView?.pause()
60 | }
61 |
62 | func fileNotifications() {
63 | NSWorkspace.shared().notificationCenter.addObserver(
64 | self, selector: #selector(onWakeNote(note:)),
65 | name: Notification.Name.NSWorkspaceDidWake, object: nil)
66 |
67 | NSWorkspace.shared().notificationCenter.addObserver(
68 | self, selector: #selector(onSleepNote(note:)),
69 | name: Notification.Name.NSWorkspaceWillSleep, object: nil)
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/Nova/data/coinmarketcap/Ticker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // Ticker.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Foundation
24 | import RealmSwift
25 | import ObjectMapper
26 |
27 | class Ticker: RealmObject, Mappable {
28 |
29 | // Realm primary key
30 | dynamic var symbol: String = ""
31 | dynamic var id: String = ""
32 | dynamic var name: String = ""
33 | dynamic var isPinned = false
34 | dynamic var price: Double = 0
35 | dynamic var priceBtc: Double = 0
36 | dynamic var rank: Int = 0
37 |
38 | required convenience init?(map: Map) {
39 | self.init()
40 | }
41 |
42 | func mapping(map: Map) {
43 | self.symbol <- map["symbol"]
44 | self.id <- map["id"]
45 | self.name <- map["name"]
46 |
47 | var priceUsdStr: String = ""
48 | var priceBtcStr: String = ""
49 | var rankStr: String = ""
50 |
51 | let targetCurrency = Prefs.shared.targetCurrency.lowercased()
52 |
53 | priceUsdStr <- map["price_\(targetCurrency)"]
54 | priceBtcStr <- map["price_btc"]
55 | rankStr <- map["rank"]
56 |
57 | self.price = Double(priceUsdStr) ?? 0
58 | self.priceBtc = Double(priceBtcStr) ?? 0
59 | self.rank = Int(rankStr) ?? 0
60 | }
61 |
62 | override static func primaryKey() -> String? {
63 | return "symbol"
64 | }
65 |
66 | /// Update Realm with dictionary to prevent from overwriting ignored properties
67 | func toDictionary() -> [String: Any] {
68 | return [
69 | "id": id,
70 | "symbol": symbol,
71 | "name": name,
72 | "price": price,
73 | "priceBtc": priceBtc,
74 | "rank": rank,
75 | ]
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Alamofire (4.5.1)
3 | - Kingfisher (3.13.1)
4 | - Moya (8.0.5):
5 | - Moya/Core (= 8.0.5)
6 | - Moya-ObjectMapper (2.3.2):
7 | - Moya-ObjectMapper/Core (= 2.3.2)
8 | - Moya-ObjectMapper/Core (2.3.2):
9 | - Moya (~> 8.0)
10 | - ObjectMapper (~> 2.2)
11 | - Moya-ObjectMapper/RxSwift (2.3.2):
12 | - Moya-ObjectMapper/Core
13 | - Moya/RxSwift
14 | - RxSwift (~> 3.1)
15 | - Moya/Core (8.0.5):
16 | - Alamofire (~> 4.1)
17 | - Result (~> 3.0)
18 | - Moya/RxSwift (8.0.5):
19 | - Moya/Core
20 | - RxSwift (~> 3.0)
21 | - ObjectMapper (2.2.9)
22 | - Realm (2.10.2):
23 | - Realm/Headers (= 2.10.2)
24 | - Realm/Headers (2.10.2)
25 | - RealmSwift (2.10.2):
26 | - Realm (= 2.10.2)
27 | - Result (3.2.4)
28 | - RxAlamofire (3.0.3):
29 | - RxAlamofire/Core (= 3.0.3)
30 | - RxAlamofire/Core (3.0.3):
31 | - Alamofire (~> 4.0)
32 | - RxSwift (~> 3.0)
33 | - RxCocoa (3.6.1):
34 | - RxSwift (~> 3.6)
35 | - RxOptional (3.2.0):
36 | - RxCocoa
37 | - RxSwift
38 | - RxRealm (0.7.2):
39 | - RealmSwift (~> 2)
40 | - RxSwift (~> 3)
41 | - RxSwift (3.6.1)
42 | - Swinject (2.1.1)
43 | - SwinjectAutoregistration (2.1.0):
44 | - Swinject (~> 2.1)
45 |
46 | DEPENDENCIES:
47 | - Alamofire
48 | - Kingfisher (~> 3.13.1)
49 | - Moya-ObjectMapper (~> 2.3.2)
50 | - Moya-ObjectMapper/RxSwift (~> 2.3.2)
51 | - Moya/RxSwift
52 | - ObjectMapper
53 | - RealmSwift
54 | - RxAlamofire
55 | - RxCocoa
56 | - RxOptional
57 | - RxRealm
58 | - RxSwift
59 | - Swinject
60 | - SwinjectAutoregistration
61 |
62 | SPEC CHECKSUMS:
63 | Alamofire: 2d95912bf4c34f164fdfc335872e8c312acaea4a
64 | Kingfisher: ed9707a409cc47fddafbcd12a43a9757e3600af5
65 | Moya: c37eec09a098ba9991b5a963b291fc5704bdb9ef
66 | Moya-ObjectMapper: 1b05f10bb69fb6c8750d497d2974c1c63c08c3a9
67 | ObjectMapper: 63cfe41bc6f8e7c8f44344c49901b8ae7de14c52
68 | Realm: 0ef72b837fb67e9f4b098bac771ddd72c7fdbb69
69 | RealmSwift: 07a9ae0505091eda6b2ee7c190c3786d6e90a7b0
70 | Result: d2d07204ce72856f1fd9130bbe42c35a7b0fea10
71 | RxAlamofire: 5eb39188c4917ad98127c0ecb4878a61b7517003
72 | RxCocoa: 84a08739ab186248c7f31ce4ee92d6f8a947d690
73 | RxOptional: d2d6a68064bc16b4801332c40663967f481b0234
74 | RxRealm: 9cf5c15c5312fc770aaa542a360cffec4916e63a
75 | RxSwift: f9de85ea20cd2f7716ee5409fc13523dc638e4e4
76 | Swinject: afea49fd95f392b41171669f165842879c48fad6
77 | SwinjectAutoregistration: 7d8ac2c23718c78286adf7590c6d0671b4ccc834
78 |
79 | PODFILE CHECKSUM: 82faa986bafd3f70ed7abfff83bb9623e3b541da
80 |
81 | COCOAPODS: 1.3.1
82 |
--------------------------------------------------------------------------------
/Nova/view/menubar/MenuBarViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // MenuBarViewModel.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 |
22 | import Foundation
23 | import RxSwift
24 |
25 | class MenuBarViewModel {
26 |
27 | let repo: DataRepository
28 |
29 | let prefs: Prefs
30 |
31 | var menuBarText = Variable(R.String.appName)
32 |
33 | let disposeBag = DisposeBag()
34 |
35 | init(repo: DataRepository, prefs: Prefs) {
36 | self.repo = repo
37 | self.prefs = prefs
38 | }
39 |
40 | func subscribe() {
41 | // Watch when list with pinned tickers changes
42 | self.repo.getPinnedTickers()
43 | .retry(5)
44 | .flatMap({ tickers -> Observable<[String]> in
45 |
46 | // If we don't have any pinned tickers, show app name
47 | guard tickers.count > 0 else {
48 | self.repo.disposeTickerSubscription()
49 | self.menuBarText.value = R.String.appName
50 | return Observable.just([])
51 | }
52 |
53 | var menuBarText = ""
54 | var tickerSymbols: [String] = []
55 |
56 | let priceFormatter = PriceFormatter(displayCurrency: self.prefs.displayCurrency, decimalFormat: self.prefs.menuBarFormat)
57 |
58 | // Create menu bar string representation
59 | for ticker in tickers {
60 | menuBarText.append(priceFormatter.formatWithTickerSymbol(ticker: ticker))
61 | tickerSymbols.append(ticker.symbol)
62 | }
63 |
64 | self.menuBarText.value = menuBarText.trim()
65 | return Observable.just(tickerSymbols)
66 | })
67 |
68 | // Distinct if pinned tickers array has the same size
69 | // This is to avoid re-subscribing after values update
70 | .distinctUntilChanged({ (old, new) -> Bool in
71 | return old.count == new.count
72 | })
73 | .filter { $0.count != 0 }
74 | .subscribe(onNext: { tickerSymbols in
75 |
76 | // Subscribe for updates for given ticker symbols
77 | self.repo.subscribeForTickerUpdates(baseSymbols: tickerSymbols)
78 | })
79 | .addDisposableTo(disposeBag)
80 |
81 | self.repo.subscribeForGlobalUpdates()
82 | }
83 |
84 | func unsubscribe() {
85 | self.repo.disposeRefreshSubscriptions()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Nova/support/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // Extensions.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Foundation
24 | import Cocoa
25 | import RealmSwift
26 | import RxSwift
27 |
28 | typealias RealmObject = Object
29 |
30 | extension NSViewController {
31 | func setTransparentTitle() {
32 | self.view.window?.titleVisibility = .hidden
33 | self.view.window?.titlebarAppearsTransparent = true
34 | self.view.window?.styleMask.insert(.fullSizeContentView)
35 | self.view.window?.isOpaque = false
36 | self.view.window?.backgroundColor = NSColor.clear
37 | }
38 | }
39 |
40 | extension NSColor {
41 | convenience init(red: Int, green: Int, blue: Int) {
42 | assert(red >= 0 && red <= 255, "Invalid red component")
43 | assert(green >= 0 && green <= 255, "Invalid green component")
44 | assert(blue >= 0 && blue <= 255, "Invalid blue component")
45 |
46 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
47 | }
48 |
49 | convenience init(rgb: Int) {
50 | self.init(
51 | red: (rgb >> 16) & 0xFF,
52 | green: (rgb >> 8) & 0xFF,
53 | blue: rgb & 0xFF
54 | )
55 | }
56 | }
57 |
58 | extension NSTextField {
59 |
60 | var cursorColor: NSColor? {
61 | get {
62 | if let fieldEditor = self.window?.fieldEditor(true, for: self) as? NSTextView {
63 | return fieldEditor.insertionPointColor
64 | }
65 | return nil
66 | }
67 | set {
68 | if let fieldEditor = self.window?.fieldEditor(true, for: self) as? NSTextView,
69 | let cursorColor = newValue {
70 | fieldEditor.insertionPointColor = cursorColor
71 | }
72 | }
73 | }
74 | }
75 |
76 | extension String {
77 | func trim() -> String {
78 | return self.trimmingCharacters(in: NSCharacterSet.whitespaces)
79 | }
80 | }
81 |
82 | extension ObservableType {
83 | public func retryWithDelay(timeInterval: Int) -> Observable {
84 | return retryWhen { (attempts: Observable) -> Observable in
85 | return Observable
86 | .zip(attempts, Observable.just(timeInterval), resultSelector: { (o1, o2) in
87 | return o2
88 | })
89 | .flatMap { i -> Observable in
90 | return Observable.timer(RxTimeInterval(i), period: RxTimeInterval(i), scheduler: MainScheduler.instance)
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Nova/view/menubar/MenuBarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // MenuBarView.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 |
22 | import Foundation
23 | import Cocoa
24 | import RxSwift
25 | import RxCocoa
26 |
27 | /// Menu bar view displays selected tickers in menu bar
28 | class MenuBarView: NSObject {
29 |
30 | /// Status item used to display coin tickers
31 | let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
32 |
33 | /// Content wrapper
34 | let popover = NSPopover()
35 |
36 | let viewModel = Injector.inject(type: MenuBarViewModel.self)
37 |
38 | let disposeBag = DisposeBag()
39 |
40 | private var eventMonitor: EventMonitor?
41 |
42 | override init() {
43 | super.init()
44 |
45 | self.statusItem.button?.action = #selector(togglePopover)
46 | self.statusItem.button?.target = self
47 |
48 | self.bindUi()
49 |
50 | let storyboard = NSStoryboard(name: "Main", bundle: nil)
51 |
52 | let popoverViewController = storyboard.instantiateController(
53 | withIdentifier: "popover") as! TickerListViewController
54 |
55 | self.popover.contentViewController = popoverViewController
56 |
57 | // Close popover on any click outside of the app
58 | self.eventMonitor = EventMonitor(
59 | mask: [.leftMouseDown, .rightMouseDown],
60 | handler: { [weak self] event in
61 | self?.hidePopover()
62 | })
63 | }
64 |
65 | func togglePopover() {
66 | if popover.isShown {
67 | self.hidePopover()
68 | } else {
69 | self.showPopover()
70 | }
71 | }
72 |
73 | func showPopover() {
74 | if let button = statusItem.button {
75 | self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
76 | self.eventMonitor?.start()
77 | }
78 | }
79 |
80 | func hidePopover() {
81 | self.popover.performClose(nil)
82 | self.eventMonitor?.stop()
83 | }
84 |
85 | func setStatusItemTitle(title: String) {
86 | let title = NSAttributedString(string: title, attributes: R.Font.menuBarTitleAttributes)
87 |
88 | self.statusItem.attributedTitle = title
89 | }
90 |
91 | func refresh() {
92 | self.viewModel.subscribe()
93 | }
94 |
95 | func pause() {
96 | self.viewModel.unsubscribe()
97 | }
98 |
99 | func resume() {
100 | self.viewModel.subscribe()
101 | }
102 |
103 | private func bindUi() {
104 | self.viewModel.subscribe()
105 | self.viewModel.menuBarText.asObservable().subscribe(onNext: { text in
106 | self.setStatusItemTitle(title: text)
107 | })
108 | .addDisposableTo(disposeBag)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Nova/repository/RemoteDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // RemoteDataSource.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Foundation
24 | import RxSwift
25 | import Moya
26 | import ObjectMapper
27 | import Moya_ObjectMapper
28 |
29 | class RemoteDataSource {
30 |
31 | private var coinMarketCapProvider: RxMoyaProvider
32 |
33 | private var cryptoCompareProvider: RxMoyaProvider
34 |
35 | private var firebaseProvider: RxMoyaProvider
36 |
37 | private let providerPlugins = [NetworkLoggerPlugin()]
38 |
39 | init(coinMarketCapProvider: RxMoyaProvider,
40 | cryptoCompareProvider: RxMoyaProvider,
41 | firebaseProvider: RxMoyaProvider) {
42 |
43 | self.coinMarketCapProvider = coinMarketCapProvider
44 | self.cryptoCompareProvider = cryptoCompareProvider
45 | self.firebaseProvider = firebaseProvider
46 | }
47 |
48 | /// Get all available tickers from CoinMarketCap
49 | func getAllTickers() -> Observable<[Ticker]> {
50 | return self.coinMarketCapProvider.request(.allTickers)
51 | .observeOn(Schedulers.backgroundInteractive)
52 | .mapArray(Ticker.self)
53 |
54 | }
55 |
56 | /// Get top N (limit) tickers, sorted by market cap
57 | func getTopTickers(limit: Int) -> Observable<[Ticker]> {
58 | return self.coinMarketCapProvider.request(.topTickers(limit: limit))
59 | .mapArray(Ticker.self)
60 | }
61 |
62 | /// Get ticker for a single crypto currency
63 | func getTicker(currencyName: String) -> Observable {
64 | return self.coinMarketCapProvider.request(.ticker(currencyName: currencyName))
65 | .mapObject(Ticker.self)
66 | }
67 |
68 | /// Get tickers from CryptoCompare api
69 | ///
70 | /// - parameters:
71 | /// - base: The array of ticker symbols (BTC, ETH, XRP...)
72 | /// - target: The optional array of fiat target symbols we are converting into (USD, EUR...)
73 | /// Defaults to [\"USD\"]
74 | func getTickers(base: [String], target: [String] = ["USD"]) -> Observable<[String: [String: Double]]> {
75 | return self.cryptoCompareProvider
76 | .request(.priceMulti(fromSymbols: base, toSymbols: target))
77 | .filterSuccessfulStatusCodes()
78 | .map { response in
79 | return try JSONSerialization.jsonObject(
80 | with: response.data, options: []) as! [String: [String: Double]]
81 | }
82 | }
83 |
84 | // Get news from Firebase
85 | func getNews() -> Observable {
86 | return firebaseProvider.request(.news)
87 | .filterSuccessfulStatusCodes()
88 | .mapObject(News.self)
89 | .do(onNext: { news in
90 | print("Firebase news fetched successfully:\(news)")
91 | })
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Nova/Injector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // Injector.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 |
22 | import Foundation
23 | import Swinject
24 | import Moya
25 | import SwinjectAutoregistration
26 |
27 | /// Swinject dependency injection wrapper
28 | class Injector {
29 |
30 | private static let instance = Injector()
31 |
32 | /// App level dependencies, top level di container
33 | private let mainContainer = Container()
34 |
35 | private init() {
36 | self.registerGlobalDependencies()
37 | self.registerProviders()
38 | self.registerDataSources()
39 | self.registerViewModels()
40 | }
41 |
42 | /// Inject, resolve a dependency with the given type
43 | /// Throws exception if the given type can not be resolved
44 | public static func inject(type: T.Type) -> T {
45 | return instance.mainContainer.resolve(type)!
46 | }
47 |
48 | // MARK: Global dependencies
49 |
50 | private func registerGlobalDependencies() {
51 |
52 | self.mainContainer.register(AppDelegate.self, factory: { _ in
53 | return NSApp.delegate as! AppDelegate
54 | })
55 |
56 | self.mainContainer.autoregister(Prefs.self, initializer: Prefs.init)
57 | }
58 |
59 | // MARK: Providers
60 |
61 | private func registerProviders() {
62 | self.mainContainer.register(RxMoyaProvider.self) { _ in
63 | return RxMoyaProvider()
64 | }
65 |
66 | self.mainContainer.register(RxMoyaProvider.self) { _ in
67 | return RxMoyaProvider()
68 | }
69 |
70 | self.mainContainer.register(RxMoyaProvider.self) { _ in
71 | return RxMoyaProvider()
72 | }
73 | }
74 |
75 |
76 | // MARK: Data sources
77 |
78 | /// All data sources should be registered in container object scope (singleton)
79 | private func registerDataSources() {
80 |
81 | self.mainContainer.autoregister(
82 | RemoteDataSource.self, initializer: RemoteDataSource.init)
83 | .inObjectScope(.container)
84 |
85 | self.mainContainer.autoregister(
86 | LocalDataSource.self, initializer: LocalDataSource.init)
87 | .inObjectScope(.container)
88 |
89 | self.mainContainer.autoregister(
90 | DataRepository.self, initializer: DataRepository.init)
91 | .inObjectScope(.container)
92 | }
93 |
94 | // MARK: View models
95 |
96 | private func registerViewModels() {
97 |
98 | self.mainContainer.autoregister(
99 | MenuBarViewModel.self, initializer: MenuBarViewModel.init)
100 |
101 | self.mainContainer.autoregister(
102 | TickerListViewModel.self, initializer: TickerListViewModel.init)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Nova/support/Prefs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // Prefs.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Foundation
24 |
25 | /// Class for accessing and modifying preference data
26 | /// returned by NSUserDefaults.
27 | ///
28 | /// All clients should use a shared instance of this class
29 | class Prefs {
30 |
31 | static let shared = Prefs()
32 |
33 | /// Key values for acessing and modifying user defaults data
34 | struct Key {
35 |
36 | static let refreshInterval = "refresh_interval"
37 |
38 | static let pinnedSymbols = "pinned_symbols"
39 |
40 | static let targetCurrency = "target_currency"
41 |
42 | static let displayCurrency = "display_currency"
43 |
44 | static let showCurrencySymbol = "show_currency_symbol"
45 |
46 | static let menuBarFormat = "menu_bar_format"
47 | }
48 |
49 | let userDefaults = UserDefaults.standard
50 |
51 | /// Ticker refresh interval (seconds), default 30.0
52 | var rereshInterval: Float {
53 | get {
54 | return self.userDefaults.float(forKey: Key.refreshInterval)
55 | }
56 |
57 | set {
58 | self.userDefaults.set(newValue, forKey: Key.refreshInterval)
59 | }
60 | }
61 |
62 | /// Ticker symbols pinned to be displayed in menu bar
63 | ///
64 | /// - key: The currency symbol (ETH, BTC, etc.)
65 | /// - value: The latest price value
66 | ///
67 | /// Defaults to empty dictionry
68 | var pinedCurrencies: [String: Double] {
69 | get {
70 | if let value = self.userDefaults
71 | .dictionary(forKey: Key.pinnedSymbols) as? [String: Double] {
72 |
73 | return value
74 | }
75 | return [:]
76 | }
77 |
78 | set {
79 | self.userDefaults.set(newValue, forKey: Key.pinnedSymbols)
80 | }
81 | }
82 |
83 | /// Target currency to convert all coins into, default USD
84 | var targetCurrency: String {
85 | get {
86 | if let target = self.userDefaults.string(forKey: Key.targetCurrency) {
87 | return target
88 | }
89 | return "USD"
90 | }
91 |
92 | set {
93 | self.userDefaults.set(newValue, forKey: Key.targetCurrency)
94 | }
95 | }
96 |
97 | /// The currency we use to display prices
98 | /// For example BTC and SAT should both use BTC as a target currency,
99 | /// but we also need to be able to display them differently
100 | var displayCurrency: String {
101 | get {
102 | if let displayCurrency = self.userDefaults.string(forKey: Key.displayCurrency) {
103 | return displayCurrency
104 | }
105 | return "USD"
106 | }
107 |
108 | set {
109 | self.userDefaults.set(newValue, forKey: Key.displayCurrency)
110 | }
111 | }
112 |
113 | /// Menu bar number format (decimal points or significant digits)
114 | var menuBarFormat: String {
115 | get {
116 | if let target = self.userDefaults.string(forKey: Key.menuBarFormat) {
117 | return target
118 | }
119 | return "auto"
120 | }
121 |
122 | set {
123 | self.userDefaults.set(newValue, forKey: Key.menuBarFormat)
124 | }
125 | }
126 |
127 | var showCurrencySymbol: Bool {
128 | get {
129 | return self.userDefaults.bool(forKey: Key.showCurrencySymbol)
130 | }
131 |
132 | set {
133 | self.userDefaults.set(newValue, forKey: Key.showCurrencySymbol)
134 | }
135 | }
136 |
137 | init() {
138 |
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Nova/support/PriceFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // PriceFormatter.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 10/29/17.
21 | //
22 |
23 | import Foundation
24 |
25 | class PriceFormatter {
26 |
27 | let displaySymbol: String
28 | let displayCurrency: String
29 | let decimalFormat: String
30 |
31 | let decimalNumberFormatter: NumberFormatter
32 |
33 | init(displayCurrency: String, decimalFormat: String?) {
34 | self.displayCurrency = displayCurrency
35 | self.decimalFormat = decimalFormat != nil ? decimalFormat! : "auto"
36 |
37 | self.decimalNumberFormatter = NumberFormatter()
38 | decimalNumberFormatter.numberStyle = .decimal
39 | decimalNumberFormatter.groupingSeparator = ","
40 | decimalNumberFormatter.groupingSize = 3
41 | decimalNumberFormatter.minimumFractionDigits = 0
42 | decimalNumberFormatter.maximumFractionDigits = 0
43 |
44 | let locale = NSLocale(localeIdentifier: self.displayCurrency)
45 | self.displaySymbol = locale.displayName(
46 | forKey: NSLocale.Key.currencySymbol, value: self.displayCurrency) ?? displayCurrency
47 | }
48 |
49 | /// Format with ticker symbol (PAY 1.70)
50 | func formatWithTickerSymbol(ticker: Ticker) -> String {
51 | return self.format(ticker: ticker, symbol: ticker.symbol)
52 | }
53 |
54 | /// Format with target symbol ($ 1.70)
55 | func formatWithTargetSymbol(ticker: Ticker) -> String {
56 | return self.format(ticker: ticker, symbol: self.displaySymbol)
57 | }
58 |
59 | /// Format with provided symbol (SYMBOL PRICE)
60 | func format(ticker: Ticker, symbol: String) -> String {
61 | // significant digit formatting
62 | if self.decimalFormat.prefix(1) == "s" {
63 | let valueFormatter = NumberFormatter()
64 | valueFormatter.usesSignificantDigits = true
65 | var breakPoint: Double = 1000
66 |
67 | switch self.decimalFormat {
68 | // 3 significant digits
69 | case "s3":
70 | valueFormatter.minimumSignificantDigits = 3
71 | valueFormatter.maximumSignificantDigits = 3
72 | breakPoint = 1000
73 | break
74 | // 4 significant digits
75 | case "s4":
76 | valueFormatter.minimumSignificantDigits = 4
77 | valueFormatter.maximumSignificantDigits = 4
78 | breakPoint = 10000
79 | break
80 | // 5 significant digits
81 | case "s5":
82 | valueFormatter.minimumSignificantDigits = 5
83 | valueFormatter.maximumSignificantDigits = 5
84 | breakPoint = 100000
85 | break
86 | default:
87 | break
88 | }
89 |
90 | let price = ticker.price >= breakPoint ? ticker.price / 1000 : ticker.price
91 | return "\(symbol) \(String(format: ticker.price >= breakPoint ? "%@K" : "%@", valueFormatter.string(from: NSNumber(value: price))!)) "
92 | }
93 |
94 | let format = self.getPriceFormat(ticker: ticker)
95 |
96 | if displayCurrency == "SAT" {
97 | let satPrice = ticker.price * 100000000
98 | let formattedPrice = decimalNumberFormatter.string(for: satPrice) ?? "\(satPrice)"
99 | return "\(symbol) \(formattedPrice) "
100 | }
101 |
102 | return "\(symbol) \(String(format: format, ticker.price)) "
103 | }
104 |
105 | func getPriceFormat(ticker: Ticker) -> String {
106 | switch self.decimalFormat {
107 | case "d0":
108 | return "%.0f"
109 | case "d1":
110 | return "%.1f"
111 | case "d2":
112 | return "%.2f"
113 | case "d3":
114 | return "%.3f"
115 | default:
116 | break
117 | }
118 |
119 | if self.displayCurrency == "BTC" {
120 | return "%.8f"
121 | } else if self.displayCurrency == "SAT" {
122 | return "%.0f"
123 | }
124 |
125 | return ticker.price < 1 ? "%.4f" : "%.2f"
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Nova/repository/LocalDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // LocalDataSource.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 |
22 | import Foundation
23 | import RealmSwift
24 | import RxRealm
25 | import RxSwift
26 |
27 | class LocalDataSource {
28 |
29 | let defaultSortOrder = [
30 | SortDescriptor(keyPath: "isPinned", ascending: false),
31 | SortDescriptor(keyPath: "rank", ascending: true)
32 | ]
33 |
34 | var prefs: Prefs
35 |
36 | init(prefs: Prefs) {
37 | self.prefs = prefs
38 | }
39 |
40 | /// Asyncroniously cache tickers into Realm
41 | func saveTickersAsync(tickers: [Ticker]) {
42 | DispatchQueue(label: "realm").async {
43 | autoreleasepool {
44 | self.saveTickers(tickers: tickers)
45 | }
46 | }
47 | }
48 |
49 | /// Synchroniously cache tickers into Realm
50 | func saveTickers(tickers: [Ticker]) {
51 | let realm = try! Realm()
52 | try! realm.write {
53 | for ticker in tickers {
54 | // Generic fix for symbol collisions.
55 | // Some unknown altcoins may have colliding symbols with (BTC, ETH..), this is a problem,
56 | // because we use ticker symbol as a primary key for cryptocompare requests.
57 | // The trade-off is to drop altcoins with colliding symbols and only display the ticker with the highest market cap.
58 | if let localTicker = realm.object(ofType: Ticker.self, forPrimaryKey: ticker.symbol) {
59 | // Skip all colliding ticker symbols
60 | if localTicker.id != ticker.id && localTicker.rank < ticker.rank {
61 | continue
62 | }
63 | }
64 |
65 | realm.create(Ticker.self, value: ticker.toDictionary(), update: true)
66 | }
67 | }
68 | }
69 |
70 | /// Cache a single ticker into Realm
71 | func saveTicker(ticker: Ticker) {
72 | let realm = try! Realm()
73 |
74 | try! realm.write {
75 | realm.create(Ticker.self, value: ticker.toDictionary(), update: true)
76 | }
77 | }
78 |
79 | func updateTickerPrices(tickers: [String: [String: Double]]) {
80 | let realm = try! Realm()
81 |
82 | try! realm.write {
83 | for (key, prices) in tickers {
84 |
85 | if let updatedPrice = prices[prefs.targetCurrency] {
86 | realm.create(Ticker.self, value: ["symbol": key, "price": updatedPrice], update: true)
87 | }
88 | }
89 | }
90 | }
91 |
92 | func updateTickerPrice(symbol: String, newPrice: Double) {
93 | let realm = try! Realm()
94 |
95 | try! realm.write {
96 | realm.create(Ticker.self, value: ["symbol": symbol, "priceUsd": newPrice], update: true)
97 | }
98 | }
99 |
100 | /// Return all tickers cached in Realm
101 | /// - Ordered by market cap, pinned tickers first
102 | func getAllTickers() -> Observable<[Ticker]> {
103 | let realm = try! Realm()
104 |
105 | let tickers = realm
106 | .objects(Ticker.self)
107 | .sorted(by: defaultSortOrder)
108 |
109 | return Observable.array(from: tickers)
110 | }
111 |
112 | /// Get pinned tickers sorted by orderIndex
113 | func getPinnedTickers() -> Observable<[Ticker]> {
114 | let realm = try! Realm()
115 |
116 | let pinnedTickers = realm
117 | .objects(Ticker.self)
118 | .filter("isPinned = true")
119 | .sorted(by: defaultSortOrder)
120 |
121 | return Observable.array(from: pinnedTickers)
122 | }
123 |
124 | func isTickerPinned(symbol: String) -> Bool {
125 | let realm = try! Realm()
126 |
127 | guard let ticker =
128 | realm.object(ofType: Ticker.self, forPrimaryKey: symbol) else {
129 | return false
130 | }
131 |
132 | return ticker.isPinned
133 | }
134 |
135 | /// Set ticker as pinned (show in menu bar)
136 | func pinTicker(symbol: String) {
137 | let realm = try! Realm()
138 |
139 | try! realm.write {
140 | realm.create(Ticker.self, value: ["symbol": symbol, "isPinned": true], update: true)
141 | }
142 | }
143 |
144 | /// Unpin ticker (remove from menu bar)
145 | func unpinTicker(symbol: String) {
146 | let realm = try! Realm()
147 |
148 | try! realm.write {
149 | realm.create(Ticker.self, value: ["symbol": symbol, "isPinned": false], update: true)
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Nova/view/tickerlist/TickerListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // TickerListViewModel.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Foundation
24 | import RxSwift
25 | import RxCocoa
26 |
27 | /// Model for TickerList view
28 | class TickerListViewModel {
29 |
30 | private let repo = Injector.inject(type: DataRepository.self)
31 |
32 | private let prefs = Injector.inject(type: Prefs.self)
33 |
34 | /// Raw unfiltered data
35 | private var data: [Ticker] = []
36 |
37 | /// Raw data filtered with search string
38 | var filteredData = Variable<[Ticker]>([])
39 |
40 | /// User input, filter raw data
41 | var searchString = Variable("")
42 |
43 | /// Number of rows for tableview
44 | var numberOfRows: Int {
45 | return filteredData.value.count
46 | }
47 |
48 | /// Callback to notify tableview of data changes
49 | var reloadDataCallback: (()->())?
50 |
51 | /// True while repository processing data in background
52 | /// Bind to UI to show indicator
53 | var isRefreshing = Variable(false)
54 |
55 | /// Firebase news object
56 | var news = Variable(News())
57 |
58 | var priceFormatter: PriceFormatter
59 |
60 | /// Rx subscriptions
61 | let disposeBag = DisposeBag()
62 |
63 | var displayCurrency: String {
64 | set {
65 | self.priceFormatter = PriceFormatter(displayCurrency: newValue, decimalFormat: nil)
66 | if newValue == "SAT" {
67 | self.prefs.targetCurrency = "BTC"
68 | } else {
69 | self.prefs.targetCurrency = newValue
70 | }
71 | self.prefs.displayCurrency = newValue
72 | }
73 | get {
74 | return prefs.displayCurrency
75 | }
76 | }
77 |
78 | var menuBarFormat: String {
79 | set {
80 | self.prefs.menuBarFormat = newValue
81 | }
82 | get {
83 | return prefs.menuBarFormat
84 | }
85 | }
86 |
87 | /// CMC image format
88 | private let imageUrlFormat = "https://files.coinmarketcap.com/static/img/coins/128x128/%@.png"
89 |
90 | init() {
91 | self.priceFormatter = PriceFormatter(displayCurrency: self.prefs.displayCurrency, decimalFormat:nil)
92 |
93 | self.repo.getAllTickers()
94 | .subscribe(onNext: { tickers in
95 | self.data = tickers
96 | self.filteredData.value = self.data
97 | self.filterData(query: self.searchString.value)
98 | })
99 | .addDisposableTo(disposeBag)
100 |
101 | self.searchString
102 | .asObservable()
103 | .throttle(0.3, scheduler: MainScheduler.instance)
104 | .distinctUntilChanged()
105 | .subscribe(onNext: { searchStr in
106 | self.filterData(query: searchStr)
107 | })
108 | .addDisposableTo(disposeBag)
109 |
110 | self.repo.getNews()
111 | .subscribe(onNext: { news in
112 | self.news.value = news;
113 | })
114 | .addDisposableTo(disposeBag)
115 | }
116 |
117 | /// Get single ticker for tableview row
118 | func getTicker(row: Int) -> Ticker {
119 | return self.filteredData.value[row]
120 | }
121 |
122 | /// Currency name for tableview row
123 | func getCurrencyName(row: Int) -> String {
124 | return self.getTicker(row: row).name
125 | }
126 |
127 | /// Image url for tableview row
128 | func getCurrencyImageUrl(row: Int) -> URL? {
129 |
130 | let imageName = getTicker(row: row).name
131 | .lowercased()
132 | .replacingOccurrences(of: " ", with: "-")
133 |
134 | let urlString = String.init(format: imageUrlFormat, imageName)
135 | return URL(string: urlString)
136 | }
137 |
138 | func getTargetPrice(row: Int) -> String {
139 | let ticker = self.getTicker(row: row)
140 | return self.priceFormatter.formatWithTargetSymbol(ticker: ticker)
141 | }
142 |
143 | func pinStatusChanged(row: Int, pinned: Bool) {
144 | let ticker = getTicker(row: row)
145 |
146 | if pinned {
147 | self.repo.pinTicker(symbol: ticker.symbol)
148 | } else {
149 | self.repo.unpinTicker(symbol: ticker.symbol)
150 | }
151 | }
152 |
153 | func pinButtonState(row: Int) -> Int {
154 | let ticker = getTicker(row: row)
155 | return ticker.isPinned ? 1 : 0
156 | }
157 |
158 | func filterData(query: String) {
159 | if query.isEmpty {
160 | // Show all values
161 | self.filteredData.value = self.data
162 | self.reloadDataCallback?()
163 |
164 | return
165 | }
166 |
167 | self.filteredData.value = self.data.filter {
168 | $0.name.lowercased().contains(query.lowercased())
169 | || $0.symbol.lowercased().contains(query.lowercased())
170 | }
171 |
172 | self.reloadDataCallback?()
173 | }
174 |
175 | func refresh() {
176 | self.isRefreshing.value = true
177 |
178 | self.repo.refreshAllTickers().subscribe(onNext: { _ in
179 | self.isRefreshing.value = false
180 | }, onError: { _ in
181 | self.isRefreshing.value = false
182 | })
183 | .addDisposableTo(disposeBag)
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Nova/repository/DataRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // DataRepository.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 |
22 | import Foundation
23 | import RxSwift
24 | import RealmSwift
25 |
26 | /// Central data source that abstracts fetching and caching of app data
27 | class DataRepository {
28 |
29 | private var local: LocalDataSource
30 |
31 | private var remote: RemoteDataSource
32 |
33 | private var prefs: Prefs
34 |
35 | private var tickerUpdateSubscription: Disposable?
36 |
37 | private var globalRefreshSubscription: Disposable?
38 |
39 | private var disposeBag = DisposeBag()
40 |
41 | private var refreshSubscriptions = DisposeBag()
42 |
43 | private var tickerUpdateTimestamp: Date?
44 |
45 | init(local: LocalDataSource, remote: RemoteDataSource, prefs: Prefs) {
46 | self.local = local
47 | self.remote = remote
48 | self.prefs = prefs
49 | }
50 |
51 | /// Get pinned tickers sorted by orderIndex
52 | func getPinnedTickers() -> Observable<[Ticker]> {
53 | return self.local.getPinnedTickers()
54 | }
55 |
56 | /// Set ticker as pinned (show in menu bar)
57 | func pinTicker(symbol: String) {
58 | self.local.pinTicker(symbol: symbol)
59 | }
60 |
61 | /// Unpin ticker (remove from menu bar)
62 | func unpinTicker(symbol: String) {
63 | self.local.unpinTicker(symbol: symbol)
64 | }
65 |
66 | /// Get all cached tickers from Realm
67 | ///
68 | /// - Query remote repository for update and cache into Realm
69 | /// - Changes in Realm will automatically trigger update of UI
70 | /// - Sorted by market cap
71 | ///
72 | /// - returns:
73 | /// An observable of type `Ticker`
74 | func getAllTickers() -> Observable<[Ticker]> {
75 | self.remote.getAllTickers()
76 | .observeOn(MainScheduler.instance)
77 | .subscribe(onNext: { tickers in
78 | self.local.saveTickers(tickers: tickers)
79 | })
80 | .addDisposableTo(disposeBag)
81 |
82 | return self.local.getAllTickers()
83 | }
84 |
85 | /// Get all tickers from remote repository and cache response into Realm
86 | ///
87 | /// - returns:
88 | /// An empty observable to notify the UI when finished
89 | func refreshAllTickers() -> Observable {
90 | return self.remote.getAllTickers()
91 | // Cache into Realm
92 | .do(onNext: { response in
93 | self.local.saveTickers(tickers: response)
94 | })
95 | // Flat map to empty observable since we don't need the result
96 | .observeOn(MainScheduler.instance)
97 | .flatMap { _ in
98 | return Observable.just()
99 | }
100 | }
101 |
102 | /// Get top N (limit) tickers, sorted by market cap
103 | func getTopTickers(limit: Int) -> Observable<[Ticker]> {
104 |
105 | return self.remote.getTopTickers(limit: limit)
106 | }
107 |
108 |
109 | /// Request fresh prices from CryptoCompare api
110 | /// Update local repository on response
111 | ///
112 | /// - parameters:
113 | /// - baseSymbols: The array of crypto currency symbols (BTC, ETH, XRP...)
114 | /// - refreshInterval:
115 | /// The refresh interval in seconds. Default value is 15 seconds.
116 | func subscribeForTickerUpdates(baseSymbols: [String], refreshInterval: Float = 15.0) {
117 | print("Subscribe for ticker updates")
118 | self.tickerUpdateSubscription?.dispose()
119 |
120 | self.tickerUpdateSubscription =
121 | Observable
122 | .timer(0, period: RxTimeInterval(refreshInterval), scheduler: Schedulers.background)
123 | // Query CryptoCompare api for an update
124 | .flatMap({ _ -> Observable<[String: [String: Double]]> in
125 | return self.remote.getTickers(base: baseSymbols, target: [self.prefs.targetCurrency])
126 | })
127 | .retryWithDelay(timeInterval: Int(refreshInterval))
128 | .subscribe(onNext: { tickers in
129 | let timestamp = DateFormatter.localizedString(
130 | from: NSDate() as Date, dateStyle: .none, timeStyle: .medium)
131 |
132 | print("Pinned tickers updated at: \(timestamp)")
133 | self.local.updateTickerPrices(tickers: tickers)
134 | }, onError: { error in
135 | print(error)
136 | })
137 |
138 | self.tickerUpdateSubscription?.addDisposableTo(refreshSubscriptions)
139 | }
140 |
141 | /// Request fresh prices from CoinMarketCap
142 | /// Update local repository on response
143 | ///
144 | /// - parameters:
145 | /// - refreshIntervalMinutes: The refresh interval in minutes.
146 | /// Min. value is 5 minutes, since CMC endpoint refreshes every 5 minutes.
147 | func subscribeForGlobalUpdates(refreshIntervalMinutes: Int = 5) {
148 | print("Subscribe for global updates")
149 | guard refreshIntervalMinutes >= 5 else {
150 | fatalError("Do not use refrsh interval of less than 5 minutes." +
151 | "CoinMarketCap endpoint refreshes every 5 minutes")
152 | }
153 |
154 | self.globalRefreshSubscription?.dispose()
155 |
156 | self.globalRefreshSubscription =
157 | Observable.timer(0, period: RxTimeInterval(refreshIntervalMinutes * 60), scheduler: Schedulers.background)
158 | // Query Cryptonator api for an update
159 | .flatMap { _ -> Observable<[Ticker]> in
160 |
161 | return self.remote.getAllTickers()
162 | }
163 | .subscribe(onNext: { tickers in
164 | print("All tickers updated.")
165 | self.local.saveTickersAsync(tickers: tickers)
166 | }, onError: { error in
167 | print(error)
168 | })
169 |
170 | self.globalRefreshSubscription?.addDisposableTo(refreshSubscriptions)
171 | }
172 |
173 | func disposeTickerSubscription() {
174 | self.tickerUpdateSubscription?.dispose()
175 | }
176 |
177 | /// Terminate all running refresh subscriptions
178 | func disposeRefreshSubscriptions() {
179 | self.refreshSubscriptions = DisposeBag()
180 | }
181 |
182 | func getNews() -> Observable {
183 | return self.remote.getNews()
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Nova/view/tickerlist/TickerListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 Andrej Jurkin.
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 | // TickerListViewController.swift
18 | // Nova
19 | //
20 | // Created by Andrej Jurkin on 9/3/17.
21 | //
22 |
23 | import Cocoa
24 | import RxSwift
25 | import RxCocoa
26 | import Kingfisher
27 |
28 | class TickerListViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
29 |
30 | @IBOutlet weak var searchTextField: NSTextField!
31 | @IBOutlet weak var tickerTableView: NSTableView!
32 | @IBOutlet weak var refreshButton: NSButton!
33 | @IBOutlet var settingsMenu: NSMenu!
34 | @IBOutlet weak var targetCurrencies: NSMenu!
35 | @IBOutlet weak var menuBarFormats: NSMenu!
36 |
37 | // News
38 | @IBOutlet weak var newsContainer: NSBox!
39 | @IBOutlet weak var newsTitle: NSTextField!
40 | @IBOutlet weak var newsSubtitle: NSTextField!
41 | @IBOutlet weak var newsButton: NSTextField!
42 | @IBOutlet weak var newsContainerHeightConstraint: NSLayoutConstraint!
43 | @IBOutlet weak var tickerListTopConstraint: NSLayoutConstraint!
44 |
45 | var viewModel = Injector.inject(type: TickerListViewModel.self)
46 | var disposeBag = DisposeBag()
47 |
48 | override func viewDidLoad() {
49 | super.viewDidLoad()
50 |
51 | self.searchTextField.rx.text.orEmpty
52 | .bind(to: viewModel.searchString)
53 | .addDisposableTo(disposeBag)
54 |
55 | self.viewModel.reloadDataCallback = {
56 | self.tickerTableView.reloadData()
57 | }
58 |
59 | // Bind refresh button
60 | self.viewModel.isRefreshing.asObservable().subscribe { refreshingState in
61 | guard let isRefreshing = refreshingState.element else {
62 | return
63 | }
64 |
65 | if isRefreshing {
66 | self.refreshButton.isEnabled = false
67 | self.startRefreshAnimation()
68 | } else {
69 | self.refreshButton.isEnabled = true
70 | self.stopRefreshAnimation()
71 | }
72 | }
73 | .addDisposableTo(disposeBag)
74 |
75 | // Set placeholder color
76 | if let font = NSFont(name: "Avenir-Book", size: 17) {
77 | let attributes = [NSForegroundColorAttributeName: R.Color.placeholderLight, NSFontAttributeName: font]
78 |
79 | let searchPlaceholder = NSAttributedString(string: "Search...", attributes: attributes)
80 | self.searchTextField.placeholderAttributedString = searchPlaceholder
81 | }
82 |
83 | // Bind menu item actions
84 | let targetCurrency = self.viewModel.displayCurrency
85 | for menu in targetCurrencies.items {
86 | menu.target = self
87 | menu.action = #selector(onTargetCurrencyClick(sender:))
88 |
89 | if targetCurrency == menu.title {
90 | menu.state = NSOnState
91 | }
92 | }
93 |
94 | let menuBarFormat = self.viewModel.menuBarFormat
95 | for menu in menuBarFormats.items {
96 | if !menu.isEnabled {
97 | continue
98 | }
99 |
100 | menu.target = self
101 | menu.action = #selector(onMenuBarFormatClick(sender:))
102 |
103 | if menuBarFormat == menu.identifier {
104 | menu.state = NSOnState
105 | }
106 | }
107 | }
108 |
109 | override var representedObject: Any? {
110 |
111 | didSet {
112 | // Update the view, if already loaded.
113 | }
114 | }
115 |
116 | override func viewDidAppear() {
117 | super.viewDidAppear()
118 | self.view.window?.titleVisibility = .hidden
119 | self.view.window?.titlebarAppearsTransparent = true
120 | self.view.window?.styleMask.insert(.fullSizeContentView)
121 | self.view.window?.isOpaque = false
122 | self.view.window?.backgroundColor = NSColor.clear
123 | self.searchTextField.cursorColor = R.Color.primaryLight
124 |
125 | // Bind filtered data
126 | self.viewModel.filteredData
127 | .asObservable()
128 | .subscribe(onNext: { _ in
129 | self.tickerTableView.reloadData()
130 | })
131 | .addDisposableTo(disposeBag)
132 |
133 | // Bind news
134 | self.viewModel.news
135 | .asObservable()
136 | .subscribe({ news in
137 | guard let news = news.element else {
138 | return
139 | }
140 | if (news.hidden) {
141 | self.hideNewsContainer()
142 | } else {
143 | self.showNewsContainer()
144 | self.newsContainer.isHidden = news.hidden
145 | self.newsTitle.stringValue = news.title
146 | self.newsSubtitle.stringValue = news.subtitle
147 | self.newsButton.stringValue = news.buttonTitle
148 | }
149 | })
150 | .addDisposableTo(disposeBag)
151 | }
152 |
153 | @IBAction func onRefreshButtonClick(_ sender: Any) {
154 | self.refreshData()
155 | }
156 |
157 | @IBAction func onSettingsButtonClick(_ sender: Any) {
158 | self.settingsMenu.popUp(positioning: self.settingsMenu.item(at: 0),
159 | at: NSEvent.mouseLocation(), in: nil)
160 | }
161 |
162 | @IBAction func onLicenseClick(_ sender: AnyObject) {
163 | self.openPdf(resourceName: "license")
164 | }
165 |
166 | @IBAction func onAcknowledgementsClick(_ sender: AnyObject) {
167 | self.openPdf(resourceName: "acknowledgements")
168 | }
169 |
170 | @IBAction func openSupportWebsite(_ sender: Any) {
171 | if let url = URL(string: "https://github.com/AndrejJurkin/nova-osx") {
172 | NSWorkspace.shared().open(url)
173 | }
174 | }
175 |
176 | @IBAction func onNewsButtonClick(_ sender: Any) {
177 | if let url = URL(string: self.viewModel.news.value.link) {
178 | NSWorkspace.shared().open(url)
179 | AppDelegate.shared().menuBarView?.hidePopover()
180 | }
181 | }
182 |
183 | func refreshData() {
184 | self.viewModel.refresh()
185 | AppDelegate.shared().menuBarView?.refresh()
186 | }
187 |
188 | func onTargetCurrencyClick(sender: NSMenuItem) {
189 | sender.state = NSOnState
190 |
191 | for menuItem in targetCurrencies.items {
192 | if sender.title == menuItem.title {
193 | continue
194 | }
195 |
196 | menuItem.state = NSOffState
197 | }
198 |
199 | self.viewModel.displayCurrency = sender.title
200 | self.refreshData()
201 | }
202 |
203 | func onMenuBarFormatClick(sender: NSMenuItem) {
204 | sender.state = NSOnState
205 |
206 | for menuItem in menuBarFormats.items {
207 | if (sender.identifier == menuItem.identifier) || !menuItem.isEnabled {
208 | continue
209 | }
210 |
211 | menuItem.state = NSOffState
212 | }
213 |
214 | self.viewModel.menuBarFormat = sender.identifier!
215 | AppDelegate.shared().menuBarView?.refresh()
216 | }
217 |
218 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
219 |
220 | let view = tableView.make(withIdentifier: "Cell", owner: self) as! TickerListCellView
221 |
222 | view.currencyName.stringValue = self.viewModel.getCurrencyName(row: row)
223 | view.currencyImageView.kf.setImage(with: self.viewModel.getCurrencyImageUrl(row: row))
224 | view.currencyPrice.stringValue = self.viewModel.getTargetPrice(row: row)
225 |
226 | view.pinButton.tag = row
227 | view.pinButton.target = self
228 | view.pinButton.action = #selector(onPinButtonClick(sender:))
229 | view.pinButton.state = self.viewModel.pinButtonState(row: row)
230 |
231 | return view
232 | }
233 |
234 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
235 | return 48.0
236 | }
237 |
238 | func numberOfRows(in tableView: NSTableView) -> Int {
239 | return self.viewModel.numberOfRows
240 | }
241 |
242 | func onPinButtonClick(sender: NSButton) {
243 | self.viewModel.pinStatusChanged(row: sender.tag, pinned: sender.state == 1)
244 | }
245 |
246 | func startRefreshAnimation() {
247 | let anim = CABasicAnimation(keyPath: "transform.rotation.z")
248 | anim.fromValue = 0
249 | anim.toValue = Double.pi * -2
250 | anim.duration = 0.75
251 | anim.repeatCount = HUGE
252 |
253 | if let frame = self.refreshButton.layer?.frame {
254 | let center = CGPoint(x: frame.midX, y: frame.midY)
255 | self.refreshButton.layer?.position = center
256 | self.refreshButton.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
257 | self.refreshButton.layer?.add(anim, forKey: nil)
258 | }
259 | }
260 |
261 | func stopRefreshAnimation() {
262 | self.refreshButton.layer?.removeAllAnimations()
263 | }
264 |
265 | func openPdf(resourceName: String) {
266 | AppDelegate.shared().menuBarView?.hidePopover()
267 |
268 | if let pdfURL = Bundle.main.url(forResource: resourceName, withExtension: "pdf") {
269 | NSWorkspace.shared().open(pdfURL)
270 | }
271 | }
272 |
273 | func hideNewsContainer() {
274 | self.tickerListTopConstraint.constant = 0;
275 | }
276 |
277 | func showNewsContainer() {
278 | self.tickerListTopConstraint.constant = self.newsContainer.frame.size.height
279 | }
280 | }
281 |
282 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Nova.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0A2D3E361F5AC429002510BA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2D3E351F5AC429002510BA /* AppDelegate.swift */; };
11 | 0A2D3E381F5AC429002510BA /* TickerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2D3E371F5AC429002510BA /* TickerListViewController.swift */; };
12 | 0A2D3E3A1F5AC429002510BA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A2D3E391F5AC429002510BA /* Assets.xcassets */; };
13 | 0A2D3E3D1F5AC429002510BA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A2D3E3B1F5AC429002510BA /* Main.storyboard */; };
14 | 0A2D3E481F5AC429002510BA /* NovaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2D3E471F5AC429002510BA /* NovaTests.swift */; };
15 | 0A2D3E551F5AFA2A002510BA /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2D3E541F5AFA2A002510BA /* Constants.swift */; };
16 | 0A2D3E5A1F5B4F24002510BA /* CoinMarketCapProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2D3E591F5B4F24002510BA /* CoinMarketCapProvider.swift */; };
17 | 0A52028E1F65631E00F71C1E /* TickerListCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A52028D1F65631E00F71C1E /* TickerListCellView.swift */; };
18 | 0A8F66381F766B2F00723F8F /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8F66371F766B2F00723F8F /* EventMonitor.swift */; };
19 | 0A924C921F5C54D100A8875E /* TickerListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A924C911F5C54D100A8875E /* TickerListViewModel.swift */; };
20 | 0AA23C8C1FA6525A000FF632 /* PriceFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA23C8B1FA6525A000FF632 /* PriceFormatter.swift */; };
21 | 0AB59B0A1F699CF400798BBB /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB59B091F699CF400798BBB /* Extensions.swift */; };
22 | 0AB59B0C1F69A6AD00798BBB /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB59B0B1F69A6AD00798BBB /* MenuBarView.swift */; };
23 | 0AB59B0E1F69A6B300798BBB /* MenuBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB59B0D1F69A6B300798BBB /* MenuBarViewModel.swift */; };
24 | 0AB59B111F69A80900798BBB /* Prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB59B101F69A80900798BBB /* Prefs.swift */; };
25 | 0ACB1FA11F74F430009C3D2D /* CryptoCompareProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACB1FA01F74F430009C3D2D /* CryptoCompareProvider.swift */; };
26 | 0ACE435E1F769A41008059A7 /* license.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 0ACE435D1F769A41008059A7 /* license.pdf */; };
27 | 0ACE43601F769A5D008059A7 /* acknowledgements.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 0ACE435F1F769A5D008059A7 /* acknowledgements.pdf */; };
28 | 0AF23FA11F5BEC4200AB0D15 /* Ticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AF23FA01F5BEC4200AB0D15 /* Ticker.swift */; };
29 | 0AF549F71FF67D910097430E /* FirebaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AF549F61FF67D910097430E /* FirebaseProvider.swift */; };
30 | 0AF549F91FF796250097430E /* News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AF549F81FF796250097430E /* News.swift */; };
31 | 771EBD911F72BE6F00F756ED /* Schedulers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771EBD901F72BE6F00F756ED /* Schedulers.swift */; };
32 | 7763D05B1F6FAA0C009EE03E /* RemoteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7763D05A1F6FAA0C009EE03E /* RemoteDataSource.swift */; };
33 | 7763D05D1F6FAA18009EE03E /* LocalDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7763D05C1F6FAA18009EE03E /* LocalDataSource.swift */; };
34 | 7763D0601F6FB683009EE03E /* Injector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7763D05F1F6FB683009EE03E /* Injector.swift */; };
35 | 7763D0641F6FCE64009EE03E /* DataRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7763D0631F6FCE64009EE03E /* DataRepository.swift */; };
36 | 7764E6621F7A5F3200FC67F0 /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7764E6611F7A5F3200FC67F0 /* ContactViewController.swift */; };
37 | 7764E6651F7A962900FC67F0 /* DonateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7764E6631F7A962900FC67F0 /* DonateViewController.swift */; };
38 | D418FF040586A9DD73E9A005 /* Pods_Nova.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AC2ED320E03871B42E360B4 /* Pods_Nova.framework */; };
39 | /* End PBXBuildFile section */
40 |
41 | /* Begin PBXContainerItemProxy section */
42 | 0A2D3E441F5AC429002510BA /* PBXContainerItemProxy */ = {
43 | isa = PBXContainerItemProxy;
44 | containerPortal = 0A2D3E2A1F5AC429002510BA /* Project object */;
45 | proxyType = 1;
46 | remoteGlobalIDString = 0A2D3E311F5AC429002510BA;
47 | remoteInfo = Nova;
48 | };
49 | /* End PBXContainerItemProxy section */
50 |
51 | /* Begin PBXFileReference section */
52 | 0A2D3E321F5AC429002510BA /* Nova.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nova.app; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 0A2D3E351F5AC429002510BA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
54 | 0A2D3E371F5AC429002510BA /* TickerListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TickerListViewController.swift; sourceTree = ""; };
55 | 0A2D3E391F5AC429002510BA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
56 | 0A2D3E3C1F5AC429002510BA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
57 | 0A2D3E3E1F5AC429002510BA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
58 | 0A2D3E431F5AC429002510BA /* NovaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NovaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
59 | 0A2D3E471F5AC429002510BA /* NovaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NovaTests.swift; sourceTree = ""; };
60 | 0A2D3E491F5AC429002510BA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | 0A2D3E541F5AFA2A002510BA /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
62 | 0A2D3E591F5B4F24002510BA /* CoinMarketCapProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinMarketCapProvider.swift; sourceTree = ""; };
63 | 0A52028D1F65631E00F71C1E /* TickerListCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TickerListCellView.swift; sourceTree = ""; };
64 | 0A8F66371F766B2F00723F8F /* EventMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventMonitor.swift; sourceTree = ""; };
65 | 0A924C911F5C54D100A8875E /* TickerListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TickerListViewModel.swift; sourceTree = ""; };
66 | 0AA23C8B1FA6525A000FF632 /* PriceFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceFormatter.swift; sourceTree = ""; };
67 | 0AB59B091F699CF400798BBB /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
68 | 0AB59B0B1F69A6AD00798BBB /* MenuBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBarView.swift; sourceTree = ""; };
69 | 0AB59B0D1F69A6B300798BBB /* MenuBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBarViewModel.swift; sourceTree = ""; };
70 | 0AB59B101F69A80900798BBB /* Prefs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Prefs.swift; sourceTree = ""; };
71 | 0ACB1FA01F74F430009C3D2D /* CryptoCompareProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoCompareProvider.swift; sourceTree = ""; };
72 | 0ACE435D1F769A41008059A7 /* license.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = license.pdf; sourceTree = ""; };
73 | 0ACE435F1F769A5D008059A7 /* acknowledgements.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = acknowledgements.pdf; sourceTree = ""; };
74 | 0AF23FA01F5BEC4200AB0D15 /* Ticker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ticker.swift; sourceTree = ""; };
75 | 0AF549F61FF67D910097430E /* FirebaseProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseProvider.swift; sourceTree = ""; };
76 | 0AF549F81FF796250097430E /* News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = News.swift; sourceTree = ""; };
77 | 207457C11AEF35045A801412 /* Pods-Nova.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nova.release.xcconfig"; path = "Pods/Target Support Files/Pods-Nova/Pods-Nova.release.xcconfig"; sourceTree = ""; };
78 | 4AC2ED320E03871B42E360B4 /* Pods_Nova.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Nova.framework; sourceTree = BUILT_PRODUCTS_DIR; };
79 | 770C67611F8373A700D3D104 /* Nova.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Nova.entitlements; sourceTree = ""; };
80 | 771EBD901F72BE6F00F756ED /* Schedulers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schedulers.swift; sourceTree = ""; };
81 | 7763D05A1F6FAA0C009EE03E /* RemoteDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteDataSource.swift; sourceTree = ""; };
82 | 7763D05C1F6FAA18009EE03E /* LocalDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalDataSource.swift; sourceTree = ""; };
83 | 7763D05F1F6FB683009EE03E /* Injector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Injector.swift; sourceTree = ""; };
84 | 7763D0631F6FCE64009EE03E /* DataRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataRepository.swift; sourceTree = ""; };
85 | 7764E6611F7A5F3200FC67F0 /* ContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewController.swift; sourceTree = ""; };
86 | 7764E6631F7A962900FC67F0 /* DonateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DonateViewController.swift; sourceTree = ""; };
87 | C31629810B46A92A7F784CDD /* Pods-Nova.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nova.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Nova/Pods-Nova.debug.xcconfig"; sourceTree = ""; };
88 | DB43C4AF6E52B7B34E2E6C3C /* Pods_NovaTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NovaTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
89 | /* End PBXFileReference section */
90 |
91 | /* Begin PBXFrameworksBuildPhase section */
92 | 0A2D3E2F1F5AC429002510BA /* Frameworks */ = {
93 | isa = PBXFrameworksBuildPhase;
94 | buildActionMask = 2147483647;
95 | files = (
96 | D418FF040586A9DD73E9A005 /* Pods_Nova.framework in Frameworks */,
97 | );
98 | runOnlyForDeploymentPostprocessing = 0;
99 | };
100 | 0A2D3E401F5AC429002510BA /* Frameworks */ = {
101 | isa = PBXFrameworksBuildPhase;
102 | buildActionMask = 2147483647;
103 | files = (
104 | );
105 | runOnlyForDeploymentPostprocessing = 0;
106 | };
107 | /* End PBXFrameworksBuildPhase section */
108 |
109 | /* Begin PBXGroup section */
110 | 0A2D3E291F5AC429002510BA = {
111 | isa = PBXGroup;
112 | children = (
113 | F2B85A38647AABC95B5FF6C8 /* Frameworks */,
114 | 0A2D3E341F5AC429002510BA /* Nova */,
115 | 0A2D3E461F5AC429002510BA /* NovaTests */,
116 | E32EDFE3368E06439B9064F1 /* Pods */,
117 | 0A2D3E331F5AC429002510BA /* Products */,
118 | );
119 | sourceTree = "";
120 | };
121 | 0A2D3E331F5AC429002510BA /* Products */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 0A2D3E321F5AC429002510BA /* Nova.app */,
125 | 0A2D3E431F5AC429002510BA /* NovaTests.xctest */,
126 | );
127 | name = Products;
128 | sourceTree = "";
129 | };
130 | 0A2D3E341F5AC429002510BA /* Nova */ = {
131 | isa = PBXGroup;
132 | children = (
133 | 0A2D3E561F5B4EAE002510BA /* data */,
134 | 7763D0561F6FA888009EE03E /* repository */,
135 | 7763D0651F6FD043009EE03E /* support */,
136 | 0A924C8F1F5C516300A8875E /* view */,
137 | 0A2D3E351F5AC429002510BA /* AppDelegate.swift */,
138 | 0A2D3E391F5AC429002510BA /* Assets.xcassets */,
139 | 0A2D3E3E1F5AC429002510BA /* Info.plist */,
140 | 7763D05F1F6FB683009EE03E /* Injector.swift */,
141 | 0A2D3E3B1F5AC429002510BA /* Main.storyboard */,
142 | 770C67611F8373A700D3D104 /* Nova.entitlements */,
143 | );
144 | path = Nova;
145 | sourceTree = "";
146 | };
147 | 0A2D3E461F5AC429002510BA /* NovaTests */ = {
148 | isa = PBXGroup;
149 | children = (
150 | 0A2D3E491F5AC429002510BA /* Info.plist */,
151 | 0A2D3E471F5AC429002510BA /* NovaTests.swift */,
152 | );
153 | path = NovaTests;
154 | sourceTree = "";
155 | };
156 | 0A2D3E561F5B4EAE002510BA /* data */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 0AF549F51FF67D6E0097430E /* firebase */,
160 | 777467491F681E5A00F01AF3 /* coinmarketcap */,
161 | 0ACB1F9E1F74F296009C3D2D /* cryptocompare */,
162 | );
163 | path = data;
164 | sourceTree = "";
165 | };
166 | 0A924C8F1F5C516300A8875E /* view */ = {
167 | isa = PBXGroup;
168 | children = (
169 | 0AB59B0F1F69A6B600798BBB /* menubar */,
170 | 0A924C901F5C54BC00A8875E /* tickerlist */,
171 | 7764E6611F7A5F3200FC67F0 /* ContactViewController.swift */,
172 | 7764E6631F7A962900FC67F0 /* DonateViewController.swift */,
173 | );
174 | path = view;
175 | sourceTree = "";
176 | };
177 | 0A924C901F5C54BC00A8875E /* tickerlist */ = {
178 | isa = PBXGroup;
179 | children = (
180 | 0A52028D1F65631E00F71C1E /* TickerListCellView.swift */,
181 | 0A2D3E371F5AC429002510BA /* TickerListViewController.swift */,
182 | 0A924C911F5C54D100A8875E /* TickerListViewModel.swift */,
183 | );
184 | path = tickerlist;
185 | sourceTree = "";
186 | };
187 | 0AB59B0F1F69A6B600798BBB /* menubar */ = {
188 | isa = PBXGroup;
189 | children = (
190 | 0AB59B0B1F69A6AD00798BBB /* MenuBarView.swift */,
191 | 0AB59B0D1F69A6B300798BBB /* MenuBarViewModel.swift */,
192 | );
193 | path = menubar;
194 | sourceTree = "";
195 | };
196 | 0ACB1F9E1F74F296009C3D2D /* cryptocompare */ = {
197 | isa = PBXGroup;
198 | children = (
199 | 0ACB1FA01F74F430009C3D2D /* CryptoCompareProvider.swift */,
200 | );
201 | path = cryptocompare;
202 | sourceTree = "";
203 | };
204 | 0AF549F51FF67D6E0097430E /* firebase */ = {
205 | isa = PBXGroup;
206 | children = (
207 | 0AF549F61FF67D910097430E /* FirebaseProvider.swift */,
208 | 0AF549F81FF796250097430E /* News.swift */,
209 | );
210 | path = firebase;
211 | sourceTree = "";
212 | };
213 | 0AF9200E1F76935B00C62EBA /* pdf */ = {
214 | isa = PBXGroup;
215 | children = (
216 | 0ACE435F1F769A5D008059A7 /* acknowledgements.pdf */,
217 | 0ACE435D1F769A41008059A7 /* license.pdf */,
218 | );
219 | path = pdf;
220 | sourceTree = "";
221 | };
222 | 7763D0561F6FA888009EE03E /* repository */ = {
223 | isa = PBXGroup;
224 | children = (
225 | 7763D0631F6FCE64009EE03E /* DataRepository.swift */,
226 | 7763D05C1F6FAA18009EE03E /* LocalDataSource.swift */,
227 | 7763D05A1F6FAA0C009EE03E /* RemoteDataSource.swift */,
228 | );
229 | path = repository;
230 | sourceTree = "";
231 | };
232 | 7763D0651F6FD043009EE03E /* support */ = {
233 | isa = PBXGroup;
234 | children = (
235 | 0AF9200E1F76935B00C62EBA /* pdf */,
236 | 0A2D3E541F5AFA2A002510BA /* Constants.swift */,
237 | 0A8F66371F766B2F00723F8F /* EventMonitor.swift */,
238 | 0AB59B091F699CF400798BBB /* Extensions.swift */,
239 | 0AB59B101F69A80900798BBB /* Prefs.swift */,
240 | 0AA23C8B1FA6525A000FF632 /* PriceFormatter.swift */,
241 | 771EBD901F72BE6F00F756ED /* Schedulers.swift */,
242 | );
243 | path = support;
244 | sourceTree = "";
245 | };
246 | 777467491F681E5A00F01AF3 /* coinmarketcap */ = {
247 | isa = PBXGroup;
248 | children = (
249 | 0A2D3E591F5B4F24002510BA /* CoinMarketCapProvider.swift */,
250 | 0AF23FA01F5BEC4200AB0D15 /* Ticker.swift */,
251 | );
252 | path = coinmarketcap;
253 | sourceTree = "";
254 | };
255 | E32EDFE3368E06439B9064F1 /* Pods */ = {
256 | isa = PBXGroup;
257 | children = (
258 | C31629810B46A92A7F784CDD /* Pods-Nova.debug.xcconfig */,
259 | 207457C11AEF35045A801412 /* Pods-Nova.release.xcconfig */,
260 | );
261 | name = Pods;
262 | sourceTree = "";
263 | };
264 | F2B85A38647AABC95B5FF6C8 /* Frameworks */ = {
265 | isa = PBXGroup;
266 | children = (
267 | 4AC2ED320E03871B42E360B4 /* Pods_Nova.framework */,
268 | DB43C4AF6E52B7B34E2E6C3C /* Pods_NovaTests.framework */,
269 | );
270 | name = Frameworks;
271 | sourceTree = "";
272 | };
273 | /* End PBXGroup section */
274 |
275 | /* Begin PBXNativeTarget section */
276 | 0A2D3E311F5AC429002510BA /* Nova */ = {
277 | isa = PBXNativeTarget;
278 | buildConfigurationList = 0A2D3E4C1F5AC429002510BA /* Build configuration list for PBXNativeTarget "Nova" */;
279 | buildPhases = (
280 | D325D3843B7EE25F13F42E0F /* [CP] Check Pods Manifest.lock */,
281 | 0A2D3E2E1F5AC429002510BA /* Sources */,
282 | 0A2D3E2F1F5AC429002510BA /* Frameworks */,
283 | 0A2D3E301F5AC429002510BA /* Resources */,
284 | 122A1166610F127D6296BD09 /* [CP] Embed Pods Frameworks */,
285 | 5787E495D0977E4A24807FAD /* [CP] Copy Pods Resources */,
286 | );
287 | buildRules = (
288 | );
289 | dependencies = (
290 | );
291 | name = Nova;
292 | productName = Nova;
293 | productReference = 0A2D3E321F5AC429002510BA /* Nova.app */;
294 | productType = "com.apple.product-type.application";
295 | };
296 | 0A2D3E421F5AC429002510BA /* NovaTests */ = {
297 | isa = PBXNativeTarget;
298 | buildConfigurationList = 0A2D3E4F1F5AC429002510BA /* Build configuration list for PBXNativeTarget "NovaTests" */;
299 | buildPhases = (
300 | 0A2D3E3F1F5AC429002510BA /* Sources */,
301 | 0A2D3E401F5AC429002510BA /* Frameworks */,
302 | 0A2D3E411F5AC429002510BA /* Resources */,
303 | );
304 | buildRules = (
305 | );
306 | dependencies = (
307 | 0A2D3E451F5AC429002510BA /* PBXTargetDependency */,
308 | );
309 | name = NovaTests;
310 | productName = NovaTests;
311 | productReference = 0A2D3E431F5AC429002510BA /* NovaTests.xctest */;
312 | productType = "com.apple.product-type.bundle.unit-test";
313 | };
314 | /* End PBXNativeTarget section */
315 |
316 | /* Begin PBXProject section */
317 | 0A2D3E2A1F5AC429002510BA /* Project object */ = {
318 | isa = PBXProject;
319 | attributes = {
320 | LastSwiftUpdateCheck = 0800;
321 | LastUpgradeCheck = 0800;
322 | ORGANIZATIONNAME = "Andrej Jurkin";
323 | TargetAttributes = {
324 | 0A2D3E311F5AC429002510BA = {
325 | CreatedOnToolsVersion = 8.0;
326 | DevelopmentTeam = XJF3S8KP2L;
327 | ProvisioningStyle = Automatic;
328 | SystemCapabilities = {
329 | com.apple.Sandbox = {
330 | enabled = 1;
331 | };
332 | };
333 | };
334 | 0A2D3E421F5AC429002510BA = {
335 | CreatedOnToolsVersion = 8.0;
336 | DevelopmentTeam = XJF3S8KP2L;
337 | ProvisioningStyle = Automatic;
338 | TestTargetID = 0A2D3E311F5AC429002510BA;
339 | };
340 | };
341 | };
342 | buildConfigurationList = 0A2D3E2D1F5AC429002510BA /* Build configuration list for PBXProject "Nova" */;
343 | compatibilityVersion = "Xcode 3.2";
344 | developmentRegion = English;
345 | hasScannedForEncodings = 0;
346 | knownRegions = (
347 | en,
348 | Base,
349 | );
350 | mainGroup = 0A2D3E291F5AC429002510BA;
351 | productRefGroup = 0A2D3E331F5AC429002510BA /* Products */;
352 | projectDirPath = "";
353 | projectRoot = "";
354 | targets = (
355 | 0A2D3E311F5AC429002510BA /* Nova */,
356 | 0A2D3E421F5AC429002510BA /* NovaTests */,
357 | );
358 | };
359 | /* End PBXProject section */
360 |
361 | /* Begin PBXResourcesBuildPhase section */
362 | 0A2D3E301F5AC429002510BA /* Resources */ = {
363 | isa = PBXResourcesBuildPhase;
364 | buildActionMask = 2147483647;
365 | files = (
366 | 0A2D3E3A1F5AC429002510BA /* Assets.xcassets in Resources */,
367 | 0ACE435E1F769A41008059A7 /* license.pdf in Resources */,
368 | 0A2D3E3D1F5AC429002510BA /* Main.storyboard in Resources */,
369 | 0ACE43601F769A5D008059A7 /* acknowledgements.pdf in Resources */,
370 | );
371 | runOnlyForDeploymentPostprocessing = 0;
372 | };
373 | 0A2D3E411F5AC429002510BA /* Resources */ = {
374 | isa = PBXResourcesBuildPhase;
375 | buildActionMask = 2147483647;
376 | files = (
377 | );
378 | runOnlyForDeploymentPostprocessing = 0;
379 | };
380 | /* End PBXResourcesBuildPhase section */
381 |
382 | /* Begin PBXShellScriptBuildPhase section */
383 | 122A1166610F127D6296BD09 /* [CP] Embed Pods Frameworks */ = {
384 | isa = PBXShellScriptBuildPhase;
385 | buildActionMask = 2147483647;
386 | files = (
387 | );
388 | inputPaths = (
389 | "${SRCROOT}/Pods/Target Support Files/Pods-Nova/Pods-Nova-frameworks.sh",
390 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
391 | "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework",
392 | "${BUILT_PRODUCTS_DIR}/Moya/Moya.framework",
393 | "${BUILT_PRODUCTS_DIR}/Moya-ObjectMapper/Moya_ObjectMapper.framework",
394 | "${BUILT_PRODUCTS_DIR}/ObjectMapper/ObjectMapper.framework",
395 | "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework",
396 | "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework",
397 | "${BUILT_PRODUCTS_DIR}/Result/Result.framework",
398 | "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework",
399 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
400 | "${BUILT_PRODUCTS_DIR}/RxOptional/RxOptional.framework",
401 | "${BUILT_PRODUCTS_DIR}/RxRealm/RxRealm.framework",
402 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
403 | "${BUILT_PRODUCTS_DIR}/Swinject/Swinject.framework",
404 | "${BUILT_PRODUCTS_DIR}/SwinjectAutoregistration/SwinjectAutoregistration.framework",
405 | );
406 | name = "[CP] Embed Pods Frameworks";
407 | outputPaths = (
408 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
409 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework",
410 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Moya.framework",
411 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Moya_ObjectMapper.framework",
412 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectMapper.framework",
413 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework",
414 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework",
415 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework",
416 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAlamofire.framework",
417 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
418 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxOptional.framework",
419 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRealm.framework",
420 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
421 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Swinject.framework",
422 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwinjectAutoregistration.framework",
423 | );
424 | runOnlyForDeploymentPostprocessing = 0;
425 | shellPath = /bin/sh;
426 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Nova/Pods-Nova-frameworks.sh\"\n";
427 | showEnvVarsInLog = 0;
428 | };
429 | 5787E495D0977E4A24807FAD /* [CP] Copy Pods Resources */ = {
430 | isa = PBXShellScriptBuildPhase;
431 | buildActionMask = 2147483647;
432 | files = (
433 | );
434 | inputPaths = (
435 | );
436 | name = "[CP] Copy Pods Resources";
437 | outputPaths = (
438 | );
439 | runOnlyForDeploymentPostprocessing = 0;
440 | shellPath = /bin/sh;
441 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Nova/Pods-Nova-resources.sh\"\n";
442 | showEnvVarsInLog = 0;
443 | };
444 | D325D3843B7EE25F13F42E0F /* [CP] Check Pods Manifest.lock */ = {
445 | isa = PBXShellScriptBuildPhase;
446 | buildActionMask = 2147483647;
447 | files = (
448 | );
449 | inputPaths = (
450 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
451 | "${PODS_ROOT}/Manifest.lock",
452 | );
453 | name = "[CP] Check Pods Manifest.lock";
454 | outputPaths = (
455 | "$(DERIVED_FILE_DIR)/Pods-Nova-checkManifestLockResult.txt",
456 | );
457 | runOnlyForDeploymentPostprocessing = 0;
458 | shellPath = /bin/sh;
459 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
460 | showEnvVarsInLog = 0;
461 | };
462 | /* End PBXShellScriptBuildPhase section */
463 |
464 | /* Begin PBXSourcesBuildPhase section */
465 | 0A2D3E2E1F5AC429002510BA /* Sources */ = {
466 | isa = PBXSourcesBuildPhase;
467 | buildActionMask = 2147483647;
468 | files = (
469 | 0AB59B111F69A80900798BBB /* Prefs.swift in Sources */,
470 | 0AF549F91FF796250097430E /* News.swift in Sources */,
471 | 771EBD911F72BE6F00F756ED /* Schedulers.swift in Sources */,
472 | 0A2D3E551F5AFA2A002510BA /* Constants.swift in Sources */,
473 | 0A2D3E381F5AC429002510BA /* TickerListViewController.swift in Sources */,
474 | 0ACB1FA11F74F430009C3D2D /* CryptoCompareProvider.swift in Sources */,
475 | 0AB59B0E1F69A6B300798BBB /* MenuBarViewModel.swift in Sources */,
476 | 0AF23FA11F5BEC4200AB0D15 /* Ticker.swift in Sources */,
477 | 7764E6621F7A5F3200FC67F0 /* ContactViewController.swift in Sources */,
478 | 7763D05D1F6FAA18009EE03E /* LocalDataSource.swift in Sources */,
479 | 7763D05B1F6FAA0C009EE03E /* RemoteDataSource.swift in Sources */,
480 | 0A8F66381F766B2F00723F8F /* EventMonitor.swift in Sources */,
481 | 0A52028E1F65631E00F71C1E /* TickerListCellView.swift in Sources */,
482 | 0A2D3E5A1F5B4F24002510BA /* CoinMarketCapProvider.swift in Sources */,
483 | 0A2D3E361F5AC429002510BA /* AppDelegate.swift in Sources */,
484 | 7763D0601F6FB683009EE03E /* Injector.swift in Sources */,
485 | 0AB59B0C1F69A6AD00798BBB /* MenuBarView.swift in Sources */,
486 | 0AA23C8C1FA6525A000FF632 /* PriceFormatter.swift in Sources */,
487 | 0AF549F71FF67D910097430E /* FirebaseProvider.swift in Sources */,
488 | 7763D0641F6FCE64009EE03E /* DataRepository.swift in Sources */,
489 | 7764E6651F7A962900FC67F0 /* DonateViewController.swift in Sources */,
490 | 0AB59B0A1F699CF400798BBB /* Extensions.swift in Sources */,
491 | 0A924C921F5C54D100A8875E /* TickerListViewModel.swift in Sources */,
492 | );
493 | runOnlyForDeploymentPostprocessing = 0;
494 | };
495 | 0A2D3E3F1F5AC429002510BA /* Sources */ = {
496 | isa = PBXSourcesBuildPhase;
497 | buildActionMask = 2147483647;
498 | files = (
499 | 0A2D3E481F5AC429002510BA /* NovaTests.swift in Sources */,
500 | );
501 | runOnlyForDeploymentPostprocessing = 0;
502 | };
503 | /* End PBXSourcesBuildPhase section */
504 |
505 | /* Begin PBXTargetDependency section */
506 | 0A2D3E451F5AC429002510BA /* PBXTargetDependency */ = {
507 | isa = PBXTargetDependency;
508 | target = 0A2D3E311F5AC429002510BA /* Nova */;
509 | targetProxy = 0A2D3E441F5AC429002510BA /* PBXContainerItemProxy */;
510 | };
511 | /* End PBXTargetDependency section */
512 |
513 | /* Begin PBXVariantGroup section */
514 | 0A2D3E3B1F5AC429002510BA /* Main.storyboard */ = {
515 | isa = PBXVariantGroup;
516 | children = (
517 | 0A2D3E3C1F5AC429002510BA /* Base */,
518 | );
519 | name = Main.storyboard;
520 | path = .;
521 | sourceTree = "";
522 | };
523 | /* End PBXVariantGroup section */
524 |
525 | /* Begin XCBuildConfiguration section */
526 | 0A2D3E4A1F5AC429002510BA /* Debug */ = {
527 | isa = XCBuildConfiguration;
528 | buildSettings = {
529 | ALWAYS_SEARCH_USER_PATHS = NO;
530 | CLANG_ANALYZER_NONNULL = YES;
531 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
532 | CLANG_CXX_LIBRARY = "libc++";
533 | CLANG_ENABLE_MODULES = YES;
534 | CLANG_ENABLE_OBJC_ARC = YES;
535 | CLANG_WARN_BOOL_CONVERSION = YES;
536 | CLANG_WARN_CONSTANT_CONVERSION = YES;
537 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
538 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
539 | CLANG_WARN_EMPTY_BODY = YES;
540 | CLANG_WARN_ENUM_CONVERSION = YES;
541 | CLANG_WARN_INFINITE_RECURSION = YES;
542 | CLANG_WARN_INT_CONVERSION = YES;
543 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
544 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
545 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
546 | CLANG_WARN_UNREACHABLE_CODE = YES;
547 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
548 | CODE_SIGN_IDENTITY = "-";
549 | COPY_PHASE_STRIP = NO;
550 | DEBUG_INFORMATION_FORMAT = dwarf;
551 | ENABLE_STRICT_OBJC_MSGSEND = YES;
552 | ENABLE_TESTABILITY = YES;
553 | GCC_C_LANGUAGE_STANDARD = gnu99;
554 | GCC_DYNAMIC_NO_PIC = NO;
555 | GCC_NO_COMMON_BLOCKS = YES;
556 | GCC_OPTIMIZATION_LEVEL = 0;
557 | GCC_PREPROCESSOR_DEFINITIONS = (
558 | "DEBUG=1",
559 | "$(inherited)",
560 | );
561 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
562 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
563 | GCC_WARN_UNDECLARED_SELECTOR = YES;
564 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
565 | GCC_WARN_UNUSED_FUNCTION = YES;
566 | GCC_WARN_UNUSED_VARIABLE = YES;
567 | MACOSX_DEPLOYMENT_TARGET = 10.11;
568 | MTL_ENABLE_DEBUG_INFO = YES;
569 | ONLY_ACTIVE_ARCH = YES;
570 | SDKROOT = macosx;
571 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
572 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
573 | };
574 | name = Debug;
575 | };
576 | 0A2D3E4B1F5AC429002510BA /* Release */ = {
577 | isa = XCBuildConfiguration;
578 | buildSettings = {
579 | ALWAYS_SEARCH_USER_PATHS = NO;
580 | CLANG_ANALYZER_NONNULL = YES;
581 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
582 | CLANG_CXX_LIBRARY = "libc++";
583 | CLANG_ENABLE_MODULES = YES;
584 | CLANG_ENABLE_OBJC_ARC = YES;
585 | CLANG_WARN_BOOL_CONVERSION = YES;
586 | CLANG_WARN_CONSTANT_CONVERSION = YES;
587 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
588 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
589 | CLANG_WARN_EMPTY_BODY = YES;
590 | CLANG_WARN_ENUM_CONVERSION = YES;
591 | CLANG_WARN_INFINITE_RECURSION = YES;
592 | CLANG_WARN_INT_CONVERSION = YES;
593 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
594 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
595 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
596 | CLANG_WARN_UNREACHABLE_CODE = YES;
597 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
598 | CODE_SIGN_IDENTITY = "-";
599 | COPY_PHASE_STRIP = NO;
600 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
601 | ENABLE_NS_ASSERTIONS = NO;
602 | ENABLE_STRICT_OBJC_MSGSEND = YES;
603 | GCC_C_LANGUAGE_STANDARD = gnu99;
604 | GCC_NO_COMMON_BLOCKS = YES;
605 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
606 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
607 | GCC_WARN_UNDECLARED_SELECTOR = YES;
608 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
609 | GCC_WARN_UNUSED_FUNCTION = YES;
610 | GCC_WARN_UNUSED_VARIABLE = YES;
611 | MACOSX_DEPLOYMENT_TARGET = 10.11;
612 | MTL_ENABLE_DEBUG_INFO = NO;
613 | SDKROOT = macosx;
614 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
615 | };
616 | name = Release;
617 | };
618 | 0A2D3E4D1F5AC429002510BA /* Debug */ = {
619 | isa = XCBuildConfiguration;
620 | baseConfigurationReference = C31629810B46A92A7F784CDD /* Pods-Nova.debug.xcconfig */;
621 | buildSettings = {
622 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
623 | CODE_SIGN_ENTITLEMENTS = Nova/Nova.entitlements;
624 | CODE_SIGN_IDENTITY = "Mac Developer";
625 | COMBINE_HIDPI_IMAGES = YES;
626 | DEVELOPMENT_TEAM = XJF3S8KP2L;
627 | INFOPLIST_FILE = Nova/Info.plist;
628 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
629 | PRODUCT_BUNDLE_IDENTIFIER = jurkin.osx.Nova;
630 | PRODUCT_NAME = "$(TARGET_NAME)";
631 | SWIFT_VERSION = 3.0;
632 | };
633 | name = Debug;
634 | };
635 | 0A2D3E4E1F5AC429002510BA /* Release */ = {
636 | isa = XCBuildConfiguration;
637 | baseConfigurationReference = 207457C11AEF35045A801412 /* Pods-Nova.release.xcconfig */;
638 | buildSettings = {
639 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
640 | CODE_SIGN_ENTITLEMENTS = Nova/Nova.entitlements;
641 | CODE_SIGN_IDENTITY = "Mac Developer";
642 | COMBINE_HIDPI_IMAGES = YES;
643 | DEVELOPMENT_TEAM = XJF3S8KP2L;
644 | INFOPLIST_FILE = Nova/Info.plist;
645 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
646 | PRODUCT_BUNDLE_IDENTIFIER = jurkin.osx.Nova;
647 | PRODUCT_NAME = "$(TARGET_NAME)";
648 | SWIFT_VERSION = 3.0;
649 | };
650 | name = Release;
651 | };
652 | 0A2D3E501F5AC429002510BA /* Debug */ = {
653 | isa = XCBuildConfiguration;
654 | buildSettings = {
655 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
656 | BUNDLE_LOADER = "$(TEST_HOST)";
657 | COMBINE_HIDPI_IMAGES = YES;
658 | DEVELOPMENT_TEAM = XJF3S8KP2L;
659 | INFOPLIST_FILE = NovaTests/Info.plist;
660 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
661 | PRODUCT_BUNDLE_IDENTIFIER = jurkin.osx.NovaTests;
662 | PRODUCT_NAME = "$(TARGET_NAME)";
663 | SWIFT_VERSION = 3.0;
664 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nova.app/Contents/MacOS/Nova";
665 | };
666 | name = Debug;
667 | };
668 | 0A2D3E511F5AC429002510BA /* Release */ = {
669 | isa = XCBuildConfiguration;
670 | buildSettings = {
671 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
672 | BUNDLE_LOADER = "$(TEST_HOST)";
673 | COMBINE_HIDPI_IMAGES = YES;
674 | DEVELOPMENT_TEAM = XJF3S8KP2L;
675 | INFOPLIST_FILE = NovaTests/Info.plist;
676 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
677 | PRODUCT_BUNDLE_IDENTIFIER = jurkin.osx.NovaTests;
678 | PRODUCT_NAME = "$(TARGET_NAME)";
679 | SWIFT_VERSION = 3.0;
680 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nova.app/Contents/MacOS/Nova";
681 | };
682 | name = Release;
683 | };
684 | /* End XCBuildConfiguration section */
685 |
686 | /* Begin XCConfigurationList section */
687 | 0A2D3E2D1F5AC429002510BA /* Build configuration list for PBXProject "Nova" */ = {
688 | isa = XCConfigurationList;
689 | buildConfigurations = (
690 | 0A2D3E4A1F5AC429002510BA /* Debug */,
691 | 0A2D3E4B1F5AC429002510BA /* Release */,
692 | );
693 | defaultConfigurationIsVisible = 0;
694 | defaultConfigurationName = Release;
695 | };
696 | 0A2D3E4C1F5AC429002510BA /* Build configuration list for PBXNativeTarget "Nova" */ = {
697 | isa = XCConfigurationList;
698 | buildConfigurations = (
699 | 0A2D3E4D1F5AC429002510BA /* Debug */,
700 | 0A2D3E4E1F5AC429002510BA /* Release */,
701 | );
702 | defaultConfigurationIsVisible = 0;
703 | defaultConfigurationName = Release;
704 | };
705 | 0A2D3E4F1F5AC429002510BA /* Build configuration list for PBXNativeTarget "NovaTests" */ = {
706 | isa = XCConfigurationList;
707 | buildConfigurations = (
708 | 0A2D3E501F5AC429002510BA /* Debug */,
709 | 0A2D3E511F5AC429002510BA /* Release */,
710 | );
711 | defaultConfigurationIsVisible = 0;
712 | defaultConfigurationName = Release;
713 | };
714 | /* End XCConfigurationList section */
715 | };
716 | rootObject = 0A2D3E2A1F5AC429002510BA /* Project object */;
717 | }
718 |
--------------------------------------------------------------------------------