├── .gitignore ├── Cartfile ├── Cartfile.resolved ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── LightningKit.xcodeproj └── project.pbxproj ├── LightningKit ├── Extensions │ ├── Data.swift │ ├── EventLoopFuture.swift │ └── FileManager.swift ├── ILndNode.swift ├── Info.plist ├── Kit.swift ├── LightningKit.h ├── Local │ ├── LndMobileCallbacks │ │ ├── LndMobileCallbackError.swift │ │ ├── MessageResponseCallback.swift │ │ ├── MessageResponseStream.swift │ │ └── VoidResponseCallback.swift │ └── LocalLnd.swift ├── LocalNodeCredentials.swift ├── NodeStatus.swift ├── Proto │ ├── rpc.grpc.swift │ └── rpc.pb.swift ├── Remote │ ├── ConnectivityManager.swift │ ├── LndNioConnection.swift │ ├── RemoteLnd.swift │ └── WalletUnlocker.swift └── RpcCredentials.swift ├── LightningWallet.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── LightningWallet.xcscheme ├── LightningWallet.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── LightningWallet ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── LightningApp_1024.png │ │ ├── LightningApp_120-1.png │ │ ├── LightningApp_120.png │ │ ├── LightningApp_180.png │ │ ├── LightningApp_40.png │ │ ├── LightningApp_58.png │ │ ├── LightningApp_60.png │ │ ├── LightningApp_80.png │ │ └── LightningApp_87.png │ ├── Contents.json │ ├── Main │ │ ├── Arrow Down.imageset │ │ │ ├── Arrow Left@2x.png │ │ │ ├── Arrow Left@3x.png │ │ │ └── Contents.json │ │ ├── Arrow Up.imageset │ │ │ ├── Arrow Left@2x.png │ │ │ ├── Arrow Left@3x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Settings Icon.imageset │ │ │ ├── Contents.json │ │ │ ├── Settings@2x.png │ │ │ └── Settings@3x.png │ └── Settings │ │ ├── About Icon.imageset │ │ ├── Contents.json │ │ ├── info@2x.png │ │ └── info@3x.png │ │ ├── Contact Icon.imageset │ │ ├── Contents.json │ │ ├── report@2x.png │ │ └── report@3x.png │ │ ├── Contents.json │ │ ├── Currency Icon.imageset │ │ ├── Contents.json │ │ ├── currency@2x.png │ │ └── currency@3x.png │ │ ├── Face ID Icon.imageset │ │ ├── Contents.json │ │ ├── Face ID@2x.png │ │ └── Face ID@3x.png │ │ ├── Language Icon.imageset │ │ ├── Contents.json │ │ ├── language@2x.png │ │ └── language@3x.png │ │ ├── Light Mode Icon.imageset │ │ ├── Contents.json │ │ ├── lightMode@2x.png │ │ └── lightMode@3x.png │ │ ├── Logo Image.imageset │ │ ├── Contents.json │ │ ├── hs_center@2x.png │ │ └── hs_center@3x.png │ │ ├── Passcode Icon.imageset │ │ ├── Contents.json │ │ ├── Passcode@2x.png │ │ └── Passcode@3x.png │ │ ├── Security Icon.imageset │ │ ├── Contents.json │ │ ├── Security@2x.png │ │ └── Security@3x.png │ │ ├── Tell Friends Icon.imageset │ │ ├── Contents.json │ │ ├── share@2x.png │ │ └── share@3x.png │ │ ├── Touch ID Icon.imageset │ │ ├── Contents.json │ │ ├── Touch ID@2x.png │ │ └── Touch ID@3x.png │ │ └── settings.tab_bar_item.imageset │ │ ├── Contents.json │ │ ├── Settings@2x.png │ │ └── Settings@3x.png ├── Core │ ├── App.swift │ ├── Managers │ │ ├── AppManager.swift │ │ ├── LanguageManager.swift │ │ ├── LightningKitManager.swift │ │ ├── PinKitDelegate.swift │ │ └── WalletManager.swift │ ├── Models │ │ ├── LightningConnection.swift │ │ └── Wallet.swift │ ├── Protocols.swift │ └── Storage │ │ ├── KeychainKitDelegate.swift │ │ ├── LocalStorage.swift │ │ └── WalletStorage.swift ├── Info.plist ├── LaunchScreen.xib ├── Modules │ ├── Channels │ │ ├── ChannelCell.swift │ │ ├── ChannelsFilterCell.swift │ │ ├── ChannelsHeaderView.swift │ │ ├── ChannelsInteractor.swift │ │ ├── ChannelsModule.swift │ │ ├── ChannelsPresenter.swift │ │ ├── ChannelsRouter.swift │ │ └── ChannelsViewController.swift │ ├── Launch │ │ ├── LaunchInteractor.swift │ │ ├── LaunchModule.swift │ │ ├── LaunchPresenter.swift │ │ └── LaunchRouter.swift │ ├── LockScreen │ │ ├── LockScreenModule.swift │ │ ├── LockScreenPresenter.swift │ │ └── LockScreenRouter.swift │ ├── Main │ │ ├── MainInteractor.swift │ │ ├── MainModule.swift │ │ ├── MainPresenter.swift │ │ ├── MainRouter.swift │ │ └── MainViewController.swift │ ├── NodeConnect │ │ ├── NodeConnectInteractor.swift │ │ ├── NodeConnectModule.swift │ │ ├── NodeConnectPresenter.swift │ │ ├── NodeConnectRouter.swift │ │ └── NodeConnectViewController.swift │ ├── NodeCredentials │ │ ├── NodeCredentialsInteractor.swift │ │ ├── NodeCredentialsModule.swift │ │ ├── NodeCredentialsPresenter.swift │ │ ├── NodeCredentialsRouter.swift │ │ ├── NodeCredentialsViewController.swift │ │ └── NotificationTimer.swift │ ├── Settings │ │ ├── Main │ │ │ ├── MainSettingsFooter.swift │ │ │ ├── MainSettingsInteractor.swift │ │ │ ├── MainSettingsModule.swift │ │ │ ├── MainSettingsPresenter.swift │ │ │ ├── MainSettingsRouter.swift │ │ │ └── MainSettingsViewController.swift │ │ └── Security │ │ │ ├── SecuritySettingsInteractor.swift │ │ │ ├── SecuritySettingsModule.swift │ │ │ ├── SecuritySettingsPresenter.swift │ │ │ ├── SecuritySettingsRouter.swift │ │ │ └── SecuritySettingsViewController.swift │ ├── Transactions │ │ ├── TransactionCell.swift │ │ ├── TransactionViewItem.swift │ │ ├── TransactionsModule.swift │ │ ├── TransactionsPresenter.swift │ │ ├── TransactionsRouter.swift │ │ └── TransactionsViewController.swift │ ├── UnlockRemoteWallet │ │ ├── UnlockRemoteWalletInteractor.swift │ │ ├── UnlockRemoteWalletModule.swift │ │ ├── UnlockRemoteWalletPresenter.swift │ │ ├── UnlockRemoteWalletRouter.swift │ │ └── UnlockRemoteWalletViewController.swift │ └── Welcome │ │ ├── WelcomeScreenInteractor.swift │ │ ├── WelcomeScreenModule.swift │ │ ├── WelcomeScreenPresenter.swift │ │ ├── WelcomeScreenRouter.swift │ │ └── WelcomeScreenViewController.swift ├── UserInterface │ ├── ScanQr │ │ ├── PermissionHelper.swift │ │ ├── ScanQrAlertView.swift │ │ ├── ScanQrBlurView.swift │ │ └── ScanQrView.swift │ ├── TransparentNavigationBar.swift │ └── ValueFormatterFactory.swift ├── de.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── en.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── es.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── fr.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── ko.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── ru.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── tr.lproj │ ├── InfoPlist.strings │ └── Localizable.strings └── zh.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── Podfile ├── Podfile.lock ├── README.md └── fastlane ├── Fastfile └── Pluginfile /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .bundle/ 3 | Carthage/ 4 | build/* 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | Pods/* 18 | !Podfile.lock 19 | .idea/ 20 | .DS_Store 21 | /.irb-history 22 | /screenshot_*.png 23 | 24 | history_bash.txt 25 | 26 | #Fastlane 27 | 28 | fastlane/report.xml 29 | fastlane/README.md 30 | 31 | # Cuckoo 32 | 33 | GeneratedMocks.swift 34 | 35 | Development.xcconfig 36 | Production.xcconfig 37 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | binary "https://raw.githubusercontent.com/ottosuess/dependencies/master/Lndmobile.json" == 0.9.0 -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | binary "https://raw.githubusercontent.com/ottosuess/dependencies/master/Lndmobile.json" "0.9.0" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :fastlane do 4 | gem 'fastlane' 5 | gem 'cocoapods' 6 | gem 'xcodeproj' 7 | end 8 | 9 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 10 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | addressable (2.7.0) 11 | public_suffix (>= 2.0.2, < 5.0) 12 | algoliasearch (1.27.1) 13 | httpclient (~> 2.8, >= 2.8.3) 14 | json (>= 1.5.1) 15 | atomos (0.1.3) 16 | babosa (1.0.3) 17 | claide (1.0.3) 18 | cocoapods (1.8.4) 19 | activesupport (>= 4.0.2, < 5) 20 | claide (>= 1.0.2, < 2.0) 21 | cocoapods-core (= 1.8.4) 22 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 23 | cocoapods-downloader (>= 1.2.2, < 2.0) 24 | cocoapods-plugins (>= 1.0.0, < 2.0) 25 | cocoapods-search (>= 1.0.0, < 2.0) 26 | cocoapods-stats (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.4.0, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored2 (~> 3.1) 30 | escape (~> 0.0.4) 31 | fourflusher (>= 2.3.0, < 3.0) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.6.6) 34 | nap (~> 1.0) 35 | ruby-macho (~> 1.4) 36 | xcodeproj (>= 1.11.1, < 2.0) 37 | cocoapods-core (1.8.4) 38 | activesupport (>= 4.0.2, < 6) 39 | algoliasearch (~> 1.0) 40 | concurrent-ruby (~> 1.1) 41 | fuzzy_match (~> 2.0.4) 42 | nap (~> 1.0) 43 | cocoapods-deintegrate (1.0.4) 44 | cocoapods-downloader (1.3.0) 45 | cocoapods-plugins (1.0.0) 46 | nap 47 | cocoapods-search (1.0.0) 48 | cocoapods-stats (1.1.0) 49 | cocoapods-trunk (1.4.1) 50 | nap (>= 0.8, < 2.0) 51 | netrc (~> 0.11) 52 | cocoapods-try (1.1.0) 53 | colored (1.2) 54 | colored2 (3.1.2) 55 | commander-fastlane (4.4.6) 56 | highline (~> 1.7.2) 57 | concurrent-ruby (1.1.5) 58 | declarative (0.0.10) 59 | declarative-option (0.1.0) 60 | digest-crc (0.4.1) 61 | domain_name (0.5.20190701) 62 | unf (>= 0.0.5, < 1.0.0) 63 | dotenv (2.7.5) 64 | emoji_regex (1.0.1) 65 | escape (0.0.4) 66 | excon (0.72.0) 67 | faraday (0.17.3) 68 | multipart-post (>= 1.2, < 3) 69 | faraday-cookie_jar (0.0.6) 70 | faraday (>= 0.7.4) 71 | http-cookie (~> 1.0.0) 72 | faraday_middleware (0.13.1) 73 | faraday (>= 0.7.4, < 1.0) 74 | fastimage (2.1.7) 75 | fastlane (2.141.0) 76 | CFPropertyList (>= 2.3, < 4.0.0) 77 | addressable (>= 2.3, < 3.0.0) 78 | babosa (>= 1.0.2, < 2.0.0) 79 | bundler (>= 1.12.0, < 3.0.0) 80 | colored 81 | commander-fastlane (>= 4.4.6, < 5.0.0) 82 | dotenv (>= 2.1.1, < 3.0.0) 83 | emoji_regex (>= 0.1, < 2.0) 84 | excon (>= 0.71.0, < 1.0.0) 85 | faraday (~> 0.17) 86 | faraday-cookie_jar (~> 0.0.6) 87 | faraday_middleware (~> 0.13.1) 88 | fastimage (>= 2.1.0, < 3.0.0) 89 | gh_inspector (>= 1.1.2, < 2.0.0) 90 | google-api-client (>= 0.29.2, < 0.37.0) 91 | google-cloud-storage (>= 1.15.0, < 2.0.0) 92 | highline (>= 1.7.2, < 2.0.0) 93 | json (< 3.0.0) 94 | jwt (~> 2.1.0) 95 | mini_magick (>= 4.9.4, < 5.0.0) 96 | multi_xml (~> 0.5) 97 | multipart-post (~> 2.0.0) 98 | plist (>= 3.1.0, < 4.0.0) 99 | public_suffix (~> 2.0.0) 100 | rubyzip (>= 1.3.0, < 2.0.0) 101 | security (= 0.1.3) 102 | simctl (~> 1.6.3) 103 | slack-notifier (>= 2.0.0, < 3.0.0) 104 | terminal-notifier (>= 2.0.0, < 3.0.0) 105 | terminal-table (>= 1.4.5, < 2.0.0) 106 | tty-screen (>= 0.6.3, < 1.0.0) 107 | tty-spinner (>= 0.8.0, < 1.0.0) 108 | word_wrap (~> 1.0.0) 109 | xcodeproj (>= 1.13.0, < 2.0.0) 110 | xcpretty (~> 0.3.0) 111 | xcpretty-travis-formatter (>= 0.0.3) 112 | fastlane-plugin-appcenter (1.8.0) 113 | fastlane-plugin-versioning (0.4.2) 114 | fourflusher (2.3.1) 115 | fuzzy_match (2.0.4) 116 | gh_inspector (1.1.3) 117 | google-api-client (0.36.4) 118 | addressable (~> 2.5, >= 2.5.1) 119 | googleauth (~> 0.9) 120 | httpclient (>= 2.8.1, < 3.0) 121 | mini_mime (~> 1.0) 122 | representable (~> 3.0) 123 | retriable (>= 2.0, < 4.0) 124 | signet (~> 0.12) 125 | google-cloud-core (1.5.0) 126 | google-cloud-env (~> 1.0) 127 | google-cloud-errors (~> 1.0) 128 | google-cloud-env (1.3.0) 129 | faraday (~> 0.11) 130 | google-cloud-errors (1.0.0) 131 | google-cloud-storage (1.25.1) 132 | addressable (~> 2.5) 133 | digest-crc (~> 0.4) 134 | google-api-client (~> 0.33) 135 | google-cloud-core (~> 1.2) 136 | googleauth (~> 0.9) 137 | mini_mime (~> 1.0) 138 | googleauth (0.10.0) 139 | faraday (~> 0.12) 140 | jwt (>= 1.4, < 3.0) 141 | memoist (~> 0.16) 142 | multi_json (~> 1.11) 143 | os (>= 0.9, < 2.0) 144 | signet (~> 0.12) 145 | highline (1.7.10) 146 | http-cookie (1.0.3) 147 | domain_name (~> 0.5) 148 | httpclient (2.8.3) 149 | i18n (0.9.5) 150 | concurrent-ruby (~> 1.0) 151 | json (2.3.0) 152 | jwt (2.1.0) 153 | memoist (0.16.2) 154 | mini_magick (4.10.1) 155 | mini_mime (1.0.2) 156 | minitest (5.14.0) 157 | molinillo (0.6.6) 158 | multi_json (1.14.1) 159 | multi_xml (0.6.0) 160 | multipart-post (2.0.0) 161 | nanaimo (0.2.6) 162 | nap (1.1.0) 163 | naturally (2.2.0) 164 | netrc (0.11.0) 165 | os (1.0.1) 166 | plist (3.5.0) 167 | public_suffix (2.0.5) 168 | representable (3.0.4) 169 | declarative (< 0.1.0) 170 | declarative-option (< 0.2.0) 171 | uber (< 0.2.0) 172 | retriable (3.1.2) 173 | rouge (2.0.7) 174 | ruby-macho (1.4.0) 175 | rubyzip (1.3.0) 176 | security (0.1.3) 177 | signet (0.12.0) 178 | addressable (~> 2.3) 179 | faraday (~> 0.9) 180 | jwt (>= 1.5, < 3.0) 181 | multi_json (~> 1.10) 182 | simctl (1.6.8) 183 | CFPropertyList 184 | naturally 185 | slack-notifier (2.3.2) 186 | terminal-notifier (2.0.0) 187 | terminal-table (1.8.0) 188 | unicode-display_width (~> 1.1, >= 1.1.1) 189 | thread_safe (0.3.6) 190 | tty-cursor (0.7.1) 191 | tty-screen (0.7.1) 192 | tty-spinner (0.9.3) 193 | tty-cursor (~> 0.7) 194 | tzinfo (1.2.6) 195 | thread_safe (~> 0.1) 196 | uber (0.1.0) 197 | unf (0.1.4) 198 | unf_ext 199 | unf_ext (0.0.7.6) 200 | unicode-display_width (1.6.1) 201 | word_wrap (1.0.0) 202 | xcodeproj (1.15.0) 203 | CFPropertyList (>= 2.3.3, < 4.0) 204 | atomos (~> 0.1.3) 205 | claide (>= 1.0.2, < 2.0) 206 | colored2 (~> 3.1) 207 | nanaimo (~> 0.2.6) 208 | xcpretty (0.3.0) 209 | rouge (~> 2.0.7) 210 | xcpretty-travis-formatter (1.0.0) 211 | xcpretty (~> 0.2, >= 0.0.7) 212 | 213 | PLATFORMS 214 | ruby 215 | 216 | DEPENDENCIES 217 | cocoapods 218 | fastlane 219 | fastlane-plugin-appcenter 220 | fastlane-plugin-versioning 221 | xcodeproj 222 | 223 | BUNDLED WITH 224 | 2.0.2 225 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LightningKit/Extensions/Data.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Data { 4 | public init?(hex: String) { 5 | let len = hex.count / 2 6 | var data = Data(capacity: len) 7 | for i in 0.. Single { 6 | Single.create { emitter in 7 | self.whenSuccess { emitter(.success($0)) } 8 | self.whenFailure { emitter(.error($0)) } 9 | 10 | return Disposables.create() 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LightningKit/Extensions/FileManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension FileManager { 4 | class UnableToGetWalletDirector: Error {} 5 | 6 | public func walletDirectory() throws -> URL { 7 | guard let applicationSupportDirectory = urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { 8 | throw UnableToGetWalletDirector() 9 | } 10 | let url = applicationSupportDirectory.appendingPathComponent("lnd", isDirectory: true) 11 | try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) 12 | 13 | return url 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LightningKit/ILndNode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GRPC 3 | import RxSwift 4 | 5 | protocol ILndNode { 6 | var statusObservable: Observable { get } 7 | var invoicesObservable: Observable { get } 8 | var channelsObservable: Observable { get } 9 | var transactionsObservable: Observable { get } 10 | 11 | var infoSingle: Single { get } 12 | var walletBalanceSingle: Single { get } 13 | var channelBalanceSingle: Single { get } 14 | var onChainAddressSingle: Single { get } 15 | 16 | var channelsSingle: Single { get } 17 | var closedChannelsSingle: Single { get } 18 | var pendingChannelsSingle: Single { get } 19 | var paymentsSingle: Single { get } 20 | var transactionsSingle: Single { get } 21 | 22 | func invoicesSingle(request: Lnrpc_ListInvoiceRequest) -> Single 23 | func paySingle(request: Lnrpc_SendRequest) -> Single 24 | func addInvoiceSingle(invoice: Lnrpc_Invoice) -> Single 25 | func unlockWalletSingle(request: Lnrpc_UnlockWalletRequest) -> Single 26 | func decodeSingle(paymentRequest: Lnrpc_PayReqString) -> Single 27 | func openChannelSingle(request: Lnrpc_OpenChannelRequest) -> Observable 28 | func closeChannelSingle(request: Lnrpc_CloseChannelRequest) throws -> Observable 29 | func connectSingle(request: Lnrpc_ConnectPeerRequest) -> Single 30 | } 31 | -------------------------------------------------------------------------------- /LightningKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /LightningKit/LightningKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // LightningKit.h 3 | // LightningKit 4 | // 5 | // Created by Ermat Alymbaev on 2/17/20. 6 | // Copyright © 2020 Horizontal Systems. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for LightningKit. 12 | FOUNDATION_EXPORT double LightningKitVersionNumber; 13 | 14 | //! Project version string for LightningKit. 15 | FOUNDATION_EXPORT const unsigned char LightningKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /LightningKit/Local/LndMobileCallbacks/LndMobileCallbackError.swift: -------------------------------------------------------------------------------- 1 | enum LndMobileCallbackError: Error { 2 | case unknownError 3 | case responseCannotBeDecoded 4 | case nilResponse 5 | } 6 | -------------------------------------------------------------------------------- /LightningKit/Local/LndMobileCallbacks/MessageResponseCallback.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import Lndmobile 3 | import SwiftProtobuf 4 | 5 | class MessageResponseCallback: NSObject, LndmobileCallbackProtocol { 6 | private let emitter: (SingleEvent) -> Void 7 | 8 | init(emitter: @escaping (SingleEvent) -> Void) { 9 | self.emitter = emitter 10 | } 11 | 12 | func onError(_ error: Error?) { 13 | emitter(.error(error ?? LndMobileCallbackError.unknownError)) 14 | } 15 | 16 | func onResponse(_ response: Data?) { 17 | guard let responseData = response else { 18 | emitter(.success(T())) 19 | return 20 | } 21 | 22 | guard let responseMessage = try? T(serializedData: responseData) else { 23 | emitter(.error(LndMobileCallbackError.responseCannotBeDecoded)) 24 | return 25 | } 26 | 27 | emitter(.success(responseMessage)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LightningKit/Local/LndMobileCallbacks/MessageResponseStream.swift: -------------------------------------------------------------------------------- 1 | import Lndmobile 2 | import RxSwift 3 | import SwiftProtobuf 4 | 5 | class MessageResponseStream: NSObject, LndmobileRecvStreamProtocol { 6 | private let emitter: AnyObserver 7 | 8 | 9 | init(emitter: AnyObserver) { 10 | self.emitter = emitter 11 | } 12 | 13 | func onError(_ error: Error?) { 14 | emitter.onError(error ?? LndMobileCallbackError.unknownError) 15 | } 16 | 17 | func onResponse(_ response: Data?) { 18 | guard let responseData = response else { 19 | emitter.onNext(T()) 20 | return 21 | } 22 | 23 | guard let responseMessage = try? T(serializedData: responseData) else { 24 | emitter.onError(LndMobileCallbackError.responseCannotBeDecoded) 25 | return 26 | } 27 | 28 | emitter.onNext(responseMessage) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LightningKit/Local/LndMobileCallbacks/VoidResponseCallback.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import Lndmobile 3 | 4 | class VoidResponseCallback: NSObject, LndmobileCallbackProtocol { 5 | private let emitter: ((SingleEvent) -> Void)? 6 | 7 | init(emitter: ((SingleEvent) -> Void)?) { 8 | self.emitter = emitter 9 | } 10 | 11 | func onError(_ error: Error?) { 12 | emitter?(.error(error ?? LndMobileCallbackError.unknownError)) 13 | } 14 | 15 | func onResponse(_ response: Data?) { 16 | emitter?(.success(Void())) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LightningKit/LocalNodeCredentials.swift: -------------------------------------------------------------------------------- 1 | public struct LocalNodeCredentials { 2 | public let lndDirPath: String 3 | public let password: String 4 | 5 | public init(lndDirPath: String, password: String) { 6 | self.lndDirPath = lndDirPath 7 | self.password = password 8 | } 9 | 10 | } 11 | 12 | extension LocalNodeCredentials: Codable { 13 | } 14 | 15 | extension LocalNodeCredentials: CustomStringConvertible { 16 | 17 | public var description: String { 18 | "[lndUrl: \(lndDirPath); password: ***]" 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /LightningKit/NodeStatus.swift: -------------------------------------------------------------------------------- 1 | public enum NodeStatus { 2 | case connecting 3 | case locked 4 | case unlocking 5 | case syncing 6 | case running 7 | case error(_ error: Error) 8 | } 9 | 10 | extension NodeStatus: Equatable { 11 | public static func == (lhs: NodeStatus, rhs: NodeStatus) -> Bool { 12 | switch (lhs, rhs) { 13 | case (.connecting, .connecting), (.locked, .locked), (.unlocking, .unlocking), (.syncing, .syncing), (.running, .running), (.error, .error): return true 14 | default: return false 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LightningKit/Remote/ConnectivityManager.swift: -------------------------------------------------------------------------------- 1 | import GRPC 2 | import RxSwift 3 | 4 | class ConnectivityManager { 5 | private var available: Bool? = nil 6 | private var onAvailabilityChangeCallback: ((Bool) -> Void)? 7 | 8 | private var availabilitySingle: Single { 9 | Single.create { [weak self] emitter in 10 | self?.onAvailabilityChangeCallback = { emitter(.success($0)) } 11 | return Disposables.create() 12 | }.do(onDispose: { [weak self] in 13 | self?.onAvailabilityChangeCallback = nil 14 | }) 15 | } 16 | 17 | var isConnected: Bool { 18 | available ?? false 19 | } 20 | 21 | func runIfConnected(_ callFunction: @escaping () -> Single) -> Single { 22 | guard let available = self.available else { 23 | // Here, the availability is unknown, that is, the connection establishment is not completed yet. 24 | // So, we wait until it completes and we learn wether the connection is established or an error has occured. 25 | 26 | return availabilitySingle.flatMap { available in 27 | available 28 | ? callFunction() 29 | : Single.error(GRPCStatus(code: .unavailable, message: "Not connected to remote node")) 30 | } 31 | } 32 | 33 | return available 34 | ? callFunction() 35 | : Single.error(GRPCStatus(code: .unavailable, message: "Not connected to remote node")) 36 | } 37 | } 38 | 39 | extension ConnectivityManager: ConnectivityStateDelegate { 40 | func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) { 41 | if newState == .ready { 42 | available = true 43 | onAvailabilityChangeCallback?(true) 44 | } else if newState == .transientFailure { 45 | available = false 46 | onAvailabilityChangeCallback?(false) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LightningKit/Remote/LndNioConnection.swift: -------------------------------------------------------------------------------- 1 | import GRPC 2 | import NIO 3 | import NIOSSL 4 | import NIOHPACK 5 | import RxSwift 6 | 7 | class LndNioConnection { 8 | private var connectivityManager: ConnectivityManager 9 | 10 | deinit { 11 | group.shutdownGracefully({_ in}) 12 | } 13 | 14 | private let group: MultiThreadedEventLoopGroup 15 | let lightningClient: Lnrpc_LightningServiceClient 16 | let walletUnlockClient: Lnrpc_WalletUnlockerServiceClient 17 | 18 | init(rpcCredentials: RpcCredentials) throws { 19 | group = MultiThreadedEventLoopGroup(numberOfThreads: 1) 20 | 21 | let certificateBytes: [UInt8] = Array(rpcCredentials.certificate.utf8) 22 | let certificate = try NIOSSLCertificate(bytes: certificateBytes, format: .pem) 23 | let tlsConfig = ClientConnection.Configuration.TLS( 24 | trustRoots: .certificates([certificate]) 25 | ) 26 | 27 | let callOptions = CallOptions( 28 | customMetadata: HPACKHeaders([("macaroon", rpcCredentials.macaroon)]) 29 | ) 30 | 31 | connectivityManager = ConnectivityManager() 32 | 33 | let config = ClientConnection.Configuration( 34 | target: .hostAndPort(rpcCredentials.host, rpcCredentials.port), 35 | eventLoopGroup: group, 36 | connectivityStateDelegate: connectivityManager, 37 | tls: tlsConfig 38 | ) 39 | 40 | let connection = ClientConnection(configuration: config) 41 | 42 | lightningClient = Lnrpc_LightningServiceClient(connection: connection, defaultCallOptions: callOptions) 43 | walletUnlockClient = Lnrpc_WalletUnlockerServiceClient(connection: connection, defaultCallOptions: callOptions) 44 | } 45 | 46 | func walletUnlockerUnaryCall(_ callFunction: @escaping (Lnrpc_WalletUnlockerServiceClient) -> EventLoopFuture) -> Single { 47 | connectivityManager.runIfConnected { 48 | callFunction(self.walletUnlockClient).toSingle() 49 | } 50 | } 51 | 52 | func unaryCall(_ callFunction: @escaping (Lnrpc_LightningServiceClient) -> EventLoopFuture) -> Single { 53 | connectivityManager.runIfConnected { 54 | callFunction(self.lightningClient).toSingle() 55 | } 56 | } 57 | 58 | func serverStreamCall(_ callFunction: @escaping (Lnrpc_LightningServiceClient, @escaping (T) -> Void) -> ServerStreamingCall) -> Observable { 59 | guard connectivityManager.isConnected else { 60 | return Observable.error(GRPCStatus(code: .unavailable, message: "Not connected to remote node")) 61 | } 62 | 63 | return Observable.create { [weak self] emitter in 64 | guard let connection = self else { 65 | emitter.onCompleted() 66 | return Disposables.create() 67 | } 68 | 69 | let call = callFunction(connection.lightningClient) { response in 70 | emitter.onNext(response) 71 | } 72 | 73 | call.status.whenSuccess({ status in 74 | if status != .ok { 75 | emitter.onError(status) 76 | } 77 | 78 | }) 79 | 80 | call.status.whenComplete({ _ in emitter.onCompleted() }) 81 | 82 | return Disposables.create() 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /LightningKit/Remote/RemoteLnd.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GRPC 3 | import NIO 4 | import NIOSSL 5 | import NIOHPACK 6 | import Logging 7 | import RxSwift 8 | 9 | class RemoteLnd: ILndNode { 10 | private let connection: LndNioConnection 11 | private let walletUnlocker: WalletUnlocker 12 | private let disposeBag = DisposeBag() 13 | 14 | private let statusSubject = BehaviorSubject(value: .connecting) 15 | private(set) var status: NodeStatus = .connecting { 16 | didSet { 17 | if status != oldValue { 18 | statusSubject.onNext(status) 19 | } 20 | } 21 | } 22 | 23 | var statusObservable: Observable { 24 | statusSubject.asObservable() 25 | } 26 | 27 | var invoicesObservable: Observable { 28 | connection.serverStreamCall() { client, handler -> ServerStreamingCall in 29 | client.subscribeInvoices(Lnrpc_InvoiceSubscription(), handler: handler) 30 | } 31 | } 32 | 33 | var channelsObservable: Observable { 34 | connection.serverStreamCall() { client, handler -> ServerStreamingCall in 35 | client.subscribeChannelEvents(Lnrpc_ChannelEventSubscription(), handler: handler) 36 | } 37 | } 38 | 39 | var transactionsObservable: Observable { 40 | connection.serverStreamCall() { client, handler -> ServerStreamingCall in 41 | client.subscribeTransactions(Lnrpc_GetTransactionsRequest(), handler: handler) 42 | } 43 | } 44 | 45 | var infoSingle: Single { 46 | connection.unaryCall() { 47 | $0.getInfo(Lnrpc_GetInfoRequest()).response 48 | } 49 | } 50 | 51 | var walletBalanceSingle: Single { 52 | connection.unaryCall() { 53 | $0.walletBalance(Lnrpc_WalletBalanceRequest()).response 54 | } 55 | } 56 | 57 | var channelBalanceSingle: Single { 58 | connection.unaryCall() { 59 | $0.channelBalance(Lnrpc_ChannelBalanceRequest()).response 60 | } 61 | } 62 | 63 | var onChainAddressSingle: Single { 64 | connection.unaryCall() { 65 | $0.newAddress(Lnrpc_NewAddressRequest()).response 66 | } 67 | } 68 | 69 | var channelsSingle: Single { 70 | connection.unaryCall() { 71 | $0.listChannels(Lnrpc_ListChannelsRequest()).response 72 | } 73 | } 74 | 75 | var closedChannelsSingle: Single { 76 | connection.unaryCall() { 77 | $0.closedChannels(Lnrpc_ClosedChannelsRequest()).response 78 | } 79 | } 80 | 81 | var pendingChannelsSingle: Single { 82 | connection.unaryCall() { 83 | $0.pendingChannels(Lnrpc_PendingChannelsRequest()).response 84 | } 85 | } 86 | 87 | var paymentsSingle: Single { 88 | connection.unaryCall() { 89 | $0.listPayments(Lnrpc_ListPaymentsRequest()).response 90 | } 91 | } 92 | 93 | var transactionsSingle: Single { 94 | connection.unaryCall { 95 | $0.getTransactions(Lnrpc_GetTransactionsRequest()).response 96 | } 97 | } 98 | 99 | init(rpcCredentials: RpcCredentials) throws { 100 | connection = try LndNioConnection(rpcCredentials: rpcCredentials) 101 | walletUnlocker = WalletUnlocker(connection: connection) 102 | } 103 | 104 | func scheduleStatusUpdates() { 105 | Observable.interval(.seconds(3), scheduler: SerialDispatchQueueScheduler(qos: .background)) 106 | .flatMap { [weak self] _ -> Observable in 107 | guard let node = self else { 108 | return .empty() 109 | } 110 | 111 | return node.fetchStatusSingle().asObservable() 112 | } 113 | .subscribe(onNext: { [weak self] in self?.status = $0 }) 114 | .disposed(by: disposeBag) 115 | } 116 | 117 | func invoicesSingle(request: Lnrpc_ListInvoiceRequest) -> Single { 118 | connection.unaryCall() { 119 | $0.listInvoices(request).response 120 | } 121 | } 122 | 123 | func paySingle(request: Lnrpc_SendRequest) -> Single { 124 | connection.unaryCall() { 125 | $0.sendPaymentSync(request).response 126 | } 127 | } 128 | 129 | func addInvoiceSingle(invoice: Lnrpc_Invoice) -> Single { 130 | connection.unaryCall() { 131 | $0.addInvoice(invoice).response 132 | } 133 | } 134 | 135 | func unlockWalletSingle(request: Lnrpc_UnlockWalletRequest) -> Single { 136 | if walletUnlocker.isUnlocking() { 137 | return Single.error(WalletUnlocker.UnlockingException()) 138 | } 139 | 140 | return walletUnlocker.startUnlock(request: request) 141 | } 142 | 143 | func decodeSingle(paymentRequest: Lnrpc_PayReqString) -> Single { 144 | connection.unaryCall() { 145 | $0.decodePayReq(paymentRequest).response 146 | } 147 | } 148 | 149 | func openChannelSingle(request: Lnrpc_OpenChannelRequest) -> Observable { 150 | connection.serverStreamCall() { client, handler -> ServerStreamingCall in 151 | client.openChannel(request, handler: handler) 152 | } 153 | } 154 | 155 | func closeChannelSingle(request: Lnrpc_CloseChannelRequest) throws -> Observable { 156 | connection.serverStreamCall { client, handler -> ServerStreamingCall in 157 | client.closeChannel(request, handler: handler) 158 | } 159 | } 160 | 161 | func connectSingle(request: Lnrpc_ConnectPeerRequest) -> Single { 162 | connection.unaryCall() { 163 | $0.connectPeer(request).response 164 | } 165 | } 166 | 167 | func validateAsync() -> Single { 168 | fetchStatusSingle() 169 | .flatMap { 170 | if case let .error(error) = $0 { 171 | return Single.error(error) 172 | } else { 173 | return Single.just(Void()) 174 | } 175 | } 176 | } 177 | 178 | private func fetchStatusSingle() -> Single { 179 | infoSingle 180 | .map { 181 | $0.syncedToGraph ? .running : .syncing 182 | } 183 | .catchError { error in 184 | var status: NodeStatus 185 | 186 | if let grpcStatusError = error as? GRPCStatus { 187 | if grpcStatusError.code == .unimplemented { 188 | status = .locked 189 | } else if 190 | grpcStatusError.code == .unavailable && self.walletUnlocker.isUnlocking() { 191 | status = .unlocking 192 | } else { 193 | status = .error(grpcStatusError) 194 | } 195 | } else { 196 | status = .error(error) 197 | } 198 | 199 | return Single.just(status) 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /LightningKit/Remote/WalletUnlocker.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import GRPC 3 | import RxSwift 4 | import NIO 5 | 6 | class WalletUnlocker { 7 | class UnlockingException: Error {} 8 | 9 | private let unlockWaitTime: TimeInterval = 30 // seconds 10 | private let connection: LndNioConnection 11 | private var unlockFinishTime: TimeInterval? 12 | 13 | init(connection: LndNioConnection) { 14 | self.connection = connection 15 | } 16 | 17 | func startUnlock(request: Lnrpc_UnlockWalletRequest) -> Single { 18 | connection.walletUnlockerUnaryCall() { (client) -> EventLoopFuture in 19 | client.unlockWallet(request).response 20 | } 21 | .map { _ in Void() } 22 | .do( 23 | afterSuccess: { [weak self] _ in 24 | self?.unlockFinishTime = Date().timeIntervalSince1970 25 | }, 26 | onError: { [weak self] _ in 27 | self?.unlockFinishTime = nil 28 | } 29 | ) 30 | } 31 | 32 | func isUnlocking() -> Bool { 33 | if let time = unlockFinishTime { 34 | return time + unlockWaitTime > Date().timeIntervalSince1970 35 | } 36 | 37 | return false 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LightningKit/RpcCredentials.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct RpcCredentials { 4 | public let host: String 5 | public let port: Int 6 | public let certificate: String 7 | public let macaroon: String 8 | 9 | public init(host: String, port: Int, certificate: String, macaroon: String) { 10 | self.host = host 11 | self.port = port 12 | self.certificate = certificate 13 | self.macaroon = macaroon 14 | } 15 | 16 | public init?(lndConnectUrlString: String) { 17 | let string = lndConnectUrlString.trimmingCharacters(in: .whitespacesAndNewlines) 18 | guard let url = URL(string: string) else { return nil } 19 | self.init(lndConnectUrl: url) 20 | } 21 | 22 | public init?(lndConnectUrl: URL) { 23 | guard 24 | let queryParameters = lndConnectUrl.queryParameters, 25 | let host = lndConnectUrl.host, 26 | let port = lndConnectUrl.port, 27 | let macaroonString = queryParameters["macaroon"]?.base64UrlToBase64().base64ToHex(), 28 | let certificateString = queryParameters["cert"]?.base64UrlToBase64() 29 | else { return nil } 30 | 31 | self.init(host: host, port: port, certificate: Pem(key: certificateString).string, macaroon: macaroonString) 32 | } 33 | 34 | } 35 | 36 | extension RpcCredentials: Codable { 37 | } 38 | 39 | extension RpcCredentials: CustomStringConvertible { 40 | 41 | public var description: String { 42 | "[host: \(host); port: \(port); certificate: \(certificate.count) char(s); macaroon: \(macaroon.count) char(s)]" 43 | } 44 | 45 | } 46 | 47 | fileprivate extension String { 48 | func separate(every: Int, with separator: String) -> String { 49 | let result = stride(from: 0, to: count, by: every) 50 | .map { Array(Array(self)[$0.. String? { 56 | guard let data = Data(base64Encoded: self) else { 57 | return nil 58 | } 59 | 60 | return data.hex 61 | } 62 | 63 | func base64UrlToBase64() -> String { 64 | var base64 = self 65 | .replacingOccurrences(of: "-", with: "+") 66 | .replacingOccurrences(of: "_", with: "/") 67 | 68 | if base64.count % 4 != 0 { 69 | base64.append(String(repeating: "=", count: 4 - base64.count % 4)) 70 | } 71 | 72 | return base64 73 | } 74 | } 75 | 76 | fileprivate extension URL { 77 | var queryParameters: [String: String]? { 78 | guard 79 | let components = URLComponents(url: self, resolvingAgainstBaseURL: true), 80 | let queryItems = components.queryItems 81 | else { return nil } 82 | 83 | var parameters = [String: String]() 84 | for item in queryItems { 85 | parameters[item.name] = item.value 86 | } 87 | return parameters 88 | } 89 | } 90 | 91 | fileprivate class Pem { 92 | private let prefix = "-----BEGIN CERTIFICATE-----" 93 | private let suffix = "-----END CERTIFICATE-----" 94 | let string: String 95 | 96 | init(key: String) { 97 | if key.hasPrefix(prefix) { 98 | string = key 99 | } else { 100 | string = "\(prefix)\n\(key.separate(every: 64, with: "\n"))\n\(suffix)\n" 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /LightningWallet.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LightningWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LightningWallet.xcodeproj/xcshareddata/xcschemes/LightningWallet.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /LightningWallet.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LightningWallet.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LightningWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "GRPC", 6 | "repositoryURL": "git@github.com:grpc/grpc-swift.git", 7 | "state": { 8 | "branch": "nio", 9 | "revision": "e3ab183d95a2ee77929240e23b119426d581c821", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "swift-log", 15 | "repositoryURL": "https://github.com/apple/swift-log", 16 | "state": { 17 | "branch": null, 18 | "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", 19 | "version": "1.2.0" 20 | } 21 | }, 22 | { 23 | "package": "swift-nio", 24 | "repositoryURL": "https://github.com/apple/swift-nio.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "16ab4d657e1ad4e77bd5f8b94af8538561643053", 28 | "version": "2.14.0" 29 | } 30 | }, 31 | { 32 | "package": "swift-nio-http2", 33 | "repositoryURL": "https://github.com/apple/swift-nio-http2.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "65ebab9db70623bf1b58305edaa26ee5629856ed", 37 | "version": "1.9.1" 38 | } 39 | }, 40 | { 41 | "package": "swift-nio-ssl", 42 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "af46d9b58fafbb76f9b01177568d435a1b024f99", 46 | "version": "2.6.2" 47 | } 48 | }, 49 | { 50 | "package": "swift-nio-transport-services", 51 | "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "c7f06384dc5ce7e8506de5ed9b59e35b4d88c64b", 55 | "version": "1.3.0" 56 | } 57 | }, 58 | { 59 | "package": "SwiftProtobuf", 60 | "repositoryURL": "https://github.com/apple/swift-protobuf.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "7790acf0a81d08429cb20375bf42a8c7d279c5fe", 64 | "version": "1.8.0" 65 | } 66 | } 67 | ] 68 | }, 69 | "version": 1 70 | } 71 | -------------------------------------------------------------------------------- /LightningWallet/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | var window: UIWindow? 7 | 8 | private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | App.shared.appManager.didFinishLaunching() 12 | Theme.updateNavigationBarTheme() 13 | 14 | window = UIWindow(frame: UIScreen.main.bounds) 15 | window?.makeKeyAndVisible() 16 | 17 | window?.backgroundColor = .themeTyler 18 | window?.rootViewController = LaunchRouter.module() 19 | 20 | UIApplication.shared.setMinimumBackgroundFetchInterval(3600) 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { 26 | App.shared.appManager.willResignActive() 27 | } 28 | 29 | func applicationDidBecomeActive(_ application: UIApplication) { 30 | App.shared.appManager.didBecomeActive() 31 | } 32 | 33 | func applicationDidEnterBackground(_ application: UIApplication) { 34 | App.shared.appManager.didEnterBackground() 35 | 36 | backgroundTask = UIApplication.shared.beginBackgroundTask { 37 | UIApplication.shared.endBackgroundTask(self.backgroundTask) 38 | self.backgroundTask = UIBackgroundTaskIdentifier.invalid 39 | } 40 | } 41 | 42 | func applicationWillEnterForeground(_ application: UIApplication) { 43 | App.shared.appManager.willEnterForeground() 44 | 45 | if backgroundTask != UIBackgroundTaskIdentifier.invalid { 46 | UIApplication.shared.endBackgroundTask(backgroundTask) 47 | backgroundTask = UIBackgroundTaskIdentifier.invalid 48 | } 49 | } 50 | 51 | func applicationWillTerminate(_ application: UIApplication) { 52 | App.shared.appManager.willTerminate() 53 | } 54 | 55 | func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool { 56 | if extensionPointIdentifier == .keyboard { 57 | //disable custom keyboards 58 | return false 59 | } 60 | return true 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "LightningApp_40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "LightningApp_60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "LightningApp_58.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "LightningApp_87.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "LightningApp_80.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "LightningApp_120.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "LightningApp_120-1.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "LightningApp_180.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "1024x1024", 53 | "idiom" : "ios-marketing", 54 | "filename" : "LightningApp_1024.png", 55 | "scale" : "1x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_1024.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_120-1.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_120.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_180.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_40.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_58.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_60.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_80.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/AppIcon.appiconset/LightningApp_87.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Arrow Down.imageset/Arrow Left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Main/Arrow Down.imageset/Arrow Left@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Arrow Down.imageset/Arrow Left@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Main/Arrow Down.imageset/Arrow Left@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Arrow Down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Arrow Left@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Arrow Left@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Arrow Up.imageset/Arrow Left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Main/Arrow Up.imageset/Arrow Left@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Arrow Up.imageset/Arrow Left@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Main/Arrow Up.imageset/Arrow Left@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Arrow Up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Arrow Left@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Arrow Left@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Settings Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Settings@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Settings@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Settings Icon.imageset/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Main/Settings Icon.imageset/Settings@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Main/Settings Icon.imageset/Settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Main/Settings Icon.imageset/Settings@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/About Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "info@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "info@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/About Icon.imageset/info@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/About Icon.imageset/info@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/About Icon.imageset/info@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/About Icon.imageset/info@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Contact Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "report@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "report@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Contact Icon.imageset/report@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Contact Icon.imageset/report@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Contact Icon.imageset/report@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Contact Icon.imageset/report@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Currency Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "currency@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "currency@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Currency Icon.imageset/currency@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Currency Icon.imageset/currency@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Currency Icon.imageset/currency@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Currency Icon.imageset/currency@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Face ID Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Face ID@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Face ID@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Face ID Icon.imageset/Face ID@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Face ID Icon.imageset/Face ID@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Face ID Icon.imageset/Face ID@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Face ID Icon.imageset/Face ID@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Language Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "language@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "language@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Language Icon.imageset/language@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Language Icon.imageset/language@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Language Icon.imageset/language@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Language Icon.imageset/language@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Light Mode Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lightMode@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lightMode@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Light Mode Icon.imageset/lightMode@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Light Mode Icon.imageset/lightMode@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Light Mode Icon.imageset/lightMode@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Light Mode Icon.imageset/lightMode@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Logo Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "hs_center@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "hs_center@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Logo Image.imageset/hs_center@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Logo Image.imageset/hs_center@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Logo Image.imageset/hs_center@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Logo Image.imageset/hs_center@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Passcode Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Passcode@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Passcode@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Passcode Icon.imageset/Passcode@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Passcode Icon.imageset/Passcode@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Passcode Icon.imageset/Passcode@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Passcode Icon.imageset/Passcode@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Security Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Security@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Security@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Security Icon.imageset/Security@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Security Icon.imageset/Security@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Security Icon.imageset/Security@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Security Icon.imageset/Security@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Tell Friends Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "share@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "share@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Tell Friends Icon.imageset/share@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Tell Friends Icon.imageset/share@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Tell Friends Icon.imageset/share@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Tell Friends Icon.imageset/share@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Touch ID Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Touch ID@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Touch ID@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Touch ID Icon.imageset/Touch ID@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Touch ID Icon.imageset/Touch ID@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/Touch ID Icon.imageset/Touch ID@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/Touch ID Icon.imageset/Touch ID@3x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/settings.tab_bar_item.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Settings@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "Settings@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/settings.tab_bar_item.imageset/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/settings.tab_bar_item.imageset/Settings@2x.png -------------------------------------------------------------------------------- /LightningWallet/Assets.xcassets/Settings/settings.tab_bar_item.imageset/Settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/Assets.xcassets/Settings/settings.tab_bar_item.imageset/Settings@3x.png -------------------------------------------------------------------------------- /LightningWallet/Core/App.swift: -------------------------------------------------------------------------------- 1 | import StorageKit 2 | import PinKit 3 | import CurrencyKit 4 | 5 | class App { 6 | static let shared = App() 7 | 8 | let keychainKit: IKeychainKit 9 | let pinKit: IPinKit 10 | let currencyKit: ICurrencyKit 11 | 12 | let walletStorage: WalletStorage 13 | let lightningKitManager: LightningKitManager 14 | let walletManager: WalletManager 15 | 16 | let keychainKitDelegate: KeychainKitDelegate 17 | let pinKitDelegate: PinKitDelegate 18 | 19 | let appManager: AppManager 20 | 21 | init() { 22 | keychainKit = KeychainKit(service: "io.horizontalsystems.lightning") 23 | pinKit = PinKit.Kit(secureStorage: keychainKit.secureStorage, localStorage: StorageKit.LocalStorage.default) 24 | currencyKit = CurrencyKit.Kit(localStorage: StorageKit.LocalStorage.default) 25 | 26 | walletStorage = WalletStorage(secureStorage: keychainKit.secureStorage) 27 | lightningKitManager = LightningKitManager() 28 | walletManager = WalletManager(walletStorage: walletStorage, lightningKitManager: lightningKitManager) 29 | 30 | keychainKitDelegate = KeychainKitDelegate() 31 | keychainKit.set(delegate: keychainKitDelegate) 32 | 33 | pinKitDelegate = PinKitDelegate() 34 | pinKit.set(delegate: pinKitDelegate) 35 | 36 | appManager = AppManager( 37 | keychainKit: keychainKit, 38 | pinKit: pinKit, 39 | walletManager: walletManager 40 | ) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /LightningWallet/Core/Managers/AppManager.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import StorageKit 3 | import PinKit 4 | 5 | class AppManager { 6 | private let keychainKit: IKeychainKit 7 | private let pinKit: IPinKit 8 | private let walletManager: WalletManager 9 | 10 | private let didBecomeActiveSubject = PublishSubject<()>() 11 | private let willEnterForegroundSubject = PublishSubject<()>() 12 | 13 | init(keychainKit: IKeychainKit, pinKit: IPinKit, walletManager: WalletManager) { 14 | self.keychainKit = keychainKit 15 | self.pinKit = pinKit 16 | self.walletManager = walletManager 17 | } 18 | 19 | } 20 | 21 | extension AppManager { 22 | 23 | func didFinishLaunching() { 24 | keychainKit.handleLaunch() 25 | pinKit.didFinishLaunching() 26 | walletManager.bootstrapStoredWallet() 27 | } 28 | 29 | func willResignActive() { 30 | } 31 | 32 | func didBecomeActive() { 33 | } 34 | 35 | func didEnterBackground() { 36 | pinKit.didEnterBackground() 37 | } 38 | 39 | func willEnterForeground() { 40 | keychainKit.handleForeground() 41 | pinKit.willEnterForeground() 42 | } 43 | 44 | func willTerminate() { 45 | } 46 | 47 | } 48 | 49 | extension AppManager: IAppManager { 50 | 51 | var didBecomeActiveObservable: Observable<()> { 52 | didBecomeActiveSubject.asObservable() 53 | } 54 | 55 | var willEnterForegroundObservable: Observable<()> { 56 | willEnterForegroundSubject.asObservable() 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /LightningWallet/Core/Managers/LanguageManager.swift: -------------------------------------------------------------------------------- 1 | import LanguageKit 2 | 3 | extension String { 4 | 5 | var localized: String { 6 | LanguageManager.shared.localize(string: self, bundle: Bundle.main) 7 | } 8 | 9 | func localized(_ arguments: CVarArg...) -> String { 10 | LanguageManager.shared.localize(string: self, bundle: Bundle.main, arguments: arguments) 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /LightningWallet/Core/Managers/LightningKitManager.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | class LightningKitManager { 4 | private(set) var currentKit: LightningKit.Kit? 5 | 6 | func loadKit(connection: LightningConnection) { 7 | switch connection { 8 | case .local: () 9 | case .remote(let credentials): 10 | currentKit = try? LightningKit.Kit.remote(rpcCredentials: credentials) 11 | } 12 | } 13 | 14 | func unloadKit() { 15 | currentKit = nil 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /LightningWallet/Core/Managers/PinKitDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PinKit 3 | 4 | class PinKitDelegate { 5 | weak var viewController: UIViewController? 6 | } 7 | 8 | extension PinKitDelegate: IPinKitDelegate { 9 | 10 | func onLock(delegate: IUnlockDelegate) { 11 | var controller = viewController 12 | 13 | while let presentedController = controller?.presentedViewController { 14 | controller = presentedController 15 | } 16 | 17 | controller?.present(App.shared.pinKit.unlockPinModule(delegate: delegate, enableBiometry: true, presentationStyle: .simple, cancellable: false), animated: true) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /LightningWallet/Core/Managers/WalletManager.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | class WalletManager { 4 | private let walletStorage: WalletStorage 5 | private let lightningKitManager: LightningKitManager 6 | 7 | init(walletStorage: WalletStorage, lightningKitManager: LightningKitManager) { 8 | self.walletStorage = walletStorage 9 | self.lightningKitManager = lightningKitManager 10 | } 11 | 12 | var hasStoredWallet: Bool { 13 | walletStorage.storedWallet != nil 14 | } 15 | 16 | func bootstrapStoredWallet() { 17 | if let wallet = walletStorage.storedWallet { 18 | lightningKitManager.loadKit(connection: wallet.connection) 19 | } 20 | } 21 | 22 | func saveAndBootstrapRemoteWallet(credentials: RpcCredentials) { 23 | let connection: LightningConnection = .remote(credentials: credentials) 24 | let wallet = Wallet(connection: connection) 25 | 26 | walletStorage.storedWallet = wallet 27 | lightningKitManager.loadKit(connection: connection) 28 | } 29 | 30 | func removeStoredWallet() { 31 | walletStorage.storedWallet = nil 32 | lightningKitManager.unloadKit() 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LightningWallet/Core/Models/LightningConnection.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | enum LightningConnection { 4 | case local 5 | case remote(credentials: RpcCredentials) 6 | } 7 | 8 | extension LightningConnection: Codable { 9 | 10 | private enum CodingKeys: String, CodingKey { 11 | case base, remoteRpcConfiguration 12 | } 13 | 14 | private enum Base: String, Codable { 15 | case local 16 | case remote 17 | } 18 | 19 | public init(from decoder: Decoder) throws { 20 | let container = try decoder.container(keyedBy: CodingKeys.self) 21 | let base = try container.decode(Base.self, forKey: .base) 22 | 23 | switch base { 24 | case .local: 25 | self = .local 26 | case .remote: 27 | let rpcCredentials = try container.decode(RpcCredentials.self, forKey: .remoteRpcConfiguration) 28 | self = .remote(credentials: rpcCredentials) 29 | } 30 | } 31 | 32 | public func encode(to encoder: Encoder) throws { 33 | var container = encoder.container(keyedBy: CodingKeys.self) 34 | 35 | switch self { 36 | case .local: 37 | try container.encode(Base.local, forKey: .base) 38 | case .remote(let rpcCredentials): 39 | try container.encode(Base.remote, forKey: .base) 40 | try container.encode(rpcCredentials, forKey: .remoteRpcConfiguration) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LightningWallet/Core/Models/Wallet.swift: -------------------------------------------------------------------------------- 1 | struct Wallet: Codable { 2 | let connection: LightningConnection 3 | } 4 | -------------------------------------------------------------------------------- /LightningWallet/Core/Protocols.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import CurrencyKit 3 | 4 | protocol IAppManager { 5 | var didBecomeActiveObservable: Observable<()> { get } 6 | var willEnterForegroundObservable: Observable<()> { get } 7 | } 8 | 9 | protocol ILocalStorage: class { 10 | } 11 | 12 | protocol IValueFormatterFactory { 13 | func currencyValue(balance: Decimal?, rate: Decimal?, currency: Currency) -> String? 14 | func coinBalance(balance: Decimal?) -> String? 15 | } 16 | -------------------------------------------------------------------------------- /LightningWallet/Core/Storage/KeychainKitDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import StorageKit 3 | 4 | class KeychainKitDelegate { 5 | 6 | private func show(viewController: UIViewController) { 7 | UIApplication.shared.keyWindow?.set(newRootController: viewController) 8 | } 9 | 10 | } 11 | 12 | extension KeychainKitDelegate: IKeychainKitDelegate { 13 | 14 | func onInitialLock() { 15 | } 16 | 17 | public func onLock() { 18 | show(viewController: NoPasscodeViewController()) 19 | } 20 | 21 | public func onUnlock() { 22 | show(viewController: LaunchRouter.module()) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /LightningWallet/Core/Storage/LocalStorage.swift: -------------------------------------------------------------------------------- 1 | import StorageKit 2 | 3 | class LocalStorage { 4 | private let storage: StorageKit.ILocalStorage 5 | 6 | init(storage: StorageKit.ILocalStorage) { 7 | self.storage = storage 8 | } 9 | 10 | } 11 | 12 | extension LocalStorage: ILocalStorage { 13 | } 14 | -------------------------------------------------------------------------------- /LightningWallet/Core/Storage/WalletStorage.swift: -------------------------------------------------------------------------------- 1 | import StorageKit 2 | 3 | class WalletStorage { 4 | private static let keyWallet = "wallet" 5 | 6 | private let secureStorage: ISecureStorage 7 | 8 | init(secureStorage: ISecureStorage) { 9 | self.secureStorage = secureStorage 10 | } 11 | 12 | var storedWallet: Wallet? { 13 | get { 14 | guard let data: Data = secureStorage.value(for: WalletStorage.keyWallet) else { 15 | return nil 16 | } 17 | 18 | return try? JSONDecoder().decode(Wallet.self, from: data) 19 | } 20 | set { 21 | if let newValue = newValue { 22 | guard let data = try? JSONEncoder().encode(newValue) else { 23 | // TODO: need to decide if we should remove value from keychain if encoding failed 24 | return 25 | } 26 | 27 | try? secureStorage.set(value: data, for: WalletStorage.keyWallet) 28 | } else { 29 | try? secureStorage.removeValue(for: WalletStorage.keyWallet) 30 | } 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LightningWallet/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | We need access to camera to scan QR codes. 25 | NSFaceIDUsageDescription 26 | We use Face ID to unlock wallet. 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /LightningWallet/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | 4 | class ChannelCell: UITableViewCell { 5 | private let label = UILabel() 6 | 7 | override init(style: CellStyle, reuseIdentifier: String?) { 8 | super.init(style: style, reuseIdentifier: reuseIdentifier) 9 | 10 | contentView.addSubview(label) 11 | label.snp.makeConstraints { maker in 12 | maker.edges.equalToSuperview() 13 | } 14 | 15 | label.numberOfLines = 0 16 | label.font = .systemFont(ofSize: 12) 17 | } 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | func bind(viewItem: ChannelViewItem) { 24 | label.text = "State: \(viewItem.state)\nRemote PubKey: \(viewItem.remotePubKey)\nLocalBalance: \(viewItem.localBalance)\nRemote Balance: \(viewItem.remoteBalance)" 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsFilterCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | 4 | class ChannelsFilterCell: UICollectionViewCell { 5 | private let roundedView = UIView() 6 | private let nameLabel = UILabel() 7 | 8 | override init(frame: CGRect) { 9 | super.init(frame: frame) 10 | 11 | contentView.addSubview(roundedView) 12 | roundedView.addSubview(nameLabel) 13 | 14 | roundedView.snp.makeConstraints { maker in 15 | maker.edges.equalToSuperview().inset(UIEdgeInsets(top: 6, left: 0, bottom: 10, right: 0)) 16 | } 17 | 18 | roundedView.layer.cornerRadius = 14 19 | roundedView.layer.borderWidth = .heightOnePixel 20 | roundedView.clipsToBounds = true 21 | 22 | nameLabel.snp.makeConstraints { maker in 23 | maker.center.equalToSuperview() 24 | } 25 | 26 | nameLabel.font = .subhead2 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | fatalError("not implemented") 31 | } 32 | 33 | override var isSelected: Bool { 34 | get { 35 | super.isSelected 36 | } 37 | set { 38 | super.isSelected = newValue 39 | bind(selected: newValue) 40 | } 41 | } 42 | 43 | func bind(title: String, selected: Bool) { 44 | nameLabel.text = title 45 | 46 | bind(selected: selected) 47 | } 48 | 49 | func bind(selected: Bool) { 50 | nameLabel.textColor = selected ? .themeDark : .themeOz 51 | roundedView.backgroundColor = selected ? .themeYellowD : .themeJeremy 52 | roundedView.layer.borderColor = selected ? UIColor.clear.cgColor : UIColor.themeSteel20.cgColor 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsHeaderView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | import UIExtensions 4 | 5 | class ChannelsHeaderView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { 6 | static let headerHeight: CGFloat = 44 7 | 8 | private let interitemSpacing: CGFloat = .margin2x 9 | private let sideMargin: CGFloat = .margin4x 10 | 11 | private var filters: [(String, (() -> ()))] 12 | private var collectionView: UICollectionView 13 | 14 | init(filters: [(String, (() -> ()))]) { 15 | self.filters = filters 16 | 17 | let layout = UICollectionViewFlowLayout() 18 | layout.scrollDirection = .horizontal 19 | layout.sectionInset = .zero 20 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 21 | 22 | super.init(frame: .zero) 23 | 24 | backgroundColor = .themeNavigationBarBackground 25 | 26 | collectionView.delegate = self 27 | collectionView.dataSource = self 28 | collectionView.contentInset = UIEdgeInsets(top: 0, left: sideMargin, bottom: 0, right: sideMargin) 29 | collectionView.allowsMultipleSelection = false 30 | collectionView.backgroundColor = .clear 31 | collectionView.showsHorizontalScrollIndicator = false 32 | collectionView.alwaysBounceHorizontal = false 33 | collectionView.selectItem(at: IndexPath(item: 0, section: 0), animated: false, scrollPosition: .left) 34 | 35 | collectionView.registerCell(forClass: ChannelsFilterCell.self) 36 | 37 | addSubview(collectionView) 38 | collectionView.snp.makeConstraints { maker in 39 | maker.edges.equalToSuperview() 40 | } 41 | } 42 | 43 | required init?(coder aDecoder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 48 | filters.count 49 | } 50 | 51 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 52 | collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ChannelsFilterCell.self), for: indexPath) 53 | } 54 | 55 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 56 | if let cell = cell as? ChannelsFilterCell { 57 | cell.bind(title: filters[indexPath.item].0.uppercased(), selected: collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false) 58 | } 59 | } 60 | 61 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 62 | CGSize(width: (collectionView.bounds.width - interitemSpacing - sideMargin * 2) / 2, height: ChannelsHeaderView.headerHeight) 63 | } 64 | 65 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 66 | interitemSpacing 67 | } 68 | 69 | func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { 70 | if let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first, selectedIndexPath == indexPath { 71 | return false 72 | } 73 | return true 74 | } 75 | 76 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 77 | filters[indexPath.item].1() 78 | collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsInteractor.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import LightningKit 3 | 4 | class ChannelsInteractor { 5 | weak var delegate: IChannelsInteractorDelegate? 6 | 7 | private let lightningKit: LightningKit.Kit 8 | 9 | private let disposeBag = DisposeBag() 10 | 11 | init(lightningKit: LightningKit.Kit) { 12 | self.lightningKit = lightningKit 13 | } 14 | 15 | } 16 | 17 | extension ChannelsInteractor: IChannelsInteractor { 18 | 19 | func fetchOpenChannels() { 20 | lightningKit.channelsSingle 21 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 22 | .observeOn(MainScheduler.instance) 23 | .subscribe(onSuccess: { [weak self] response in 24 | self?.delegate?.didUpdate(openChannels: response.channels) 25 | }) 26 | .disposed(by: disposeBag) 27 | } 28 | 29 | func fetchPendingChannels() { 30 | lightningKit.pendingChannelsSingle 31 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 32 | .observeOn(MainScheduler.instance) 33 | .subscribe(onSuccess: { [weak self] response in 34 | self?.delegate?.didUpdatePendingChannels(response: response) 35 | }) 36 | .disposed(by: disposeBag) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsModule.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | protocol IChannelsView: class { 4 | func show(viewItems: [ChannelViewItem]) 5 | } 6 | 7 | protocol IChannelsRouter { 8 | func dismiss() 9 | } 10 | 11 | protocol IChannelsViewDelegate { 12 | func onLoad() 13 | func onClose() 14 | func onNewChannel() 15 | func onSelectOpen() 16 | func onSelectClosed() 17 | } 18 | 19 | protocol IChannelsInteractor { 20 | func fetchOpenChannels() 21 | func fetchPendingChannels() 22 | } 23 | 24 | protocol IChannelsInteractorDelegate: AnyObject { 25 | func didUpdate(openChannels: [Lnrpc_Channel]) 26 | func didUpdatePendingChannels(response: Lnrpc_PendingChannelsResponse) 27 | } 28 | 29 | struct ChannelViewItem { 30 | let state: State 31 | let remotePubKey: String 32 | let localBalance: Int 33 | let remoteBalance: Int 34 | 35 | enum State { 36 | case open 37 | case pendingOpen 38 | case pendingClosing 39 | case pendingForceClosing 40 | case waitingClose 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsPresenter.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | class ChannelsPresenter { 4 | weak var view: IChannelsView? 5 | 6 | private let interactor: IChannelsInteractor 7 | private let router: IChannelsRouter 8 | 9 | private var openChannels: [Lnrpc_Channel] = [] 10 | private var pendingOpenChannels: [Lnrpc_PendingChannelsResponse.PendingOpenChannel] = [] 11 | private var pendingClosingChannels: [Lnrpc_PendingChannelsResponse.ClosedChannel] = [] 12 | private var pendingForceClosingChannels: [Lnrpc_PendingChannelsResponse.ForceClosedChannel] = [] 13 | private var waitingCloseChannels: [Lnrpc_PendingChannelsResponse.WaitingCloseChannel] = [] 14 | 15 | init(interactor: IChannelsInteractor, router: IChannelsRouter) { 16 | self.interactor = interactor 17 | self.router = router 18 | } 19 | 20 | private func syncView() { 21 | let factory = ChannelsViewItemFactory() 22 | 23 | let openChannelViewItems = openChannels.map { factory.viewItem(channel: $0) } 24 | let pendingOpenChannelViewItems = pendingOpenChannels.map { factory.viewItem(state: .pendingOpen, pendingChannel: $0.channel) } 25 | let pendingClosingChannelViewItems = pendingClosingChannels.map { factory.viewItem(state: .pendingClosing, pendingChannel: $0.channel) } 26 | let pendingForceClosingChannelViewItems = pendingForceClosingChannels.map { factory.viewItem(state: .pendingForceClosing, pendingChannel: $0.channel) } 27 | let waitingCloseChannelViewItems = waitingCloseChannels.map { factory.viewItem(state: .waitingClose, pendingChannel: $0.channel) } 28 | 29 | view?.show(viewItems: openChannelViewItems + pendingOpenChannelViewItems + pendingClosingChannelViewItems + pendingForceClosingChannelViewItems + waitingCloseChannelViewItems) 30 | } 31 | 32 | } 33 | 34 | extension ChannelsPresenter: IChannelsViewDelegate { 35 | 36 | func onLoad() { 37 | interactor.fetchOpenChannels() 38 | interactor.fetchPendingChannels() 39 | } 40 | 41 | func onClose() { 42 | router.dismiss() 43 | } 44 | 45 | func onNewChannel() { 46 | print("onNewChannel") 47 | } 48 | 49 | func onSelectOpen() { 50 | print("onSelectOpen") 51 | } 52 | 53 | func onSelectClosed() { 54 | print("onSelectClosed") 55 | } 56 | 57 | } 58 | 59 | extension ChannelsPresenter: IChannelsInteractorDelegate { 60 | 61 | func didUpdate(openChannels: [Lnrpc_Channel]) { 62 | self.openChannels = openChannels 63 | syncView() 64 | } 65 | 66 | func didUpdatePendingChannels(response: Lnrpc_PendingChannelsResponse) { 67 | self.pendingOpenChannels = response.pendingOpenChannels 68 | self.pendingClosingChannels = response.pendingClosingChannels 69 | self.pendingForceClosingChannels = response.pendingForceClosingChannels 70 | self.waitingCloseChannels = response.waitingCloseChannels 71 | syncView() 72 | } 73 | 74 | } 75 | 76 | class ChannelsViewItemFactory { 77 | 78 | func viewItem(channel: Lnrpc_Channel) -> ChannelViewItem { 79 | ChannelViewItem( 80 | state: .open, 81 | remotePubKey: channel.remotePubkey, 82 | localBalance: Int(channel.localBalance), 83 | remoteBalance: Int(channel.remoteBalance) 84 | ) 85 | } 86 | 87 | func viewItem(state: ChannelViewItem.State, pendingChannel: Lnrpc_PendingChannelsResponse.PendingChannel) -> ChannelViewItem { 88 | ChannelViewItem( 89 | state: state, 90 | remotePubKey: pendingChannel.remoteNodePub, 91 | localBalance: Int(pendingChannel.localBalance), 92 | remoteBalance: Int(pendingChannel.remoteBalance) 93 | ) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ChannelsRouter { 4 | weak var viewController: UIViewController? 5 | } 6 | 7 | extension ChannelsRouter: IChannelsRouter { 8 | 9 | func dismiss() { 10 | viewController?.dismiss(animated: true) 11 | } 12 | 13 | } 14 | 15 | extension ChannelsRouter { 16 | 17 | static func module() -> UIViewController { 18 | guard let lightningKit = App.shared.lightningKitManager.currentKit else { 19 | // TODO: show empty view controller with message 20 | fatalError() 21 | } 22 | 23 | let router = ChannelsRouter() 24 | let interactor = ChannelsInteractor(lightningKit: lightningKit) 25 | let presenter = ChannelsPresenter(interactor: interactor, router: router) 26 | let viewController = ChannelsViewController(delegate: presenter) 27 | 28 | router.viewController = viewController 29 | interactor.delegate = presenter 30 | presenter.view = viewController 31 | 32 | return viewController 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Channels/ChannelsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import UIExtensions 4 | import SnapKit 5 | 6 | class ChannelsViewController: ThemeViewController { 7 | private let delegate: IChannelsViewDelegate 8 | 9 | // private var channelsHeaderView: ChannelsHeaderView? 10 | private let tableView = UITableView() 11 | 12 | private let newChannelButton: UIButton = .appGray 13 | private let newChannelButtonWrapper = GradientView(gradientHeight: .margin4x, fromColor: UIColor.themeCassandra.withAlphaComponent(0), toColor: UIColor.themeCassandra.withAlphaComponent(0.9)) 14 | 15 | private var viewItems: [ChannelViewItem] = [] 16 | 17 | init(delegate: IChannelsViewDelegate) { 18 | self.delegate = delegate 19 | 20 | super.init() 21 | 22 | // channelsHeaderView = ChannelsHeaderView(filters: [ 23 | // ("open".localized.uppercased(), onSelectOpen), 24 | // ("closed".localized.uppercased(), onSelectClosed) 25 | // ]) 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | navigationController?.navigationBar.prefersLargeTitles = true 36 | title = "channels".localized 37 | 38 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "backup".localized, style: .plain, target: self, action: #selector(onTapBackup)) 39 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "close".localized, style: .plain, target: self, action: #selector(onTapClose)) 40 | 41 | view.addSubview(tableView) 42 | view.addSubview(newChannelButtonWrapper) 43 | newChannelButtonWrapper.addSubview(newChannelButton) 44 | 45 | tableView.snp.makeConstraints { maker in 46 | maker.edges.equalToSuperview() 47 | } 48 | 49 | newChannelButtonWrapper.snp.makeConstraints { maker in 50 | maker.leading.trailing.equalToSuperview() 51 | maker.bottom.equalToSuperview() 52 | } 53 | newChannelButton.snp.makeConstraints { maker in 54 | maker.top.equalTo(newChannelButtonWrapper.snp.top).offset(CGFloat.margin4x) 55 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 56 | maker.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(CGFloat.margin8x) 57 | maker.height.equalTo(CGFloat.heightButton) 58 | } 59 | 60 | tableView.backgroundColor = .clear 61 | tableView.dataSource = self 62 | tableView.delegate = self 63 | tableView.separatorStyle = .none 64 | tableView.backgroundColor = .clear 65 | tableView.tableFooterView = UIView(frame: .zero) 66 | tableView.estimatedRowHeight = 0 67 | tableView.delaysContentTouches = false 68 | 69 | tableView.registerCell(forClass: ChannelCell.self) 70 | 71 | newChannelButton.setTitle("channels.new_channel".localized, for: .normal) 72 | newChannelButton.addTarget(self, action: #selector(onNewChannel), for: .touchUpInside) 73 | 74 | delegate.onLoad() 75 | } 76 | 77 | @objc private func onTapBackup() { 78 | print("onTapBackup") 79 | } 80 | 81 | @objc private func onTapClose() { 82 | delegate.onClose() 83 | } 84 | 85 | @objc private func onNewChannel() { 86 | delegate.onNewChannel() 87 | } 88 | 89 | // func onSelectOpen() { 90 | // delegate.onSelectOpen() 91 | // } 92 | // 93 | // func onSelectClosed() { 94 | // delegate.onSelectClosed() 95 | // } 96 | 97 | } 98 | 99 | extension ChannelsViewController: IChannelsView { 100 | 101 | func show(viewItems: [ChannelViewItem]) { 102 | self.viewItems = viewItems 103 | tableView.reloadData() 104 | } 105 | 106 | } 107 | 108 | extension ChannelsViewController: UITableViewDataSource, UITableViewDelegate { 109 | 110 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 111 | viewItems.count 112 | } 113 | 114 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 115 | tableView.dequeueReusableCell(withIdentifier: String(describing: ChannelCell.self), for: indexPath) 116 | } 117 | 118 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 119 | if let cell = cell as? ChannelCell { 120 | cell.bind(viewItem: viewItems[indexPath.row]) 121 | } 122 | } 123 | 124 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 125 | 100 126 | } 127 | 128 | // public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 129 | // channelsHeaderView 130 | // } 131 | // 132 | // public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 133 | // ChannelsHeaderView.headerHeight 134 | // } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Launch/LaunchInteractor.swift: -------------------------------------------------------------------------------- 1 | import StorageKit 2 | import PinKit 3 | 4 | class LaunchInteractor { 5 | private let keychainKit: IKeychainKit 6 | private let pinKit: IPinKit 7 | private let walletManager: WalletManager 8 | 9 | init(keychainKit: IKeychainKit, pinKit: IPinKit, walletManager: WalletManager) { 10 | self.keychainKit = keychainKit 11 | self.pinKit = pinKit 12 | self.walletManager = walletManager 13 | } 14 | 15 | } 16 | 17 | extension LaunchInteractor: ILaunchInteractor { 18 | 19 | var passcodeLocked: Bool { 20 | keychainKit.locked 21 | } 22 | 23 | var isPinSet: Bool { 24 | pinKit.isPinSet 25 | } 26 | 27 | var hasStoredWallet: Bool { 28 | walletManager.hasStoredWallet 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Launch/LaunchModule.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | protocol ILaunchInteractor { 4 | var passcodeLocked: Bool { get } 5 | var isPinSet: Bool { get } 6 | var hasStoredWallet: Bool { get } 7 | } 8 | 9 | protocol ILaunchPresenter { 10 | var launchMode: LaunchMode { get } 11 | } 12 | 13 | enum LaunchMode { 14 | case noPasscode 15 | case welcome 16 | case unlock 17 | case main 18 | } 19 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Launch/LaunchPresenter.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | class LaunchPresenter { 4 | private let interactor: ILaunchInteractor 5 | 6 | init(interactor: ILaunchInteractor) { 7 | self.interactor = interactor 8 | } 9 | 10 | } 11 | 12 | extension LaunchPresenter: ILaunchPresenter { 13 | 14 | var launchMode: LaunchMode { 15 | if interactor.passcodeLocked { 16 | return .noPasscode 17 | } else if interactor.isPinSet { 18 | return .unlock 19 | } else if interactor.hasStoredWallet { 20 | return .main 21 | } else { 22 | return .welcome 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Launch/LaunchRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import StorageKit 3 | 4 | class LaunchRouter { 5 | 6 | static func module() -> UIViewController { 7 | let interactor: ILaunchInteractor = LaunchInteractor(keychainKit: App.shared.keychainKit, pinKit: App.shared.pinKit, walletManager: App.shared.walletManager) 8 | let presenter: ILaunchPresenter = LaunchPresenter(interactor: interactor) 9 | 10 | switch presenter.launchMode { 11 | case .noPasscode: return NoPasscodeViewController() 12 | case .welcome: return WelcomeScreenRouter.module() 13 | case .unlock: return LockScreenRouter.module(pinKit: App.shared.pinKit, appStart: true) 14 | case .main: return MainRouter.module() 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /LightningWallet/Modules/LockScreen/LockScreenModule.swift: -------------------------------------------------------------------------------- 1 | protocol ILockScreenRouter { 2 | func dismiss() 3 | } 4 | -------------------------------------------------------------------------------- /LightningWallet/Modules/LockScreen/LockScreenPresenter.swift: -------------------------------------------------------------------------------- 1 | import PinKit 2 | 3 | class LockScreenPresenter { 4 | private let router: ILockScreenRouter 5 | 6 | init(router: ILockScreenRouter) { 7 | self.router = router 8 | } 9 | 10 | } 11 | 12 | extension LockScreenPresenter: IUnlockDelegate { 13 | 14 | func onUnlock() { 15 | router.dismiss() 16 | } 17 | 18 | func onCancelUnlock() { 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /LightningWallet/Modules/LockScreen/LockScreenRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PinKit 3 | 4 | class LockScreenRouter { 5 | weak var viewController: UIViewController? 6 | 7 | private let appStart: Bool 8 | 9 | init(appStart: Bool) { 10 | self.appStart = appStart 11 | } 12 | 13 | } 14 | 15 | 16 | extension LockScreenRouter: ILockScreenRouter { 17 | 18 | func dismiss() { 19 | if appStart { 20 | UIApplication.shared.keyWindow?.set(newRootController: MainRouter.module()) 21 | } else { 22 | viewController?.dismiss(animated: false) 23 | } 24 | } 25 | 26 | } 27 | 28 | extension LockScreenRouter { 29 | 30 | static func module(pinKit: IPinKit, appStart: Bool) -> UIViewController { 31 | let router = LockScreenRouter(appStart: appStart) 32 | let presenter = LockScreenPresenter(router: router) 33 | 34 | let unlockController = pinKit.unlockPinModule(delegate: presenter, enableBiometry: true, presentationStyle: .simple, cancellable: false) 35 | 36 | router.viewController = unlockController 37 | 38 | unlockController.modalTransitionStyle = .crossDissolve 39 | 40 | return unlockController 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Main/MainInteractor.swift: -------------------------------------------------------------------------------- 1 | import CurrencyKit 2 | import LightningKit 3 | import RxSwift 4 | 5 | class MainInteractor { 6 | weak var delegate: IMainInteractorDelegate? 7 | 8 | private let lightningKit: LightningKit.Kit 9 | private let currencyKit: ICurrencyKit 10 | 11 | private let disposeBag = DisposeBag() 12 | 13 | init(lightningKit: LightningKit.Kit, currencyKit: ICurrencyKit) { 14 | self.lightningKit = lightningKit 15 | self.currencyKit = currencyKit 16 | } 17 | 18 | } 19 | 20 | extension MainInteractor: IMainInteractor { 21 | 22 | var currency: Currency { 23 | currencyKit.baseCurrency 24 | } 25 | 26 | func subscribeToStatus() { 27 | lightningKit.statusObservable 28 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 29 | .observeOn(MainScheduler.instance) 30 | .subscribe(onNext: { [weak self] status in 31 | self?.delegate?.didUpdate(status: status) 32 | }) 33 | .disposed(by: disposeBag) 34 | } 35 | 36 | func fetchWalletBalance() { 37 | lightningKit.walletBalanceSingle 38 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 39 | .observeOn(MainScheduler.instance) 40 | .subscribe(onSuccess: { [weak self] balance in 41 | self?.delegate?.didUpdate(walletBalance: Int(balance.totalBalance)) 42 | }) 43 | .disposed(by: disposeBag) 44 | } 45 | 46 | func fetchChannelBalance() { 47 | lightningKit.channelBalanceSingle 48 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 49 | .observeOn(MainScheduler.instance) 50 | .subscribe(onSuccess: { [weak self] balance in 51 | self?.delegate?.didUpdate(channelBalance: Int(balance.balance)) 52 | }) 53 | .disposed(by: disposeBag) 54 | } 55 | 56 | func subscribeToWalletBalance() { 57 | lightningKit.walletBalanceObservable 58 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 59 | .observeOn(MainScheduler.instance) 60 | .subscribe(onNext: { [weak self] balance in 61 | self?.delegate?.didUpdate(walletBalance: Int(balance.totalBalance)) 62 | }) 63 | .disposed(by: disposeBag) 64 | } 65 | 66 | func subscribeToChannelBalance() { 67 | lightningKit.channelBalanceObservable 68 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 69 | .observeOn(MainScheduler.instance) 70 | .subscribe(onNext: { [weak self] balance in 71 | self?.delegate?.didUpdate(channelBalance: Int(balance.balance)) 72 | }) 73 | .disposed(by: disposeBag) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Main/MainModule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CurrencyKit 3 | import LightningKit 4 | 5 | protocol IMainRouter { 6 | func openSettings() 7 | func openTransactions() 8 | func openChannels() 9 | func showUnlock() 10 | } 11 | 12 | protocol IMainView: class { 13 | func showConnectingStatus() 14 | func showSyncingStatus() 15 | func showUnlockingStatus() 16 | func showLockedStatus() 17 | func showErrorStatus(error: Error) 18 | func hideStatus() 19 | 20 | func setUnlockButton(visible: Bool) 21 | 22 | func show(totalBalance: Int) 23 | func hideTotalBalance() 24 | 25 | func setLightningButtons(enabled: Bool) 26 | } 27 | 28 | protocol IMainViewDelegate { 29 | func onLoad() 30 | func onTapUnlock() 31 | func onTapDeposit() 32 | func onTapSend() 33 | func onTapChannels() 34 | func onTapSettings() 35 | func onTapTransactions() 36 | } 37 | 38 | protocol IMainInteractor { 39 | var currency: Currency { get } 40 | 41 | func subscribeToStatus() 42 | func fetchWalletBalance() 43 | func fetchChannelBalance() 44 | func subscribeToWalletBalance() 45 | func subscribeToChannelBalance() 46 | } 47 | 48 | protocol IMainInteractorDelegate: AnyObject { 49 | func didUpdate(status: NodeStatus) 50 | func didUpdate(walletBalance: Int) 51 | func didUpdate(channelBalance: Int) 52 | } 53 | 54 | enum MainState { 55 | case sync 56 | case done 57 | } 58 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Main/MainPresenter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CurrencyKit 3 | import LightningKit 4 | 5 | class MainPresenter { 6 | weak var view: IMainView? 7 | 8 | private let interactor: IMainInteractor 9 | private let router: IMainRouter 10 | 11 | private let viewFactory: IValueFormatterFactory 12 | 13 | private var walletBalance: Int? 14 | private var channelBalance: Int? 15 | private var status: NodeStatus = .connecting 16 | 17 | init(interactor: IMainInteractor, router: IMainRouter, viewFactory: IValueFormatterFactory) { 18 | self.viewFactory = viewFactory 19 | self.interactor = interactor 20 | self.router = router 21 | } 22 | 23 | private func syncBalanceView() { 24 | if (status == .syncing || status == .running), let walletBalance = walletBalance, let channelBalance = channelBalance { 25 | let totalBalance = walletBalance + channelBalance 26 | view?.show(totalBalance: totalBalance) 27 | } else { 28 | view?.hideTotalBalance() 29 | } 30 | } 31 | 32 | private func syncUnlockButton() { 33 | view?.setUnlockButton(visible: status == .locked) 34 | } 35 | 36 | private func syncLightningButtons() { 37 | view?.setLightningButtons(enabled: status == .syncing || status == .running) 38 | } 39 | 40 | private func syncControls() { 41 | switch status { 42 | case .connecting: 43 | view?.showConnectingStatus() 44 | case .syncing: 45 | view?.showSyncingStatus() 46 | case .error(let error): 47 | view?.showErrorStatus(error: error) 48 | case .running: 49 | view?.hideStatus() 50 | case .unlocking: 51 | view?.showUnlockingStatus() 52 | case .locked: 53 | view?.showLockedStatus() 54 | } 55 | 56 | syncBalanceView() 57 | syncLightningButtons() 58 | syncUnlockButton() 59 | } 60 | 61 | } 62 | 63 | extension MainPresenter: IMainViewDelegate { 64 | 65 | func onLoad() { 66 | syncControls() 67 | 68 | interactor.subscribeToStatus() 69 | interactor.subscribeToWalletBalance() 70 | interactor.subscribeToChannelBalance() 71 | } 72 | 73 | func onTapUnlock() { 74 | router.showUnlock() 75 | } 76 | 77 | func onTapDeposit() { 78 | print("onDeposit") 79 | } 80 | 81 | func onTapSend() { 82 | print("onSend") 83 | } 84 | 85 | func onTapChannels() { 86 | router.openChannels() 87 | } 88 | 89 | func onTapSettings() { 90 | router.openSettings() 91 | } 92 | 93 | func onTapTransactions() { 94 | router.openTransactions() 95 | } 96 | 97 | } 98 | 99 | extension MainPresenter: IMainInteractorDelegate { 100 | 101 | func didUpdate(status: NodeStatus) { 102 | self.status = status 103 | syncControls() 104 | 105 | if status == .running || status == .syncing { 106 | interactor.fetchWalletBalance() 107 | interactor.fetchChannelBalance() 108 | } 109 | } 110 | 111 | func didUpdate(walletBalance: Int) { 112 | self.walletBalance = walletBalance 113 | syncBalanceView() 114 | } 115 | 116 | func didUpdate(channelBalance: Int) { 117 | self.channelBalance = channelBalance 118 | syncBalanceView() 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Main/MainRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import LightningKit 4 | 5 | class MainRouter { 6 | weak var viewController: UIViewController? 7 | } 8 | 9 | extension MainRouter: IMainRouter { 10 | 11 | func openSettings() { 12 | viewController?.navigationController?.pushViewController(MainSettingsRouter.module(), animated: true) 13 | } 14 | 15 | func openTransactions() { 16 | viewController?.present(ThemeNavigationController(rootViewController: TransactionsRouter.module()), animated: true) 17 | } 18 | 19 | func openChannels() { 20 | viewController?.present(ThemeNavigationController(rootViewController: ChannelsRouter.module()), animated: true) 21 | } 22 | 23 | func showUnlock() { 24 | viewController?.present(UnlockRemoteWalletRouter.module(), animated: true) 25 | } 26 | 27 | } 28 | 29 | extension MainRouter { 30 | 31 | static func module() -> UIViewController { 32 | guard let lightningKit = App.shared.lightningKitManager.currentKit else { 33 | // TODO: show empty view controller with message 34 | fatalError() 35 | } 36 | 37 | let router = MainRouter() 38 | let interactor = MainInteractor(lightningKit: lightningKit, currencyKit: App.shared.currencyKit) 39 | let presenter = MainPresenter(interactor: interactor, router: router, viewFactory: ValueFormatterFactory()) 40 | let viewController = MainViewController(delegate: presenter) 41 | 42 | presenter.view = viewController 43 | interactor.delegate = presenter 44 | router.viewController = viewController 45 | 46 | App.shared.pinKitDelegate.viewController = viewController 47 | 48 | return ThemeNavigationController(rootViewController: viewController) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeConnect/NodeConnectInteractor.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LightningKit 3 | import RxSwift 4 | 5 | class NodeConnectInteractor { 6 | weak var delegate: INodeConnectInteractorDelegate? 7 | 8 | private let disposeBag = DisposeBag() 9 | private let walletManager: WalletManager 10 | 11 | init(walletManager: WalletManager) { 12 | self.walletManager = walletManager 13 | } 14 | 15 | } 16 | 17 | extension NodeConnectInteractor: INodeConnectInteractor { 18 | 19 | func validate(credentials: RpcCredentials) { 20 | LightningKit.Kit.validateRemoteConnection(rpcCredentials: credentials) 21 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 22 | .observeOn(MainScheduler.instance) 23 | .subscribe(onSuccess: { [weak self] in 24 | self?.delegate?.didValidateCredentials() 25 | }, onError: { [weak self] error in 26 | self?.delegate?.didFailToValidateCredentials(error: error) 27 | }) 28 | .disposed(by: disposeBag) 29 | } 30 | 31 | func saveWallet(credentials: RpcCredentials) { 32 | walletManager.saveAndBootstrapRemoteWallet(credentials: credentials) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeConnect/NodeConnectModule.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | protocol INodeConnectRouter { 4 | func showMain() 5 | } 6 | 7 | protocol INodeConnectView: class { 8 | func show(address: String) 9 | func showConnecting() 10 | func hideConnecting() 11 | func showError(error: Error) 12 | } 13 | 14 | protocol INodeConnectViewDelegate { 15 | func onLoad() 16 | func onTapConnect() 17 | } 18 | 19 | protocol INodeConnectInteractor { 20 | func validate(credentials: RpcCredentials) 21 | func saveWallet(credentials: RpcCredentials) 22 | } 23 | 24 | protocol INodeConnectInteractorDelegate: AnyObject { 25 | func didValidateCredentials() 26 | func didFailToValidateCredentials(error: Error) 27 | } 28 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeConnect/NodeConnectPresenter.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | import RxSwift 3 | 4 | class NodeConnectPresenter { 5 | weak var view: INodeConnectView? 6 | 7 | private let interactor: INodeConnectInteractor 8 | private let router: INodeConnectRouter 9 | 10 | private let credentials: RpcCredentials 11 | 12 | init(interactor: INodeConnectInteractor, router: INodeConnectRouter, credentials: RpcCredentials) { 13 | self.interactor = interactor 14 | self.router = router 15 | self.credentials = credentials 16 | } 17 | 18 | } 19 | 20 | extension NodeConnectPresenter: INodeConnectViewDelegate { 21 | 22 | func onLoad() { 23 | view?.show(address: "\(credentials.host):\(credentials.port)") 24 | } 25 | 26 | func onTapConnect() { 27 | view?.showConnecting() 28 | interactor.validate(credentials: credentials) 29 | } 30 | 31 | } 32 | 33 | extension NodeConnectPresenter: INodeConnectInteractorDelegate { 34 | 35 | func didValidateCredentials() { 36 | view?.hideConnecting() 37 | 38 | interactor.saveWallet(credentials: credentials) 39 | router.showMain() 40 | } 41 | 42 | func didFailToValidateCredentials(error: Error) { 43 | view?.showError(error: error) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeConnect/NodeConnectRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import LightningKit 4 | 5 | class NodeConnectRouter { 6 | weak var viewController: UIViewController? 7 | } 8 | 9 | extension NodeConnectRouter: INodeConnectRouter { 10 | 11 | func showMain() { 12 | UIApplication.shared.keyWindow?.set(newRootController: MainRouter.module()) 13 | } 14 | 15 | } 16 | 17 | extension NodeConnectRouter { 18 | 19 | static func module(credentials: RpcCredentials) -> UIViewController { 20 | let router = NodeConnectRouter() 21 | let interactor = NodeConnectInteractor(walletManager: App.shared.walletManager) 22 | let presenter = NodeConnectPresenter(interactor: interactor, router: router, credentials: credentials) 23 | let viewController = NodeConnectViewController(delegate: presenter) 24 | 25 | presenter.view = viewController 26 | interactor.delegate = presenter 27 | router.viewController = viewController 28 | 29 | return viewController 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeConnect/NodeConnectViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import SnapKit 4 | 5 | class NodeConnectViewController: ThemeViewController { 6 | private let delegate: INodeConnectViewDelegate 7 | 8 | private let addressLabel = UILabel() 9 | private let connectButton = UIButton.appGreen 10 | 11 | init(delegate: INodeConnectViewDelegate) { 12 | self.delegate = delegate 13 | 14 | super.init() 15 | } 16 | 17 | required init?(coder aDecoder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | navigationItem.largeTitleDisplayMode = .always 25 | title = "node_connect.title".localized 26 | TransparentNavigationBar.set(to: navigationController?.navigationBar) 27 | 28 | view.addSubview(addressLabel) 29 | addressLabel.snp.makeConstraints { maker in 30 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 31 | maker.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(CGFloat.margin4x) 32 | } 33 | addressLabel.textColor = .themeLeah 34 | addressLabel.font = .title3 35 | 36 | view.addSubview(connectButton) 37 | connectButton.snp.makeConstraints { maker in 38 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 39 | maker.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(CGFloat.margin8x) 40 | maker.height.equalTo(CGFloat.heightButton) 41 | } 42 | 43 | connectButton.setTitle("Connect", for: .normal) 44 | connectButton.addTarget(self, action: #selector(onConnect), for: .touchUpInside) 45 | 46 | delegate.onLoad() 47 | } 48 | 49 | @objc func onConnect() { 50 | delegate.onTapConnect() 51 | } 52 | 53 | } 54 | 55 | extension NodeConnectViewController: INodeConnectView { 56 | 57 | func show(address: String) { 58 | addressLabel.text = address 59 | } 60 | 61 | func showConnecting() { 62 | HudHelper.instance.showSpinner(title: "node_connect.connecting".localized, userInteractionEnabled: false) 63 | } 64 | 65 | func hideConnecting() { 66 | HudHelper.instance.hide() 67 | } 68 | 69 | func showError(error: Error) { 70 | HudHelper.instance.showError(subtitle: error.localizedDescription) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeCredentials/NodeCredentialsInteractor.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LightningKit 3 | 4 | class NodeCredentialsInteractor { 5 | weak var delegate: INodeCredentialsInteractorDelegate? 6 | 7 | init() { 8 | } 9 | 10 | } 11 | 12 | extension NodeCredentialsInteractor: INodeCredentialsInteractor { 13 | 14 | var pasteboard: String? { 15 | UIPasteboard.general.string 16 | } 17 | 18 | func credentials(urlString: String) -> RpcCredentials? { 19 | RpcCredentials(lndConnectUrlString: urlString) 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeCredentials/NodeCredentialsModule.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | protocol INodeCredentialsRouter { 4 | func openConnectNode(credentials: RpcCredentials) 5 | } 6 | 7 | protocol INodeCredentialsView: class { 8 | func showDescription() 9 | func showEmptyPasteboard() 10 | func showError() 11 | func notifyScan() 12 | func stopScan() 13 | } 14 | 15 | protocol INodeCredentialsViewDelegate { 16 | func onLoad() 17 | 18 | func onPaste() 19 | func onScan(string: String) 20 | } 21 | 22 | protocol INodeCredentialsInteractor { 23 | var pasteboard: String? { get } 24 | func credentials(urlString: String) -> RpcCredentials? 25 | } 26 | 27 | protocol INodeCredentialsInteractorDelegate: class { 28 | } 29 | 30 | protocol INotificationTimer { 31 | func start(interval: TimeInterval) 32 | } 33 | 34 | protocol INotificationTimerDelegate: class { 35 | func onFire() 36 | } 37 | 38 | enum NodeCredentialsParsing: Error { 39 | case emptyData 40 | case wrongData 41 | } -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeCredentials/NodeCredentialsPresenter.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | 3 | class NodeCredentialsPresenter { 4 | private static let validationInterval: TimeInterval = 3 5 | 6 | weak var view: INodeCredentialsView? 7 | 8 | private let interactor: INodeCredentialsInteractor 9 | private let router: INodeCredentialsRouter 10 | private let notificationTimer: INotificationTimer 11 | 12 | private var lastScannedCode: String? 13 | 14 | init(interactor: INodeCredentialsInteractor, router: INodeCredentialsRouter, notificationTimer: INotificationTimer) { 15 | self.interactor = interactor 16 | self.router = router 17 | self.notificationTimer = notificationTimer 18 | } 19 | 20 | private func process(string: String, notify: Bool) { 21 | guard lastScannedCode != string else { 22 | return 23 | } 24 | if notify { 25 | view?.notifyScan() 26 | } 27 | 28 | if let credentials = interactor.credentials(urlString: string) { 29 | view?.stopScan() 30 | view?.showDescription() 31 | 32 | router.openConnectNode(credentials: credentials) 33 | } else { 34 | view?.showError() 35 | 36 | lastScannedCode = string 37 | notificationTimer.start(interval: NodeCredentialsPresenter.validationInterval) 38 | } 39 | } 40 | } 41 | 42 | extension NodeCredentialsPresenter: INodeCredentialsViewDelegate { 43 | 44 | func onLoad() { 45 | view?.showDescription() 46 | } 47 | 48 | func onPaste() { 49 | if let string = interactor.pasteboard { 50 | process(string: string, notify: false) 51 | } else { 52 | notificationTimer.start(interval: NodeCredentialsPresenter.validationInterval) 53 | view?.showEmptyPasteboard() 54 | } 55 | } 56 | 57 | func onScan(string: String) { 58 | process(string: string, notify: true) 59 | } 60 | 61 | } 62 | 63 | extension NodeCredentialsPresenter: INodeCredentialsInteractorDelegate { 64 | } 65 | 66 | extension NodeCredentialsPresenter: INotificationTimerDelegate { 67 | 68 | func onFire() { 69 | view?.showDescription() 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeCredentials/NodeCredentialsRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import LightningKit 4 | 5 | class NodeCredentialsRouter { 6 | weak var viewController: UIViewController? 7 | } 8 | 9 | extension NodeCredentialsRouter: INodeCredentialsRouter { 10 | 11 | func openConnectNode(credentials: RpcCredentials) { 12 | viewController?.navigationController?.pushViewController(NodeConnectRouter.module(credentials: credentials), animated: true) 13 | } 14 | 15 | } 16 | 17 | extension NodeCredentialsRouter { 18 | 19 | static func module() -> UIViewController { 20 | let router = NodeCredentialsRouter() 21 | let interactor = NodeCredentialsInteractor() 22 | let notificationTimer = NotificationTimer() 23 | let presenter = NodeCredentialsPresenter(interactor: interactor, router: router, notificationTimer: notificationTimer) 24 | let viewController = NodeCredentialsViewController(delegate: presenter) 25 | 26 | presenter.view = viewController 27 | notificationTimer.delegate = presenter 28 | router.viewController = viewController 29 | 30 | return viewController 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeCredentials/NodeCredentialsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import SnapKit 4 | import HUD 5 | 6 | class NodeCredentialsViewController: ThemeViewController { 7 | private let delegate: INodeCredentialsViewDelegate 8 | 9 | private let scanView = ScanQrView() 10 | 11 | private let descriptionLabel = UILabel() 12 | private let errorLabel = UILabel() 13 | private let pasteButton = UIButton.appGray 14 | 15 | init(delegate: INodeCredentialsViewDelegate) { 16 | self.delegate = delegate 17 | 18 | super.init() 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | navigationItem.largeTitleDisplayMode = .never 29 | TransparentNavigationBar.set(to: navigationController?.navigationBar) 30 | 31 | view.addSubview(scanView) 32 | scanView.snp.makeConstraints { maker in 33 | maker.edges.equalToSuperview() 34 | } 35 | scanView.delegate = self 36 | 37 | view.addSubview(descriptionLabel) 38 | descriptionLabel.snp.makeConstraints { maker in 39 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 40 | maker.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(CGFloat.margin3x) 41 | } 42 | descriptionLabel.numberOfLines = 0 43 | descriptionLabel.textAlignment = .center 44 | descriptionLabel.textColor = .themeLeah 45 | descriptionLabel.font = .subhead2 46 | descriptionLabel.text = "node_credentials.description".localized 47 | 48 | view.addSubview(errorLabel) 49 | errorLabel.snp.makeConstraints { maker in 50 | maker.edges.equalTo(descriptionLabel) 51 | } 52 | errorLabel.numberOfLines = 0 53 | errorLabel.textAlignment = .center 54 | errorLabel.textColor = .themeLucian 55 | errorLabel.font = .subhead2 56 | 57 | view.addSubview(pasteButton) 58 | pasteButton.snp.makeConstraints { maker in 59 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 60 | maker.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(CGFloat.margin8x) 61 | maker.height.equalTo(CGFloat.heightButton) 62 | } 63 | 64 | pasteButton.setTitle("Paste", for: .normal) 65 | pasteButton.addTarget(self, action: #selector(onPaste), for: .touchUpInside) 66 | 67 | delegate.onLoad() 68 | } 69 | 70 | override func viewDidAppear(_ animated: Bool) { 71 | super.viewDidAppear(animated) 72 | 73 | scanView.start() 74 | } 75 | 76 | override func viewDidDisappear(_ animated: Bool) { 77 | super.viewDidDisappear(animated) 78 | 79 | scanView.stop() 80 | } 81 | 82 | @objc func onPaste() { 83 | delegate.onPaste() 84 | } 85 | 86 | } 87 | 88 | extension NodeCredentialsViewController: IScanQrCodeDelegate { 89 | 90 | func didScan(string: String) { 91 | delegate.onScan(string: string) 92 | } 93 | 94 | } 95 | 96 | extension NodeCredentialsViewController: INodeCredentialsView { 97 | 98 | func showDescription() { 99 | descriptionLabel.isHidden = false 100 | errorLabel.isHidden = true 101 | } 102 | 103 | func showEmptyPasteboard() { 104 | descriptionLabel.isHidden = true 105 | 106 | errorLabel.text = "node_credentials.empty_clipboard".localized 107 | errorLabel.isHidden = false 108 | } 109 | 110 | func showError() { 111 | descriptionLabel.isHidden = true 112 | 113 | errorLabel.text = "node_credentials.invalid_code".localized 114 | errorLabel.isHidden = false 115 | } 116 | 117 | func notifyScan() { 118 | HapticGenerator.instance.notification(.success) 119 | } 120 | 121 | func stopScan() { 122 | scanView.stop() 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /LightningWallet/Modules/NodeCredentials/NotificationTimer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class NotificationTimer { 4 | private var timer: Timer? 5 | 6 | weak var delegate: INotificationTimerDelegate? 7 | 8 | deinit { 9 | timer?.invalidate() 10 | } 11 | 12 | @objc func fire() { 13 | delegate?.onFire() 14 | } 15 | 16 | } 17 | 18 | extension NotificationTimer: INotificationTimer { 19 | 20 | func start(interval: TimeInterval) { 21 | timer?.invalidate() 22 | 23 | timer = Timer(fireAt: Date(timeIntervalSinceNow: interval), interval: 0, target: self, selector: #selector(fire), userInfo: nil, repeats: false) 24 | RunLoop.main.add(timer!, forMode: .common) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Main/MainSettingsFooter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import UIExtensions 3 | import SnapKit 4 | 5 | class MainSettingsFooter: UITableViewHeaderFooterView { 6 | private let versionLabel = UILabel() 7 | private let logoButton = RespondView() 8 | 9 | override init(reuseIdentifier: String?) { 10 | super.init(reuseIdentifier: reuseIdentifier) 11 | 12 | backgroundView = UIView() 13 | backgroundView?.backgroundColor = .clear 14 | 15 | contentView.addSubview(versionLabel) 16 | versionLabel.snp.makeConstraints { maker in 17 | maker.top.equalToSuperview().offset(CGFloat.margin8x) 18 | maker.centerX.equalToSuperview() 19 | } 20 | versionLabel.textColor = .themeGray 21 | versionLabel.font = .caption 22 | 23 | let separatorView = UIView() 24 | separatorView.backgroundColor = .themeGray 25 | contentView.addSubview(separatorView) 26 | separatorView.snp.makeConstraints { maker in 27 | maker.leading.trailing.equalTo(versionLabel) 28 | maker.top.equalTo(versionLabel.snp.bottom).offset(CGFloat.margin1x) 29 | maker.height.equalTo(1 / UIScreen.main.scale) 30 | } 31 | 32 | let titleLabel = UILabel() 33 | contentView.addSubview(titleLabel) 34 | titleLabel.snp.makeConstraints { maker in 35 | maker.top.equalTo(separatorView.snp.bottom).offset(CGFloat.margin1x) 36 | maker.centerX.equalToSuperview() 37 | } 38 | titleLabel.textColor = .themeGray 39 | titleLabel.font = .caption 40 | titleLabel.text = "settings.info_subtitle".localized 41 | 42 | let imageView: TintImageView = TintImageView(image: UIImage(named: "Logo Image"), tintColor: .themeGray, selectedTintColor: .themeSteelLight) 43 | logoButton.addSubview(imageView) 44 | imageView.snp.makeConstraints { maker in 45 | maker.edges.equalToSuperview() 46 | } 47 | logoButton.delegate = imageView 48 | 49 | contentView.addSubview(logoButton) 50 | logoButton.snp.makeConstraints { maker in 51 | maker.top.equalTo(titleLabel.snp.bottom).offset(CGFloat.margin8x) 52 | maker.centerX.equalToSuperview() 53 | } 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | func bind(appVersion: String?, handleTouch: (() -> ())? = nil) { 61 | var versionText = "settings.info_title".localized 62 | 63 | if let appVersion = appVersion { 64 | versionText += " \(appVersion)" 65 | } 66 | 67 | versionLabel.text = versionText 68 | logoButton.handleTouch = handleTouch 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Main/MainSettingsInteractor.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import LanguageKit 3 | import ThemeKit 4 | import CurrencyKit 5 | 6 | class MainSettingsInteractor { 7 | private let disposeBag = DisposeBag() 8 | 9 | weak var delegate: IMainSettingsInteractorDelegate? 10 | 11 | private let walletManager: WalletManager 12 | private let themeManager: ThemeManager 13 | private let currencyKit: ICurrencyKit 14 | 15 | init(walletManager: WalletManager, themeManager: ThemeManager, currencyKit: ICurrencyKit) { 16 | self.walletManager = walletManager 17 | self.themeManager = themeManager 18 | self.currencyKit = currencyKit 19 | 20 | currencyKit.baseCurrencyUpdatedObservable 21 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) 22 | .observeOn(MainScheduler.instance) 23 | .subscribe(onNext: { [weak self] _ in 24 | self?.delegate?.didUpdateBaseCurrency() 25 | }) 26 | .disposed(by: disposeBag) 27 | } 28 | 29 | } 30 | 31 | extension MainSettingsInteractor: IMainSettingsInteractor { 32 | 33 | var companyWebPageLink: String { 34 | "companyWebPageLink" 35 | } 36 | 37 | var appWebPageLink: String { 38 | "appWebPageLink" 39 | } 40 | 41 | var currentLanguageDisplayName: String? { 42 | LanguageManager.shared.currentLanguageDisplayName 43 | } 44 | 45 | var baseCurrency: Currency { 46 | currencyKit.baseCurrency 47 | } 48 | 49 | var lightMode: Bool { 50 | get { 51 | themeManager.lightMode 52 | } 53 | set { 54 | themeManager.lightMode = newValue 55 | } 56 | } 57 | 58 | var appVersion: String { 59 | "0.1" 60 | } 61 | 62 | func removeStoredWallet() { 63 | walletManager.removeStoredWallet() 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Main/MainSettingsModule.swift: -------------------------------------------------------------------------------- 1 | import CurrencyKit 2 | 3 | protocol IMainSettingsView: class { 4 | func refresh() 5 | 6 | func set(currentBaseCurrency: String) 7 | func set(currentLanguage: String?) 8 | func set(lightMode: Bool) 9 | func set(appVersion: String) 10 | } 11 | 12 | protocol IMainSettingsViewDelegate { 13 | func viewDidLoad() 14 | func didTapSecurity() 15 | func didTapBaseCurrency() 16 | func didTapLanguage() 17 | func didSwitch(lightMode: Bool) 18 | func didTapAbout() 19 | func didTapTellFriends() 20 | func didTapContact() 21 | func didTapCompanyLink() 22 | func didTapLogOut() 23 | } 24 | 25 | protocol IMainSettingsInteractor: AnyObject { 26 | var companyWebPageLink: String { get } 27 | var appWebPageLink: String { get } 28 | var currentLanguageDisplayName: String? { get } 29 | var baseCurrency: Currency { get } 30 | var lightMode: Bool { get set } 31 | var appVersion: String { get } 32 | func removeStoredWallet() 33 | } 34 | 35 | protocol IMainSettingsInteractorDelegate: class { 36 | func didUpdateBaseCurrency() 37 | } 38 | 39 | protocol IMainSettingsRouter { 40 | func showSecuritySettings() 41 | func showBaseCurrencySettings() 42 | func showLanguageSettings() 43 | func showAbout() 44 | func showShare(appWebPageLink: String) 45 | func showContact() 46 | func open(link: String) 47 | func reloadAppInterface() 48 | func showWelcome() 49 | } 50 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Main/MainSettingsPresenter.swift: -------------------------------------------------------------------------------- 1 | class MainSettingsPresenter { 2 | weak var view: IMainSettingsView? 3 | 4 | private let router: IMainSettingsRouter 5 | private let interactor: IMainSettingsInteractor 6 | 7 | init(router: IMainSettingsRouter, interactor: IMainSettingsInteractor) { 8 | self.router = router 9 | self.interactor = interactor 10 | } 11 | 12 | private func syncCurrentBaseCurrency() { 13 | view?.set(currentBaseCurrency: interactor.baseCurrency.code) 14 | } 15 | 16 | } 17 | 18 | extension MainSettingsPresenter: IMainSettingsViewDelegate { 19 | 20 | func viewDidLoad() { 21 | syncCurrentBaseCurrency() 22 | view?.set(currentLanguage: interactor.currentLanguageDisplayName) 23 | view?.set(lightMode: interactor.lightMode) 24 | view?.set(appVersion: interactor.appVersion) 25 | } 26 | 27 | func didTapSecurity() { 28 | router.showSecuritySettings() 29 | } 30 | 31 | func didTapBaseCurrency() { 32 | router.showBaseCurrencySettings() 33 | } 34 | 35 | func didTapLanguage() { 36 | router.showLanguageSettings() 37 | } 38 | 39 | func didSwitch(lightMode: Bool) { 40 | interactor.lightMode = lightMode 41 | router.reloadAppInterface() 42 | } 43 | 44 | func didTapAbout() { 45 | router.showAbout() 46 | } 47 | 48 | func didTapTellFriends() { 49 | router.showShare(appWebPageLink: interactor.appWebPageLink) 50 | } 51 | 52 | func didTapContact() { 53 | router.showContact() 54 | } 55 | 56 | func didTapCompanyLink() { 57 | router.open(link: interactor.companyWebPageLink) 58 | } 59 | 60 | func didTapLogOut() { 61 | interactor.removeStoredWallet() 62 | router.showWelcome() 63 | } 64 | 65 | } 66 | 67 | extension MainSettingsPresenter: IMainSettingsInteractorDelegate { 68 | 69 | func didUpdateBaseCurrency() { 70 | syncCurrentBaseCurrency() 71 | view?.refresh() 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Main/MainSettingsRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import LanguageKit 3 | import ThemeKit 4 | import CurrencyKit 5 | 6 | class MainSettingsRouter { 7 | weak var viewController: UIViewController? 8 | } 9 | 10 | extension MainSettingsRouter: IMainSettingsRouter { 11 | 12 | func showSecuritySettings() { 13 | viewController?.navigationController?.pushViewController(SecuritySettingsRouter.module(), animated: true) 14 | } 15 | 16 | func showBaseCurrencySettings() { 17 | viewController?.navigationController?.pushViewController(App.shared.currencyKit.baseCurrencySettingsModule, animated: true) 18 | } 19 | 20 | func showLanguageSettings() { 21 | let module = LanguageSettingsRouter.module { MainRouter.module() } 22 | viewController?.navigationController?.pushViewController(module, animated: true) 23 | } 24 | 25 | func showAbout() { 26 | // viewController?.navigationController?.pushViewController(AboutSettingsRouter.module(), animated: true) 27 | } 28 | 29 | func showShare(appWebPageLink: String) { 30 | let text = "settings_tell_friends.text".localized + "\n" + appWebPageLink 31 | let activityViewController = UIActivityViewController(activityItems: [text], applicationActivities: []) 32 | viewController?.present(activityViewController, animated: true, completion: nil) 33 | } 34 | 35 | func showContact() { 36 | // viewController?.navigationController?.pushViewController(ContactRouter.module(), animated: true) 37 | } 38 | 39 | func open(link: String) { 40 | if let url = URL(string: link) { 41 | UIApplication.shared.open(url) 42 | } 43 | } 44 | 45 | func reloadAppInterface() { 46 | UIApplication.shared.keyWindow?.set(newRootController: MainRouter.module()) 47 | } 48 | 49 | func showWelcome() { 50 | UIApplication.shared.keyWindow?.set(newRootController: WelcomeScreenRouter.module()) 51 | } 52 | 53 | } 54 | 55 | extension MainSettingsRouter { 56 | 57 | static func module() -> UIViewController { 58 | let router = MainSettingsRouter() 59 | let interactor = MainSettingsInteractor( 60 | walletManager: App.shared.walletManager, 61 | themeManager: ThemeManager.shared, 62 | currencyKit: App.shared.currencyKit 63 | ) 64 | let presenter = MainSettingsPresenter(router: router, interactor: interactor) 65 | let view = MainSettingsViewController(delegate: presenter) 66 | 67 | interactor.delegate = presenter 68 | presenter.view = view 69 | router.viewController = view 70 | 71 | return view 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Main/MainSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SectionsTableView 3 | import SnapKit 4 | import ThemeKit 5 | import UIExtensions 6 | 7 | class MainSettingsViewController: ThemeViewController { 8 | private let delegate: IMainSettingsViewDelegate 9 | 10 | private let tableView = SectionsTableView(style: .grouped) 11 | 12 | private var currentBaseCurrency: String? 13 | private var currentLanguage: String? 14 | private var lightMode: Bool = true 15 | private var appVersion: String? 16 | 17 | init(delegate: IMainSettingsViewDelegate) { 18 | self.delegate = delegate 19 | 20 | super.init() 21 | 22 | tabBarItem = UITabBarItem(title: "settings.tab_bar_item".localized, image: UIImage(named: "settings.tab_bar_item"), tag: 0) 23 | } 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | title = "settings.title".localized 33 | navigationItem.backBarButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil) 34 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "settings.log_out".localized, style: .plain, target: self, action: #selector(onLogOut)) 35 | 36 | tableView.registerCell(forClass: TitleCell.self) 37 | tableView.registerCell(forClass: RightLabelCell.self) 38 | tableView.registerCell(forClass: RightImageCell.self) 39 | tableView.registerCell(forClass: ToggleCell.self) 40 | tableView.registerHeaderFooter(forClass: MainSettingsFooter.self) 41 | 42 | tableView.sectionDataSource = self 43 | 44 | tableView.separatorStyle = .none 45 | tableView.backgroundColor = .clear 46 | 47 | view.addSubview(tableView) 48 | tableView.snp.makeConstraints { maker in 49 | maker.edges.equalToSuperview() 50 | } 51 | 52 | delegate.viewDidLoad() 53 | 54 | tableView.buildSections() 55 | } 56 | 57 | override func viewWillAppear(_ animated: Bool) { 58 | tableView.deselectCell(withCoordinator: transitionCoordinator, animated: animated) 59 | } 60 | 61 | @objc private func onLogOut() { 62 | delegate.didTapLogOut() 63 | } 64 | 65 | private var securityRows: [RowProtocol] { 66 | [ 67 | Row(id: "security_center", height: .heightSingleLineCell, bind: { cell, _ in 68 | cell.bind(titleIcon: UIImage(named: "Security Icon"), title: "settings.security_center".localized, rightImage: nil, rightImageTintColor: nil, showDisclosure: true) 69 | }, action: { [weak self] _ in 70 | self?.delegate.didTapSecurity() 71 | }), 72 | ] 73 | } 74 | 75 | private var appearanceRows: [RowProtocol] { 76 | [ 77 | Row(id: "base_currency", height: .heightSingleLineCell, bind: { [weak self] cell, _ in 78 | cell.bind(titleIcon: UIImage(named: "Currency Icon"), title: "settings.base_currency".localized, rightText: self?.currentBaseCurrency, showDisclosure: true) 79 | }, action: { [weak self] _ in 80 | self?.delegate.didTapBaseCurrency() 81 | }), 82 | 83 | Row(id: "language", height: .heightSingleLineCell, bind: { [weak self] cell, _ in 84 | cell.bind(titleIcon: UIImage(named: "Language Icon"), title: "settings.language".localized, rightText: self?.currentLanguage, showDisclosure: true) 85 | }, action: { [weak self] _ in 86 | self?.delegate.didTapLanguage() 87 | }), 88 | 89 | Row(id: "light_mode", height: .heightSingleLineCell, bind: { [unowned self] cell, _ in 90 | cell.bind(titleIcon: UIImage(named: "Light Mode Icon"), title: "settings.light_mode".localized, isOn: self.lightMode, last: true, onToggle: { [weak self] isOn in 91 | self?.delegate.didSwitch(lightMode: isOn) 92 | }) 93 | }) 94 | ] 95 | } 96 | 97 | private var aboutRows: [RowProtocol] { 98 | [ 99 | Row(id: "about", height: .heightSingleLineCell, bind: { cell, _ in 100 | cell.bind(titleIcon: UIImage(named: "About Icon"), title: "settings.about".localized, showDisclosure: true) 101 | }, action: { [weak self] _ in 102 | self?.delegate.didTapAbout() 103 | }), 104 | 105 | Row(id: "tell_friends", height: .heightSingleLineCell, autoDeselect: true, bind: { cell, _ in 106 | cell.bind(titleIcon: UIImage(named: "Tell Friends Icon"), title: "settings.tell_friends".localized, showDisclosure: true) 107 | }, action: { [weak self] _ in 108 | self?.delegate.didTapTellFriends() 109 | }), 110 | 111 | Row(id: "contact", height: .heightSingleLineCell, bind: { cell, _ in 112 | cell.bind(titleIcon: UIImage(named: "Contact Icon"), title: "settings.contact".localized, showDisclosure: true, last: true) 113 | }, action: { [weak self] _ in 114 | self?.delegate.didTapContact() 115 | }) 116 | ] 117 | } 118 | 119 | private var footer: ViewState { 120 | .cellType(hash: "about_footer", binder: { [weak self] view in 121 | view.bind(appVersion: self?.appVersion) { [weak self] in 122 | self?.delegate.didTapCompanyLink() 123 | } 124 | }, dynamicHeight: { _ in 125 | 194 126 | }) 127 | } 128 | 129 | } 130 | 131 | extension MainSettingsViewController: SectionsDataSource { 132 | 133 | func buildSections() -> [SectionProtocol] { 134 | [ 135 | Section(id: "security_settings", headerState: .margin(height: .margin8x), rows: securityRows), 136 | Section(id: "appearance_settings", headerState: .margin(height: .margin8x), rows: appearanceRows), 137 | Section(id: "about", headerState: .margin(height: .margin8x), footerState: footer, rows: aboutRows) 138 | ] 139 | } 140 | 141 | } 142 | 143 | extension MainSettingsViewController: IMainSettingsView { 144 | 145 | func refresh() { 146 | tableView.reload() 147 | } 148 | 149 | func set(currentBaseCurrency: String) { 150 | self.currentBaseCurrency = currentBaseCurrency 151 | } 152 | 153 | func set(currentLanguage: String?) { 154 | self.currentLanguage = currentLanguage 155 | } 156 | 157 | func set(lightMode: Bool) { 158 | self.lightMode = lightMode 159 | } 160 | 161 | func set(appVersion: String) { 162 | self.appVersion = appVersion 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Security/SecuritySettingsInteractor.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import PinKit 3 | 4 | class SecuritySettingsInteractor { 5 | private let disposeBag = DisposeBag() 6 | 7 | weak var delegate: ISecuritySettingsInteractorDelegate? 8 | 9 | private let pinKit: IPinKit 10 | 11 | init(pinKit: IPinKit) { 12 | self.pinKit = pinKit 13 | 14 | pinKit.isPinSetObservable 15 | .observeOn(MainScheduler.instance) 16 | .subscribe(onNext: { [weak self] isPinSet in 17 | self?.delegate?.didUpdate(pinSet: isPinSet) 18 | }) 19 | .disposed(by: disposeBag) 20 | 21 | pinKit.biometryTypeObservable 22 | .observeOn(MainScheduler.instance) 23 | .subscribe(onNext: { [weak self] biometryType in 24 | self?.delegate?.didUpdate(biometryType: biometryType) 25 | }) 26 | .disposed(by: disposeBag) 27 | } 28 | 29 | } 30 | 31 | extension SecuritySettingsInteractor: ISecuritySettingsInteractor { 32 | 33 | var biometryType: BiometryType { 34 | pinKit.biometryType 35 | } 36 | 37 | var pinSet: Bool { 38 | pinKit.isPinSet 39 | } 40 | 41 | var biometryEnabled: Bool { 42 | get { 43 | pinKit.biometryEnabled 44 | } 45 | set { 46 | pinKit.biometryEnabled = newValue 47 | } 48 | } 49 | 50 | func disablePin() throws { 51 | try pinKit.clear() 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Security/SecuritySettingsModule.swift: -------------------------------------------------------------------------------- 1 | import PinKit 2 | 3 | protocol ISecuritySettingsView: class { 4 | func refresh() 5 | 6 | func set(backupAlertVisible: Bool) 7 | func toggle(pinSet: Bool) 8 | func set(editPinVisible: Bool) 9 | func set(biometryVisible: Bool) 10 | func set(biometryType: BiometryType) 11 | func toggle(biometryEnabled: Bool) 12 | func show(error: Error) 13 | } 14 | 15 | protocol ISecuritySettingsViewDelegate { 16 | func viewDidLoad() 17 | func didSwitch(pinSet: Bool) 18 | func didTapEditPin() 19 | func didSwitch(biometryEnabled: Bool) 20 | } 21 | 22 | protocol ISecuritySettingsInteractor: AnyObject { 23 | var biometryType: BiometryType { get } 24 | var pinSet: Bool { get } 25 | var biometryEnabled: Bool { get set } 26 | func disablePin() throws 27 | } 28 | 29 | protocol ISecuritySettingsInteractorDelegate: class { 30 | func didUpdate(allBackedUp: Bool) 31 | func didUpdate(pinSet: Bool) 32 | func didUpdate(biometryType: BiometryType) 33 | } 34 | 35 | protocol ISecuritySettingsRouter { 36 | func showSetPin(delegate: ISetPinDelegate) 37 | func showEditPin() 38 | func showUnlock(delegate: IUnlockDelegate) 39 | } 40 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Security/SecuritySettingsPresenter.swift: -------------------------------------------------------------------------------- 1 | import PinKit 2 | 3 | class SecuritySettingsPresenter { 4 | weak var view: ISecuritySettingsView? 5 | 6 | private let router: ISecuritySettingsRouter 7 | private let interactor: ISecuritySettingsInteractor 8 | 9 | init(router: ISecuritySettingsRouter, interactor: ISecuritySettingsInteractor) { 10 | self.router = router 11 | self.interactor = interactor 12 | } 13 | 14 | private func sync(pinSet: Bool) { 15 | view?.toggle(pinSet: pinSet) 16 | view?.set(editPinVisible: pinSet) 17 | view?.set(biometryVisible: pinSet) 18 | } 19 | 20 | } 21 | 22 | extension SecuritySettingsPresenter: ISecuritySettingsViewDelegate { 23 | 24 | func viewDidLoad() { 25 | sync(pinSet: interactor.pinSet) 26 | view?.set(biometryType: interactor.biometryType) 27 | view?.toggle(biometryEnabled: interactor.biometryEnabled) 28 | } 29 | 30 | func didSwitch(pinSet: Bool) { 31 | if pinSet { 32 | router.showSetPin(delegate: self) 33 | } else { 34 | router.showUnlock(delegate: self) 35 | } 36 | } 37 | 38 | func didTapEditPin() { 39 | router.showEditPin() 40 | } 41 | 42 | func didSwitch(biometryEnabled: Bool) { 43 | interactor.biometryEnabled = biometryEnabled 44 | } 45 | 46 | } 47 | 48 | extension SecuritySettingsPresenter: ISecuritySettingsInteractorDelegate { 49 | 50 | func didUpdate(allBackedUp: Bool) { 51 | view?.set(backupAlertVisible: !allBackedUp) 52 | view?.refresh() 53 | } 54 | 55 | func didUpdate(pinSet: Bool) { 56 | sync(pinSet: pinSet) 57 | view?.toggle(biometryEnabled: interactor.biometryEnabled) 58 | view?.refresh() 59 | } 60 | 61 | func didUpdate(biometryType: BiometryType) { 62 | view?.set(biometryType: biometryType) 63 | view?.refresh() 64 | } 65 | 66 | } 67 | 68 | extension SecuritySettingsPresenter: ISetPinDelegate { 69 | 70 | func didCancelSetPin() { 71 | view?.toggle(pinSet: false) 72 | view?.refresh() 73 | } 74 | 75 | } 76 | 77 | extension SecuritySettingsPresenter: IUnlockDelegate { 78 | 79 | func onUnlock() { 80 | do { 81 | try interactor.disablePin() 82 | 83 | sync(pinSet: false) 84 | } catch { 85 | view?.show(error: error) 86 | view?.toggle(pinSet: true) 87 | } 88 | 89 | view?.refresh() 90 | } 91 | 92 | func onCancelUnlock() { 93 | view?.toggle(pinSet: true) 94 | view?.refresh() 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Security/SecuritySettingsRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PinKit 3 | 4 | class SecuritySettingsRouter { 5 | weak var viewController: UIViewController? 6 | } 7 | 8 | extension SecuritySettingsRouter: ISecuritySettingsRouter { 9 | 10 | func showSetPin(delegate: ISetPinDelegate) { 11 | viewController?.present(App.shared.pinKit.setPinModule(delegate: delegate), animated: true) 12 | } 13 | 14 | func showEditPin() { 15 | viewController?.present(App.shared.pinKit.editPinModule, animated: true) 16 | } 17 | 18 | func showUnlock(delegate: IUnlockDelegate) { 19 | viewController?.present(App.shared.pinKit.unlockPinModule(delegate: delegate, enableBiometry: false, presentationStyle: .simple, cancellable: true), animated: true) 20 | } 21 | 22 | } 23 | 24 | extension SecuritySettingsRouter { 25 | 26 | static func module() -> UIViewController { 27 | let router = SecuritySettingsRouter() 28 | let interactor = SecuritySettingsInteractor(pinKit: App.shared.pinKit) 29 | let presenter = SecuritySettingsPresenter(router: router, interactor: interactor) 30 | let view = SecuritySettingsViewController(delegate: presenter) 31 | 32 | interactor.delegate = presenter 33 | presenter.view = view 34 | router.viewController = view 35 | 36 | return view 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Settings/Security/SecuritySettingsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import UIExtensions 3 | import SectionsTableView 4 | import RxSwift 5 | import ThemeKit 6 | import PinKit 7 | 8 | class SecuritySettingsViewController: ThemeViewController { 9 | private let delegate: ISecuritySettingsViewDelegate 10 | 11 | private let tableView = SectionsTableView(style: .grouped) 12 | 13 | private var backupAlertVisible = false 14 | private var pinSet = false 15 | private var editPinVisible = false 16 | private var biometryVisible = false 17 | private var biometryType: BiometryType = .none 18 | private var biometryEnabled = false 19 | 20 | init(delegate: ISecuritySettingsViewDelegate) { 21 | self.delegate = delegate 22 | 23 | super.init() 24 | 25 | hidesBottomBarWhenPushed = true 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | title = "settings_security.title".localized 36 | navigationItem.backBarButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil) 37 | 38 | tableView.registerCell(forClass: TitleCell.self) 39 | tableView.registerCell(forClass: RightImageCell.self) 40 | tableView.registerCell(forClass: ToggleCell.self) 41 | 42 | tableView.sectionDataSource = self 43 | 44 | tableView.backgroundColor = .clear 45 | tableView.separatorStyle = .none 46 | 47 | view.addSubview(tableView) 48 | tableView.snp.makeConstraints { maker in 49 | maker.edges.equalToSuperview() 50 | } 51 | 52 | delegate.viewDidLoad() 53 | 54 | tableView.reload() 55 | } 56 | 57 | override func viewWillAppear(_ animated: Bool) { 58 | tableView.deselectCell(withCoordinator: transitionCoordinator, animated: animated) 59 | } 60 | 61 | private var pinRows: [RowProtocol] { 62 | var rows: [RowProtocol] = [ 63 | Row(id: "pin", height: .heightSingleLineCell, bind: { [unowned self] cell, _ in 64 | cell.bind(titleIcon: UIImage(named: "Passcode Icon"), title: "settings_security.passcode".localized, isOn: self.pinSet, last: !self.editPinVisible, onToggle: { [weak self] isOn in 65 | self?.delegate.didSwitch(pinSet: isOn) 66 | }) 67 | }) 68 | ] 69 | 70 | if editPinVisible { 71 | rows.append(Row(id: "edit_pin", height: .heightSingleLineCell, bind: { cell, _ in 72 | cell.bind(titleIcon: nil, title: "settings_security.change_pin".localized, showDisclosure: true, last: true) 73 | }, action: { [weak self] _ in 74 | DispatchQueue.main.async { 75 | self?.delegate.didTapEditPin() 76 | } 77 | })) 78 | } 79 | 80 | return rows 81 | } 82 | 83 | private var biometryRow: RowProtocol? { 84 | switch biometryType { 85 | case .touchId: return createBiometryRow(title: "settings_security.touch_id".localized, icon: "Touch Id Icon") 86 | case .faceId: return createBiometryRow(title: "settings_security.face_id".localized, icon: "Face Id Icon") 87 | default: return nil 88 | } 89 | } 90 | 91 | private func createBiometryRow(title: String, icon: String) -> RowProtocol { 92 | Row(id: "biometry", height: .heightSingleLineCell, bind: { [unowned self] cell, _ in 93 | cell.bind(titleIcon: UIImage(named: icon), title: title, isOn: self.biometryEnabled, last: true, onToggle: { [weak self] isOn in 94 | self?.delegate.didSwitch(biometryEnabled: isOn) 95 | }) 96 | }) 97 | } 98 | 99 | } 100 | 101 | extension SecuritySettingsViewController: SectionsDataSource { 102 | 103 | func buildSections() -> [SectionProtocol] { 104 | var sections: [SectionProtocol] = [ 105 | Section(id: "pin", headerState: .margin(height: .margin3x), rows: pinRows) 106 | ] 107 | 108 | if biometryVisible, let biometryRow = biometryRow { 109 | sections.append(Section(id: "biometry", headerState: .margin(height: .margin8x), rows: [biometryRow])) 110 | } 111 | 112 | return sections 113 | } 114 | 115 | } 116 | 117 | extension SecuritySettingsViewController: ISecuritySettingsView { 118 | 119 | func refresh() { 120 | tableView.reload() 121 | } 122 | 123 | func set(backupAlertVisible: Bool) { 124 | self.backupAlertVisible = backupAlertVisible 125 | } 126 | 127 | func toggle(pinSet: Bool) { 128 | self.pinSet = pinSet 129 | } 130 | 131 | func set(editPinVisible: Bool) { 132 | self.editPinVisible = editPinVisible 133 | } 134 | 135 | func set(biometryVisible: Bool) { 136 | self.biometryVisible = biometryVisible 137 | } 138 | 139 | func set(biometryType: BiometryType) { 140 | self.biometryType = biometryType 141 | } 142 | 143 | func toggle(biometryEnabled: Bool) { 144 | self.biometryEnabled = biometryEnabled 145 | } 146 | 147 | func show(error: Error) { 148 | HudHelper.instance.showError(title: error.localizedDescription) 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Transactions/TransactionCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TransactionCell: UITableViewCell { 4 | } 5 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Transactions/TransactionViewItem.swift: -------------------------------------------------------------------------------- 1 | struct TransactionViewItem { 2 | } 3 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Transactions/TransactionsModule.swift: -------------------------------------------------------------------------------- 1 | 2 | protocol ITransactionsRouter { 3 | func dismiss() 4 | } 5 | 6 | protocol ITransactionsView: class { 7 | func set(balance: String?) 8 | } 9 | 10 | protocol ITransactionsViewDelegate { 11 | func onLoad() 12 | func onDeposit() 13 | func onSend() 14 | func onMain() 15 | func onChannels() 16 | func onSettings() 17 | } 18 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Transactions/TransactionsPresenter.swift: -------------------------------------------------------------------------------- 1 | class TransactionsPresenter { 2 | weak var view: ITransactionsView? 3 | private let router: ITransactionsRouter 4 | 5 | private let factory: IValueFormatterFactory 6 | 7 | init(router: ITransactionsRouter, factory: IValueFormatterFactory) { 8 | self.router = router 9 | self.factory = factory 10 | } 11 | 12 | } 13 | 14 | extension TransactionsPresenter: ITransactionsViewDelegate { 15 | 16 | func onLoad() { 17 | view?.set(balance: factory.coinBalance(balance: 99.637)) 18 | } 19 | 20 | func onDeposit() { 21 | print("onDeposit") 22 | } 23 | 24 | func onSend() { 25 | print("onSend") 26 | } 27 | 28 | func onMain() { 29 | router.dismiss() 30 | } 31 | 32 | func onChannels() { 33 | print("onChannels") 34 | } 35 | 36 | func onSettings() { 37 | print("onSettings") 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Transactions/TransactionsRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TransactionsRouter { 4 | weak var viewController: UIViewController? 5 | } 6 | 7 | extension TransactionsRouter: ITransactionsRouter { 8 | 9 | func dismiss() { 10 | viewController?.dismiss(animated: true) 11 | } 12 | 13 | } 14 | 15 | extension TransactionsRouter { 16 | 17 | static func module() -> UIViewController { 18 | let router = TransactionsRouter() 19 | let presenter = TransactionsPresenter(router: router, factory: ValueFormatterFactory()) 20 | let viewController = TransactionsViewController(delegate: presenter) 21 | 22 | router.viewController = viewController 23 | presenter.view = viewController 24 | 25 | return viewController 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Transactions/TransactionsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import UIExtensions 4 | 5 | class TransactionsViewController: ThemeViewController { 6 | let delegate: ITransactionsViewDelegate 7 | 8 | private let depositButton: UIButton = .appGreen 9 | private let sendButton: UIButton = .appYellow 10 | 11 | private let mainButton = UIButton() 12 | private let mainButtonWrapper = GradientView(gradientHeight: .heightButton / 2, fromColor: UIColor.themeCassandra.withAlphaComponent(0), toColor: UIColor.themeCassandra.withAlphaComponent(0.9)) 13 | 14 | private let tableView = UITableView(frame: .zero, style: .plain) 15 | 16 | private var items: [TransactionViewItem]? 17 | 18 | init(delegate: ITransactionsViewDelegate) { 19 | self.delegate = delegate 20 | 21 | super.init() 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | navigationController?.navigationBar.prefersLargeTitles = false 32 | 33 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "channels".localized, style: .plain, target: self, action: #selector(onTapChannels)) 34 | navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Settings Icon"), style: .plain, target: self, action: #selector(onTapSettings)) 35 | 36 | view.addSubview(depositButton) 37 | view.addSubview(sendButton) 38 | view.addSubview(tableView) 39 | view.addSubview(mainButtonWrapper) 40 | mainButtonWrapper.addSubview(mainButton) 41 | 42 | depositButton.snp.makeConstraints { maker in 43 | maker.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(CGFloat.margin4x) 44 | maker.leading.equalToSuperview().offset(CGFloat.margin6x) 45 | maker.height.equalTo(CGFloat.heightButton) 46 | } 47 | sendButton.snp.makeConstraints { maker in 48 | maker.top.equalTo(depositButton) 49 | maker.leading.equalTo(depositButton.snp.trailing).offset(CGFloat.margin2x) 50 | maker.trailing.equalToSuperview().inset(CGFloat.margin6x) 51 | maker.height.equalTo(CGFloat.heightButton) 52 | maker.width.equalTo(depositButton) 53 | } 54 | 55 | tableView.snp.makeConstraints { maker in 56 | maker.top.equalTo(depositButton.snp.bottom).offset(CGFloat.margin6x) 57 | maker.leading.trailing.bottom.equalToSuperview() 58 | } 59 | 60 | mainButtonWrapper.snp.makeConstraints { maker in 61 | maker.leading.trailing.equalToSuperview() 62 | maker.bottom.equalToSuperview() 63 | } 64 | mainButton.snp.makeConstraints { maker in 65 | maker.top.equalTo(mainButtonWrapper.snp.top) 66 | maker.leading.trailing.equalToSuperview() 67 | maker.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) 68 | maker.height.equalTo(CGFloat.heightButton) 69 | } 70 | 71 | depositButton.setTitle("receive".localized, for: .normal) 72 | depositButton.addTarget(self, action: #selector(onTapDeposit), for: .touchUpInside) 73 | 74 | sendButton.setTitle("send".localized, for: .normal) 75 | sendButton.addTarget(self, action: #selector(onTapSend), for: .touchUpInside) 76 | 77 | mainButton.setImage(UIImage(named: "Arrow Down"), for: .normal) 78 | mainButton.addTarget(self, action: #selector(onTapMain), for: .touchUpInside) 79 | 80 | tableView.registerCell(forClass: TransactionCell.self) 81 | tableView.backgroundColor = .clear 82 | tableView.dataSource = self 83 | tableView.delegate = self 84 | tableView.separatorStyle = .none 85 | tableView.backgroundColor = .clear 86 | tableView.tableFooterView = UIView(frame: .zero) 87 | tableView.estimatedRowHeight = 0 88 | tableView.delaysContentTouches = false 89 | 90 | delegate.onLoad() 91 | } 92 | 93 | @objc private func onTapChannels() { 94 | delegate.onChannels() 95 | } 96 | 97 | @objc private func onTapSettings() { 98 | delegate.onSettings() 99 | } 100 | 101 | @objc private func onTapDeposit() { 102 | delegate.onDeposit() 103 | } 104 | 105 | @objc private func onTapSend() { 106 | delegate.onSend() 107 | 108 | } 109 | 110 | @objc private func onTapMain() { 111 | delegate.onMain() 112 | } 113 | 114 | } 115 | 116 | extension TransactionsViewController: ITransactionsView { 117 | 118 | func set(balance: String?) { 119 | title = balance 120 | } 121 | 122 | } 123 | 124 | extension TransactionsViewController: UITableViewDataSource, UITableViewDelegate { 125 | 126 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 127 | items?.count ?? 0 128 | } 129 | 130 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 131 | tableView.dequeueReusableCell(withIdentifier: String(describing: TransactionCell.self), for: indexPath) 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletInteractor.swift: -------------------------------------------------------------------------------- 1 | import LightningKit 2 | import RxSwift 3 | 4 | class UnlockRemoteWalletInteractor { 5 | weak var delegate: IUnlockRemoteWalletInteractorDelegate? 6 | 7 | private let lightningKit: LightningKit.Kit 8 | 9 | private let disposeBag = DisposeBag() 10 | 11 | init(lightningKit: LightningKit.Kit) { 12 | self.lightningKit = lightningKit 13 | } 14 | 15 | } 16 | 17 | extension UnlockRemoteWalletInteractor: IUnlockRemoteWalletInteractor { 18 | 19 | func unlockWallet(password: String) { 20 | lightningKit.unlockWalletSingle(password: Data(Array(password.utf8))) 21 | .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) 22 | .observeOn(MainScheduler.instance) 23 | .subscribe(onSuccess: { [weak self] in 24 | self?.delegate?.didUnlockWallet() 25 | }, onError: { [weak self] error in 26 | self?.delegate?.didFailToUnlockWallet(error: error) 27 | }) 28 | .disposed(by: disposeBag) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletModule.swift: -------------------------------------------------------------------------------- 1 | protocol IUnlockRemoteWalletRouter { 2 | func close() 3 | } 4 | 5 | protocol IUnlockRemoteWalletView: class { 6 | func showUnlocking() 7 | func hideUnlocking() 8 | func show(error: Error) 9 | } 10 | 11 | protocol IUnlockRemoteWalletViewDelegate { 12 | func onLoad() 13 | func onTapUnlock(password: String?) 14 | func onTapCancel() 15 | } 16 | 17 | protocol IUnlockRemoteWalletInteractor { 18 | func unlockWallet(password: String) 19 | } 20 | 21 | protocol IUnlockRemoteWalletInteractorDelegate: AnyObject { 22 | func didUnlockWallet() 23 | func didFailToUnlockWallet(error: Error) 24 | } 25 | -------------------------------------------------------------------------------- /LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletPresenter.swift: -------------------------------------------------------------------------------- 1 | class UnlockRemoteWalletPresenter { 2 | weak var view: IUnlockRemoteWalletView? 3 | 4 | private let interactor: IUnlockRemoteWalletInteractor 5 | private let router: IUnlockRemoteWalletRouter 6 | 7 | init(interactor: IUnlockRemoteWalletInteractor, router: IUnlockRemoteWalletRouter) { 8 | self.interactor = interactor 9 | self.router = router 10 | } 11 | 12 | } 13 | 14 | extension UnlockRemoteWalletPresenter: IUnlockRemoteWalletViewDelegate { 15 | 16 | func onLoad() { 17 | } 18 | 19 | func onTapUnlock(password: String?) { 20 | guard let password = password else { 21 | return 22 | } 23 | 24 | view?.showUnlocking() 25 | interactor.unlockWallet(password: password) 26 | } 27 | 28 | func onTapCancel() { 29 | router.close() 30 | } 31 | 32 | } 33 | 34 | extension UnlockRemoteWalletPresenter: IUnlockRemoteWalletInteractorDelegate { 35 | 36 | func didUnlockWallet() { 37 | view?.hideUnlocking() 38 | router.close() 39 | } 40 | 41 | func didFailToUnlockWallet(error: Error) { 42 | view?.show(error: error) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import LightningKit 4 | 5 | class UnlockRemoteWalletRouter { 6 | weak var viewController: UIViewController? 7 | } 8 | 9 | extension UnlockRemoteWalletRouter: IUnlockRemoteWalletRouter { 10 | 11 | func close() { 12 | viewController?.dismiss(animated: true) 13 | } 14 | 15 | } 16 | 17 | extension UnlockRemoteWalletRouter { 18 | 19 | static func module() -> UIViewController { 20 | guard let lightningKit = App.shared.lightningKitManager.currentKit else { 21 | // TODO: show empty view controller with message 22 | fatalError() 23 | } 24 | 25 | let router = UnlockRemoteWalletRouter() 26 | let interactor = UnlockRemoteWalletInteractor(lightningKit: lightningKit) 27 | let presenter = UnlockRemoteWalletPresenter(interactor: interactor, router: router) 28 | let viewController = UnlockRemoteWalletViewController(delegate: presenter) 29 | 30 | presenter.view = viewController 31 | interactor.delegate = presenter 32 | router.viewController = viewController 33 | 34 | App.shared.pinKitDelegate.viewController = viewController 35 | 36 | return ThemeNavigationController(rootViewController: viewController) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /LightningWallet/Modules/UnlockRemoteWallet/UnlockRemoteWalletViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import SnapKit 4 | 5 | class UnlockRemoteWalletViewController: ThemeViewController { 6 | private let delegate: IUnlockRemoteWalletViewDelegate 7 | 8 | private let descriptionLabel = UILabel() 9 | private let passwordTextField = UITextField() 10 | private let unlockButton: UIButton = .appGreen 11 | 12 | init(delegate: IUnlockRemoteWalletViewDelegate) { 13 | self.delegate = delegate 14 | 15 | super.init() 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | title = "Unlock Wallet" 26 | 27 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onTapCancel)) 28 | 29 | view.addSubview(descriptionLabel) 30 | descriptionLabel.snp.makeConstraints { maker in 31 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin4x) 32 | maker.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(CGFloat.margin3x) 33 | } 34 | 35 | descriptionLabel.numberOfLines = 0 36 | descriptionLabel.font = .subhead2 37 | descriptionLabel.textColor = .themeLeah 38 | 39 | view.addSubview(passwordTextField) 40 | passwordTextField.snp.makeConstraints { maker in 41 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin4x) 42 | maker.top.equalTo(descriptionLabel.snp.bottom).offset(CGFloat.margin3x) 43 | } 44 | 45 | passwordTextField.borderWidth = 1 46 | passwordTextField.borderColor = .themeGray 47 | 48 | view.addSubview(unlockButton) 49 | unlockButton.snp.makeConstraints { maker in 50 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin4x) 51 | maker.top.equalTo(passwordTextField.snp.bottom).offset(CGFloat.margin3x) 52 | maker.height.equalTo(CGFloat.heightButton) 53 | } 54 | 55 | unlockButton.setTitle("Unlock", for: .normal) 56 | unlockButton.addTarget(self, action: #selector(onTapUnlock), for: .touchUpInside) 57 | 58 | delegate.onLoad() 59 | } 60 | 61 | override func viewDidAppear(_ animated: Bool) { 62 | super.viewDidAppear(animated) 63 | 64 | passwordTextField.becomeFirstResponder() 65 | } 66 | 67 | @objc private func onTapUnlock() { 68 | delegate.onTapUnlock(password: passwordTextField.text) 69 | } 70 | 71 | @objc private func onTapCancel() { 72 | delegate.onTapCancel() 73 | } 74 | 75 | } 76 | 77 | extension UnlockRemoteWalletViewController: IUnlockRemoteWalletView { 78 | 79 | func showUnlocking() { 80 | passwordTextField.resignFirstResponder() 81 | HudHelper.instance.showSpinner(title: "Unlocking...", userInteractionEnabled: false) 82 | } 83 | 84 | func hideUnlocking() { 85 | HudHelper.instance.hide() 86 | } 87 | 88 | func show(error: Error) { 89 | HudHelper.instance.showError(subtitle: error.localizedDescription) 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Welcome/WelcomeScreenInteractor.swift: -------------------------------------------------------------------------------- 1 | class WelcomeScreenInteractor { 2 | 3 | init() { 4 | } 5 | 6 | } 7 | 8 | extension WelcomeScreenInteractor: IWelcomeScreenInteractor { 9 | 10 | var appVersion: String { 11 | "0.1" 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Welcome/WelcomeScreenModule.swift: -------------------------------------------------------------------------------- 1 | protocol IWelcomeScreenView: class { 2 | func set(appVersion: String) 3 | } 4 | 5 | protocol IWelcomeScreenViewDelegate { 6 | func viewDidLoad() 7 | func onTapCreate() 8 | func onTapRestore() 9 | func onTapConnect() 10 | } 11 | 12 | protocol IWelcomeScreenInteractor { 13 | var appVersion: String { get } 14 | } 15 | 16 | protocol IWelcomeScreenRouter { 17 | func showCreateWallet() 18 | func showRestoreWallet() 19 | func showConnectToRemoteNode() 20 | } 21 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Welcome/WelcomeScreenPresenter.swift: -------------------------------------------------------------------------------- 1 | class WelcomeScreenPresenter { 2 | private let interactor: IWelcomeScreenInteractor 3 | private let router: IWelcomeScreenRouter 4 | 5 | weak var view: IWelcomeScreenView? 6 | 7 | init(interactor: IWelcomeScreenInteractor, router: IWelcomeScreenRouter) { 8 | self.interactor = interactor 9 | self.router = router 10 | } 11 | 12 | } 13 | 14 | extension WelcomeScreenPresenter: IWelcomeScreenViewDelegate { 15 | 16 | func viewDidLoad() { 17 | view?.set(appVersion: interactor.appVersion) 18 | } 19 | 20 | func onTapCreate() { 21 | router.showCreateWallet() 22 | } 23 | 24 | func onTapRestore() { 25 | router.showRestoreWallet() 26 | } 27 | 28 | func onTapConnect() { 29 | router.showConnectToRemoteNode() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Welcome/WelcomeScreenRouter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ThemeKit 3 | import UIExtensions 4 | 5 | class WelcomeScreenRouter { 6 | weak var viewController: UIViewController? 7 | } 8 | 9 | extension WelcomeScreenRouter: IWelcomeScreenRouter { 10 | 11 | func showCreateWallet() { 12 | } 13 | 14 | func showRestoreWallet() { 15 | } 16 | 17 | func showConnectToRemoteNode() { 18 | viewController?.navigationController?.pushViewController(NodeCredentialsRouter.module(), animated: true) 19 | } 20 | 21 | } 22 | 23 | extension WelcomeScreenRouter { 24 | 25 | static func module() -> UIViewController { 26 | let router = WelcomeScreenRouter() 27 | let interactor = WelcomeScreenInteractor() 28 | let presenter = WelcomeScreenPresenter(interactor: interactor, router: router) 29 | let viewController = WelcomeScreenViewController(delegate: presenter) 30 | 31 | presenter.view = viewController 32 | router.viewController = viewController 33 | 34 | return ThemeNavigationController(rootViewController: viewController) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /LightningWallet/Modules/Welcome/WelcomeScreenViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | import ThemeKit 4 | 5 | class WelcomeScreenViewController: ThemeViewController { 6 | private let delegate: IWelcomeScreenViewDelegate 7 | 8 | private let topWrapper = UIView() 9 | private let titleWrapper = UIView() 10 | private let titleLabel = UILabel() 11 | private let subtitleLabel = UILabel() 12 | 13 | private let createButton = UIButton.appYellow 14 | private let restoreButton = UIButton.appGray 15 | private let connectButton = UIButton.appGreen 16 | private let versionLabel = UILabel() 17 | 18 | init(delegate: IWelcomeScreenViewDelegate) { 19 | self.delegate = delegate 20 | 21 | super.init() 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | view.addSubview(topWrapper) 32 | topWrapper.snp.makeConstraints { maker in 33 | maker.top.equalTo(view.safeAreaLayoutGuide) 34 | maker.leading.trailing.equalToSuperview() 35 | } 36 | 37 | topWrapper.addSubview(titleWrapper) 38 | titleWrapper.snp.makeConstraints { maker in 39 | maker.centerY.equalToSuperview() 40 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin8x) 41 | } 42 | 43 | titleLabel.text = "welcome.title".localized 44 | titleLabel.numberOfLines = 0 45 | titleLabel.font = .title2 46 | titleLabel.textColor = .themeOz 47 | 48 | titleWrapper.addSubview(titleLabel) 49 | titleLabel.snp.makeConstraints { maker in 50 | maker.leading.top.trailing.equalToSuperview() 51 | } 52 | 53 | subtitleLabel.text = "welcome.subtitle".localized 54 | subtitleLabel.font = .body 55 | subtitleLabel.textColor = .themeGray 56 | 57 | titleWrapper.addSubview(subtitleLabel) 58 | subtitleLabel.snp.makeConstraints { maker in 59 | maker.leading.trailing.bottom.equalToSuperview() 60 | maker.top.equalTo(titleLabel.snp.bottom).offset(CGFloat.margin4x) 61 | } 62 | 63 | createButton.isEnabled = false 64 | createButton.setTitle("welcome.new_wallet".localized, for: .normal) 65 | createButton.addTarget(self, action: #selector(onTapCreate), for: .touchUpInside) 66 | 67 | view.addSubview(createButton) 68 | createButton.snp.makeConstraints { maker in 69 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 70 | maker.top.equalTo(topWrapper.snp.bottom) 71 | maker.height.equalTo(CGFloat.heightButton) 72 | } 73 | 74 | restoreButton.isEnabled = false 75 | restoreButton.setTitle("welcome.restore_wallet".localized, for: .normal) 76 | restoreButton.addTarget(self, action: #selector(onTapRestore), for: .touchUpInside) 77 | 78 | view.addSubview(restoreButton) 79 | restoreButton.snp.makeConstraints { maker in 80 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 81 | maker.top.equalTo(createButton.snp.bottom).offset(CGFloat.margin4x) 82 | maker.height.equalTo(CGFloat.heightButton) 83 | } 84 | 85 | connectButton.setTitle("welcome.connect_to_remote_node".localized, for: .normal) 86 | connectButton.addTarget(self, action: #selector(onTapConnect), for: .touchUpInside) 87 | 88 | view.addSubview(connectButton) 89 | connectButton.snp.makeConstraints { maker in 90 | maker.leading.trailing.equalToSuperview().inset(CGFloat.margin6x) 91 | maker.top.equalTo(restoreButton.snp.bottom).offset(CGFloat.margin4x) 92 | maker.height.equalTo(CGFloat.heightButton) 93 | } 94 | 95 | versionLabel.textColor = .themeGray 96 | versionLabel.font = .caption 97 | 98 | view.addSubview(versionLabel) 99 | versionLabel.snp.makeConstraints { maker in 100 | maker.top.equalTo(connectButton.snp.bottom).offset(CGFloat.margin4x) 101 | maker.bottom.equalTo(view.safeAreaLayoutGuide).inset(CGFloat.margin4x) 102 | maker.centerX.equalToSuperview() 103 | } 104 | 105 | delegate.viewDidLoad() 106 | } 107 | 108 | override func viewWillAppear(_ animated: Bool) { 109 | super.viewWillAppear(animated) 110 | navigationController?.setNavigationBarHidden(true, animated: animated) 111 | } 112 | 113 | override func viewWillDisappear(_ animated: Bool) { 114 | super.viewWillDisappear(animated) 115 | navigationController?.setNavigationBarHidden(false, animated: animated) 116 | } 117 | 118 | @objc func onTapCreate() { 119 | delegate.onTapCreate() 120 | } 121 | 122 | @objc func onTapRestore() { 123 | delegate.onTapRestore() 124 | } 125 | 126 | @objc func onTapConnect() { 127 | delegate.onTapConnect() 128 | } 129 | 130 | } 131 | 132 | extension WelcomeScreenViewController: IWelcomeScreenView { 133 | 134 | func set(appVersion: String) { 135 | versionLabel.text = "welcome.version".localized(appVersion) 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /LightningWallet/UserInterface/ScanQr/PermissionHelper.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AVFoundation 3 | 4 | class PermissionsHelper { 5 | 6 | static func performWithCameraPermission(onComplete: @escaping (Bool) -> ()) { 7 | if AVCaptureDevice.authorizationStatus(for: AVMediaType.video) == .authorized { 8 | onComplete(true) 9 | } else { 10 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { granted in 11 | DispatchQueue.main.async { 12 | if granted { 13 | onComplete(true) 14 | } else { 15 | onComplete(false) 16 | } 17 | } 18 | }) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /LightningWallet/UserInterface/ScanQr/ScanQrAlertView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SnapKit 3 | 4 | protocol IScanQrCodeDelegate: AnyObject { 5 | func didScan(string: String) 6 | } 7 | 8 | class ScanQrAlertView: UIView { 9 | private let wrapperView = UIView() 10 | private let titleLabel = UILabel() 11 | 12 | private let actionButton = UIButton.appSecondary 13 | private var action: (() -> ())? 14 | 15 | init() { 16 | super.init(frame: .zero) 17 | 18 | addSubview(wrapperView) 19 | wrapperView.snp.makeConstraints { maker in 20 | maker.leading.trailing.equalToSuperview() 21 | maker.centerY.equalToSuperview() 22 | maker.top.greaterThanOrEqualToSuperview().offset(CGFloat.margin12x) 23 | } 24 | 25 | wrapperView.addSubview(titleLabel) 26 | wrapperView.addSubview(actionButton) 27 | 28 | updateConstraints(showButton: false) 29 | 30 | titleLabel.font = .subhead2 31 | titleLabel.textColor = .themeGray 32 | titleLabel.textAlignment = .center 33 | titleLabel.numberOfLines = 0 34 | 35 | actionButton.addTarget(self, action: #selector(onTapAction), for: .touchUpInside) 36 | 37 | clipsToBounds = true 38 | layer.cornerRadius = .cornerRadius2x 39 | backgroundColor = .black // must be black in all themes 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | @objc private func onTapAction() { 47 | action?() 48 | } 49 | 50 | private func updateConstraints(showButton: Bool) { 51 | titleLabel.snp.remakeConstraints { maker in 52 | maker.leading.equalToSuperview().offset(CGFloat.margin12x) 53 | maker.centerX.equalToSuperview() 54 | maker.top.equalToSuperview() 55 | if !showButton { 56 | maker.bottom.equalToSuperview() 57 | } 58 | } 59 | actionButton.isHidden = !showButton 60 | actionButton.snp.remakeConstraints { maker in 61 | if showButton { 62 | maker.top.equalTo(titleLabel.snp.bottom).offset(CGFloat.margin6x) 63 | } 64 | maker.leading.trailing.equalToSuperview().inset(72) 65 | maker.height.equalTo(CGFloat.heightButtonSecondary) 66 | maker.bottom.equalToSuperview() 67 | } 68 | } 69 | 70 | func bind(title: String, actionTitle: String? = nil, action: (() -> ())? = nil) { 71 | self.action = action 72 | 73 | titleLabel.text = title 74 | actionButton.setTitle(actionTitle, for: .normal) 75 | 76 | updateConstraints(showButton: actionTitle != nil) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /LightningWallet/UserInterface/ScanQr/ScanQrBlurView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import UIExtensions 3 | 4 | class ScanQrBlurView: CustomIntensityVisualEffectView { 5 | private let sideMargin: CGFloat 6 | private let maskLayer = CAShapeLayer() 7 | 8 | init(sideMargin: CGFloat) { 9 | self.sideMargin = sideMargin 10 | 11 | super.init(effect: UIBlurEffect(style: .themeHud), intensity: 0.7) 12 | } 13 | 14 | required init?(coder aDecoder: NSCoder) { 15 | fatalError("init(coder:) has not been implemented") 16 | } 17 | 18 | func layoutBlurMask() { 19 | let path = UIBezierPath ( 20 | roundedRect: bounds, 21 | cornerRadius: 0) 22 | 23 | let width = self.width - 2 * sideMargin 24 | let vMargin = (self.height - width) / 2 25 | 26 | let transparentRect = UIBezierPath ( 27 | roundedRect: bounds.inset(by: UIEdgeInsets(top: vMargin, left: sideMargin, bottom: vMargin, right: sideMargin)), 28 | cornerRadius: .cornerRadius2x) 29 | 30 | path.append(transparentRect) 31 | path.usesEvenOddFillRule = true 32 | 33 | maskLayer.path = path.cgPath 34 | maskLayer.fillRule = CAShapeLayerFillRule.evenOdd 35 | 36 | layer.mask = maskLayer //for iOS > 11.* 37 | } 38 | 39 | override func layoutSubviews() { 40 | super.layoutSubviews() 41 | 42 | layoutBlurMask() 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /LightningWallet/UserInterface/ScanQr/ScanQrView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AVFoundation 3 | import UIExtensions 4 | 5 | class ScanQrView: UIView { 6 | private let scanQueue = DispatchQueue(label: "io.horizontalsystems.lightning.scan_view", qos: .default) 7 | private static let sideMargin: CGFloat = CGFloat.margin6x 8 | 9 | weak var delegate: IScanQrCodeDelegate? 10 | 11 | private var captureSession: AVCaptureSession! 12 | private var metadataOutput: AVCaptureMetadataOutput? 13 | private let previewLayer: AVCaptureVideoPreviewLayer 14 | 15 | private let blurView = ScanQrBlurView(sideMargin: ScanQrView.sideMargin) 16 | private let alertView = ScanQrAlertView() 17 | 18 | private var initiallySetUp = false 19 | 20 | override var bounds: CGRect { 21 | didSet { 22 | previewLayer.frame = layer.bounds 23 | } 24 | } 25 | 26 | init() { 27 | captureSession = AVCaptureSession() 28 | previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) 29 | 30 | super.init(frame: .zero) 31 | 32 | previewLayer.frame = layer.bounds 33 | previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill 34 | 35 | layer.addSublayer(previewLayer) 36 | 37 | addSubview(blurView) 38 | blurView.snp.makeConstraints { maker in 39 | maker.edges.equalToSuperview() 40 | } 41 | 42 | addSubview(alertView) 43 | alertView.snp.makeConstraints { maker in 44 | maker.center.equalToSuperview() 45 | maker.leading.trailing.equalToSuperview().inset(24) 46 | maker.height.equalTo(alertView.snp.width) 47 | } 48 | alertView.isHidden = true 49 | } 50 | 51 | required init?(coder aDecoder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | 55 | private func initialSetup() { 56 | scanQueue.async { () -> Void in 57 | do { 58 | guard let videoCaptureDevice = AVCaptureDevice.default(for: AVMediaType.video) else { 59 | self.failed() 60 | return 61 | } 62 | let videoInput: AVCaptureDeviceInput 63 | 64 | videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice) 65 | guard self.captureSession.canAddInput(videoInput) else { 66 | self.failed() 67 | return 68 | } 69 | self.captureSession.addInput(videoInput) 70 | 71 | let metadataOutput = AVCaptureMetadataOutput() 72 | self.metadataOutput = metadataOutput 73 | if (self.captureSession.canAddOutput(metadataOutput)) { 74 | self.captureSession.addOutput(metadataOutput) 75 | 76 | metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) 77 | metadataOutput.metadataObjectTypes = [.qr] 78 | DispatchQueue.main.async { 79 | self.updateRectOfInterest() 80 | } 81 | } else { 82 | self.failed() 83 | } 84 | } catch { 85 | } 86 | } 87 | } 88 | 89 | private func updateRectOfInterest() { 90 | guard !width.isZero && !height.isZero else { 91 | return 92 | } 93 | 94 | let left = ScanQrView.sideMargin / width 95 | let rectWidth = width - 2 * ScanQrView.sideMargin 96 | let top = ((height - rectWidth) / 2) / height 97 | 98 | metadataOutput?.rectOfInterest = CGRect(x: top, y: left, width: rectWidth / height, height: rectWidth / width) 99 | } 100 | 101 | private func failed() { 102 | captureSession = nil 103 | showAlert(title: "access_camera.not_supported".localized) 104 | } 105 | 106 | private func showAlert(title: String, actionText: String? = nil, action: (() -> ())? = nil) { 107 | alertView.isHidden = false 108 | alertView.bind(title: title, actionTitle: actionText, action: action) 109 | } 110 | 111 | func start() { 112 | scanQueue.async { 113 | if let captureSession = self.captureSession, !captureSession.isRunning { 114 | DispatchQueue.main.async { 115 | captureSession.startRunning() 116 | } 117 | } 118 | 119 | if !self.initiallySetUp { 120 | self.initiallySetUp = true 121 | 122 | PermissionsHelper.performWithCameraPermission { [weak self] success in 123 | if success { 124 | self?.initialSetup() 125 | } else { 126 | self?.showAlert(title: "access_camera.message".localized, actionText: "access_camera.settings".localized) { 127 | if let url = URL(string: UIApplication.openSettingsURLString) { 128 | UIApplication.shared.open(url) 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | func stop() { 138 | if let captureSession = captureSession, captureSession.isRunning { 139 | captureSession.stopRunning() 140 | } 141 | } 142 | 143 | } 144 | 145 | extension ScanQrView: AVCaptureMetadataOutputObjectsDelegate { 146 | 147 | func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { 148 | if let metadataObject = metadataObjects.first, 149 | let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject, 150 | let stringValue = readableObject.stringValue { 151 | 152 | delegate?.didScan(string: stringValue) 153 | } 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /LightningWallet/UserInterface/TransparentNavigationBar.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TransparentNavigationBar { 4 | 5 | static func set(to navigationBar: UINavigationBar?) { 6 | // set navigation theme for iOS less than 13 7 | if #available(iOS 13.0, *) { 8 | let transparentAppearance = UINavigationBarAppearance() 9 | transparentAppearance.configureWithTransparentBackground() 10 | transparentAppearance.titleTextAttributes = [.foregroundColor: UIColor.themeOz] 11 | transparentAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.themeOz] 12 | 13 | navigationBar?.standardAppearance = transparentAppearance 14 | navigationBar?.scrollEdgeAppearance = transparentAppearance 15 | } else { 16 | let colorImage = UIImage(color: .clear) 17 | navigationBar?.setBackgroundImage(colorImage, for: .default) 18 | navigationBar?.shadowImage = UIImage() 19 | return 20 | } 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /LightningWallet/UserInterface/ValueFormatterFactory.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CurrencyKit 3 | 4 | class ValueFormatterFactory { 5 | 6 | private let currencyFormatter: NumberFormatter = { 7 | let formatter = NumberFormatter() 8 | formatter.numberStyle = .currency 9 | return formatter 10 | }() 11 | 12 | } 13 | 14 | extension ValueFormatterFactory: IValueFormatterFactory { 15 | 16 | func currencyValue(balance: Decimal?, rate: Decimal?, currency: Currency) -> String? { 17 | guard let balance = balance, let rate = rate else { 18 | return nil 19 | } 20 | 21 | currencyFormatter.currencyCode = currency.code 22 | currencyFormatter.currencySymbol = currency.symbol 23 | 24 | return currencyFormatter.string(from: balance * rate as NSNumber) 25 | } 26 | 27 | func coinBalance(balance: Decimal?) -> String? { 28 | guard let balance = balance else { 29 | return nil 30 | } 31 | 32 | return "\(balance) sat" 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LightningWallet/de.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/de.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/de.lproj/Localizable.strings -------------------------------------------------------------------------------- /LightningWallet/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "NSCameraUsageDescription" = "We need access to camera to scan QR codes."; 2 | "NSFaceIDUsageDescription" = "We use Face ID to unlock wallet."; 3 | -------------------------------------------------------------------------------- /LightningWallet/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "channels" = "Channels"; 2 | "receive" = "Receive"; 3 | "send" = "Send"; 4 | "backup" = "Backup"; 5 | "close" = "Close"; 6 | "open" = "Open"; 7 | "closed" = "Closed"; 8 | 9 | // Welcome 10 | 11 | "welcome.title" = "Welcome to\nLightning\nWallet"; 12 | "welcome.subtitle" = "by Horizontal Systems"; 13 | "welcome.new_wallet" = "Create Wallet"; 14 | "welcome.restore_wallet" = "Restore Wallet"; 15 | "welcome.connect_to_remote_node" = "Connect to Remote Node"; 16 | "welcome.version" = "Version %@"; 17 | 18 | //Main 19 | 20 | "main_controller.sync_text" = "Synchronizing... %@%%"; 21 | "main_controller.total_balance" = "Total Balance"; 22 | 23 | // Settings 24 | 25 | "settings.title" = "Settings"; 26 | "settings.tab_bar_item" = "Settings"; 27 | "settings.security_center" = "Security Center"; 28 | "settings.base_currency" = "Base Currency"; 29 | "settings.language" = "Language"; 30 | "settings.light_mode" = "Light Mode"; 31 | "settings.about" = "About"; 32 | "settings.tell_friends" = "Tell Friends"; 33 | "settings.contact" = "Contact"; 34 | "settings.info_title" = "LIGHTNING WALLET"; 35 | "settings.info_subtitle" = "decentralized app"; 36 | "settings.log_out" = "Log Out"; 37 | 38 | // Settings -> Tell Friends 39 | 40 | "settings_tell_friends.text" = "Lightning Wallet - a lightning wallet I recommend"; 41 | 42 | // Settings -> Security 43 | 44 | "settings_security.title" = "Security Center"; 45 | "settings_security.passcode" = "Passcode"; 46 | "settings_security.change_pin" = "Edit Passcode"; 47 | "settings_security.touch_id" = "Touch ID"; 48 | "settings_security.face_id" = "Face ID"; 49 | "security_settings.delete_alert_button" = "Delete from Phone"; 50 | 51 | "channels.new_channel" = "New Channel"; 52 | 53 | // Access Camera 54 | 55 | "access_camera.message" = "Access to your camera for this app was denied, you can go to settings and allow access"; 56 | "access_camera.not_supported" = "Your device does not support scanning a code from an item. Please use a device with a camera."; 57 | "access_camera.settings" = "Go To Settings"; 58 | 59 | // Node Credentials 60 | 61 | "node_credentials.invalid_code" = "Invalid code format"; 62 | "node_credentials.empty_clipboard" = "Empty clipboard"; 63 | "node_credentials.description" = "Scan the QR generated by lndconnect server, or paste link lndconnect-j to connect your node"; 64 | 65 | // Node Connect 66 | 67 | "node_connect.title" = "Address"; 68 | "node_connect.connecting" = "Connecting..."; 69 | -------------------------------------------------------------------------------- /LightningWallet/es.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/es.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /LightningWallet/fr.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/fr.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/fr.lproj/Localizable.strings -------------------------------------------------------------------------------- /LightningWallet/ko.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/ko.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/ko.lproj/Localizable.strings -------------------------------------------------------------------------------- /LightningWallet/ru.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/ru.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /LightningWallet/tr.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/tr.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/tr.lproj/Localizable.strings -------------------------------------------------------------------------------- /LightningWallet/zh.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/zh.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /LightningWallet/zh.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizontalsystems/lightning-wallet-ios/8eaea6866f7ac3382bf779435589083f55721692/LightningWallet/zh.lproj/Localizable.strings -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11' 2 | use_frameworks! 3 | 4 | inhibit_all_warnings! 5 | 6 | workspace 'LightningWallet' 7 | 8 | target 'LightningWallet' do 9 | project 'LightningWallet' 10 | 11 | pod 'UIExtensions.swift', git: 'https://github.com/horizontalsystems/gui-kit/' 12 | # pod 'UIExtensions.swift', path: '../gui-kit/' 13 | pod 'HUD.swift', git: 'https://github.com/horizontalsystems/gui-kit/' 14 | # pod 'HUD.swift', path: '../gui-kit/' 15 | pod 'ThemeKit.swift', git: 'https://github.com/horizontalsystems/component-kit-ios/' 16 | # pod 'ThemeKit.swift', path: '../component-kit-ios/' 17 | pod 'LanguageKit.swift', git: 'https://github.com/horizontalsystems/component-kit-ios/' 18 | # pod 'LanguageKit.swift', path: '../component-kit-ios/' 19 | pod 'StorageKit.swift', git: 'https://github.com/horizontalsystems/component-kit-ios/' 20 | # pod 'StorageKit.swift', path: '../component-kit-ios/' 21 | pod 'PinKit.swift', git: 'https://github.com/horizontalsystems/component-kit-ios/' 22 | # pod 'PinKit.swift', path: '../component-kit-ios/' 23 | pod 'CurrencyKit.swift', git: 'https://github.com/horizontalsystems/component-kit-ios/' 24 | # pod 'CurrencyKit.swift', path: '../component-kit-ios/' 25 | 26 | pod 'SectionsTableView.swift' 27 | 28 | pod 'Alamofire' 29 | pod 'ObjectMapper' 30 | 31 | pod 'GRDB.swift' 32 | pod 'KeychainAccess' 33 | 34 | pod 'RxSwift' 35 | pod 'SnapKit' 36 | end 37 | 38 | target 'LightningKit' do 39 | project 'LightningKit' 40 | 41 | pod 'RxSwift' 42 | end 43 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.9.1) 3 | - CurrencyKit.swift (1.0): 4 | - LanguageKit.swift (~> 1.0) 5 | - RxSwift (~> 5.0) 6 | - SectionsTableView.swift (~> 1.1) 7 | - StorageKit.swift (~> 1.0) 8 | - ThemeKit.swift (~> 1.0) 9 | - GRDB.swift (4.9.0): 10 | - GRDB.swift/standard (= 4.9.0) 11 | - GRDB.swift/standard (4.9.0) 12 | - HUD.swift (1.2): 13 | - SnapKit (~> 5.0) 14 | - UIExtensions.swift (~> 1.1) 15 | - KeychainAccess (4.1.0) 16 | - LanguageKit.swift (1.0): 17 | - SectionsTableView.swift (~> 1.1) 18 | - ThemeKit.swift (~> 1.0) 19 | - UIExtensions.swift (~> 1.1) 20 | - ObjectMapper (3.5.1) 21 | - PinKit.swift (1.0): 22 | - LanguageKit.swift (~> 1.0) 23 | - RxSwift 24 | - StorageKit.swift (~> 1.0) 25 | - ThemeKit.swift (~> 1.0) 26 | - UIExtensions.swift (~> 1.1) 27 | - RxSwift (5.0.1) 28 | - SectionsTableView.swift (1.1): 29 | - SnapKit (~> 5.0) 30 | - UIExtensions.swift (~> 1.1) 31 | - SnapKit (5.0.1) 32 | - StorageKit.swift (1.0): 33 | - KeychainAccess (~> 4.1) 34 | - LanguageKit.swift (~> 1.0) 35 | - ThemeKit.swift (~> 1.0) 36 | - ThemeKit.swift (1.0): 37 | - HUD.swift (~> 1.2) 38 | - SnapKit (~> 5.0) 39 | - UIExtensions.swift (~> 1.1) 40 | - UIExtensions.swift (1.1) 41 | 42 | DEPENDENCIES: 43 | - Alamofire 44 | - CurrencyKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) 45 | - GRDB.swift 46 | - HUD.swift (from `https://github.com/horizontalsystems/gui-kit/`) 47 | - KeychainAccess 48 | - LanguageKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) 49 | - ObjectMapper 50 | - PinKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) 51 | - RxSwift 52 | - SectionsTableView.swift 53 | - SnapKit 54 | - StorageKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) 55 | - ThemeKit.swift (from `https://github.com/horizontalsystems/component-kit-ios/`) 56 | - UIExtensions.swift (from `https://github.com/horizontalsystems/gui-kit/`) 57 | 58 | SPEC REPOS: 59 | trunk: 60 | - Alamofire 61 | - GRDB.swift 62 | - KeychainAccess 63 | - ObjectMapper 64 | - RxSwift 65 | - SectionsTableView.swift 66 | - SnapKit 67 | 68 | EXTERNAL SOURCES: 69 | CurrencyKit.swift: 70 | :git: https://github.com/horizontalsystems/component-kit-ios/ 71 | HUD.swift: 72 | :git: https://github.com/horizontalsystems/gui-kit/ 73 | LanguageKit.swift: 74 | :git: https://github.com/horizontalsystems/component-kit-ios/ 75 | PinKit.swift: 76 | :git: https://github.com/horizontalsystems/component-kit-ios/ 77 | StorageKit.swift: 78 | :git: https://github.com/horizontalsystems/component-kit-ios/ 79 | ThemeKit.swift: 80 | :git: https://github.com/horizontalsystems/component-kit-ios/ 81 | UIExtensions.swift: 82 | :git: https://github.com/horizontalsystems/gui-kit/ 83 | 84 | CHECKOUT OPTIONS: 85 | CurrencyKit.swift: 86 | :commit: ee2fd2f8f530a68ba33dfc8ff0b4f70c9742df0e 87 | :git: https://github.com/horizontalsystems/component-kit-ios/ 88 | HUD.swift: 89 | :commit: c91d33f17f74bb06d37186f2f843497ccbfb07ef 90 | :git: https://github.com/horizontalsystems/gui-kit/ 91 | LanguageKit.swift: 92 | :commit: 08eb226900bebfb9be174754532d65c02175cefd 93 | :git: https://github.com/horizontalsystems/component-kit-ios/ 94 | PinKit.swift: 95 | :commit: f601d8b4e3e91f46f7bc26a90dcb279ce203ccd2 96 | :git: https://github.com/horizontalsystems/component-kit-ios/ 97 | StorageKit.swift: 98 | :commit: 526ccca2797e5d5777443d9ffad7a16006d06b89 99 | :git: https://github.com/horizontalsystems/component-kit-ios/ 100 | ThemeKit.swift: 101 | :commit: f3e8465544c26f2c75f0a6e07fa408a7b8ad6602 102 | :git: https://github.com/horizontalsystems/component-kit-ios/ 103 | UIExtensions.swift: 104 | :commit: 0a1cc8f0ec54fdf82de9a30de5a65f5146513990 105 | :git: https://github.com/horizontalsystems/gui-kit/ 106 | 107 | SPEC CHECKSUMS: 108 | Alamofire: 85e8a02c69d6020a0d734f6054870d7ecb75cf18 109 | CurrencyKit.swift: c1a37e0e43eac3828be95c8348693f192d67b376 110 | GRDB.swift: 4a6aab0c6f492db0ed42c76efdd5733245516db4 111 | HUD.swift: 0b9d074f07f601891878f87ff10ad867eed3182b 112 | KeychainAccess: 445e28864fe6d3458b41fa211bcdc39890e8bd5a 113 | LanguageKit.swift: f1105c75fe9d04f482d2ab55e48eb91ed008abef 114 | ObjectMapper: 70187b8941977c62ccfb423caf6b50be405cabf0 115 | PinKit.swift: 1acbf82ce5114972f1eb9a5a0c1b808dc08ad12e 116 | RxSwift: e2dc62b366a3adf6a0be44ba9f405efd4c94e0c4 117 | SectionsTableView.swift: 91f2235b63405f938621b7df040149d7c65b355d 118 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb 119 | StorageKit.swift: 1b618d59978510ac2f32971a01f87e54cddbe0da 120 | ThemeKit.swift: ab5c081b10f6a80149d37751f3ee98f1292bf497 121 | UIExtensions.swift: b3852a339a81c730d98b3342ae0275e807703782 122 | 123 | PODFILE CHECKSUM: d9058190292fc1f957185e649c4ed6b9a6b608b7 124 | 125 | COCOAPODS: 1.8.4 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightning Wallet 2 | 3 | ## Installation 4 | 5 | 1. Run `carthage bootstrap --platform iOS` to download the required frameworks. 6 | 2. Run `pod install` to install required pods 7 | 8 | ## Source Code 9 | 10 | [https://github.com/horizontalsystems/lightning-wallet-ios](https://github.com/horizontalsystems/lightning-wallet-ios) 11 | 12 | ## License 13 | 14 | This wallet is open source and available under the terms of the MIT Lincense. 15 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | lane :build_for_hockey do |options| 2 | prepare_environment 3 | 4 | cert(username: ENV['ITUNES_CONNECT_USERNAME'], team_id: options[:dp_team_id]) 5 | sigh(app_identifier: "io.horizontalsystems.LightningWallet", force: true, adhoc: true, username: ENV['ITUNES_CONNECT_USERNAME'], team_id: options[:dp_team_id]) 6 | gym(scheme: "LightningWallet", export_method: "ad-hoc", silent: true, clean: true, export_options: { compileBitcode: false }) 7 | 8 | upload_to_app_center(options[:app_center_api_token]) 9 | end 10 | 11 | def prepare_environment 12 | sh("rm -rf ~/Library/Developer/Xcode/Archives/**") 13 | sh("rm -rf ~/Library/MobileDevice/Provisioning\\ Profiles/**") 14 | 15 | reset_git_repo( 16 | force: true, 17 | exclude: "Carthage/" 18 | ) 19 | 20 | clear_derived_data 21 | 22 | increment_build_number_in_plist( 23 | build_number: ENV['BUILD_NUMBER'] 24 | ) 25 | 26 | cocoapods(repo_update: true) 27 | end 28 | 29 | def upload_to_app_center(api_token) 30 | appcenter_upload( 31 | api_token: api_token, 32 | owner_name: "Horizontal-Systems", 33 | owner_type: "organization", 34 | app_name: "Lightning-Wallet", 35 | notify_testers: true, 36 | release_notes: last_git_commit[:message] 37 | ) 38 | end 39 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-appcenter' 6 | gem 'fastlane-plugin-versioning' 7 | --------------------------------------------------------------------------------