diff --git a/BreadWallet.xcodeproj/project.pbxproj b/BreadWallet.xcodeproj/project.pbxproj index aa5ab8b6b..ded9df963 100644 --- a/BreadWallet.xcodeproj/project.pbxproj +++ b/BreadWallet.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 2204604E1B9F61E600D07B61 /* BREventManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2204604D1B9F61E600D07B61 /* BREventManager.m */; }; 2204604F1B9F61E600D07B61 /* BREventManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2204604D1B9F61E600D07B61 /* BREventManager.m */; }; 220460551BA0BEEE00D07B61 /* BREventConfirmView.m in Sources */ = {isa = PBXBuildFile; fileRef = 220460541BA0BEEE00D07B61 /* BREventConfirmView.m */; }; + 2284B63F1C7BD28F0012ECDF /* BRAWKeypad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F45A0A1C30EAB700B07A15 /* BRAWKeypad.swift */; }; 31D68B531C23B6C10030FAAA /* BreadWalletUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D68B521C23B6C10030FAAA /* BreadWalletUITests.swift */; }; 31D68B5B1C23B6FD0030FAAA /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D68B5A1C23B6FD0030FAAA /* SnapshotHelper.swift */; }; 751B2B5C1B68911700EA87FB /* BRTxHistoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 751B2B5B1B68911700EA87FB /* BRTxHistoryViewController.m */; }; @@ -69,9 +70,29 @@ 75F2E0D31BE9803900EAE861 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 75F2E0D21BE9803900EAE861 /* Launch Screen.storyboard */; }; 75F2E0D61BEAA6A400EAE861 /* BRTxMetadataEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 75F2E0D51BEAA6A400EAE861 /* BRTxMetadataEntity.m */; }; 84F68C8A1975E9E2002FC300 /* BRScanViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 84F68C891975E9E2002FC300 /* BRScanViewController.m */; }; + BA4A72671BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4A72661BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift */; }; BA54D3CE1B2EA74000C9CB28 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BA54D3CD1B2EA74000C9CB28 /* Media.xcassets */; }; + BA6645961BD924EB007A6BB1 /* BRAWTransactionRowControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA6645951BD924EB007A6BB1 /* BRAWTransactionRowControl.swift */; }; + BA7B53501BDA9A2800355E8D /* BRAppleWatchData.m in Sources */ = {isa = PBXBuildFile; fileRef = BA7B534F1BDA9A2800355E8D /* BRAppleWatchData.m */; }; + BA7B53511BDA9A2800355E8D /* BRAppleWatchData.m in Sources */ = {isa = PBXBuildFile; fileRef = BA7B534F1BDA9A2800355E8D /* BRAppleWatchData.m */; }; + BA7B53541BDAA0A800355E8D /* BRAppleWatchTransactionData.m in Sources */ = {isa = PBXBuildFile; fileRef = BA7B53531BDAA0A800355E8D /* BRAppleWatchTransactionData.m */; }; + BA7B53551BDAA0A800355E8D /* BRAppleWatchTransactionData.m in Sources */ = {isa = PBXBuildFile; fileRef = BA7B53531BDAA0A800355E8D /* BRAppleWatchTransactionData.m */; }; + BA7B535C1BDBC36700355E8D /* BRTransaction+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = BA7B535B1BDBC36700355E8D /* BRTransaction+Utils.m */; }; + BA7B53751BDBDB0500355E8D /* BRAWWatchDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7B53741BDBDB0500355E8D /* BRAWWatchDataManager.swift */; }; + BA7B537B1BDD1DCF00355E8D /* LoadingIndicator.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BA7B537A1BDD1DCF00355E8D /* LoadingIndicator.xcassets */; }; + BA913BE21BD57E4D005A7C0E /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BA913BE01BD57E4D005A7C0E /* Interface.storyboard */; }; + BA913BE41BD57E4D005A7C0E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BA913BE31BD57E4D005A7C0E /* Assets.xcassets */; }; + BA913BEB1BD57E4D005A7C0E /* WatchApp Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + BA913BF21BD57E4D005A7C0E /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA913BF11BD57E4D005A7C0E /* ExtensionDelegate.swift */; }; + BA913BFA1BD57E4D005A7C0E /* WatchApp.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = BA913BDE1BD57E4D005A7C0E /* WatchApp.app */; }; + BAA0E8CA1BD9273700DF4236 /* BRAWGlanceInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0E8C91BD9273700DF4236 /* BRAWGlanceInterfaceController.swift */; }; BAA4843C1B3EFFAF0075C749 /* UIImage+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = BAA4843B1B3EFFAF0075C749 /* UIImage+Utils.m */; }; BAA4843D1B3EFFAF0075C749 /* UIImage+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = BAA4843B1B3EFFAF0075C749 /* UIImage+Utils.m */; }; + BAA6E3EB1BD5C78500773205 /* BRAWRootInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA6E3EA1BD5C78500773205 /* BRAWRootInterfaceController.swift */; }; + BAA6E3EF1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA6E3EE1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift */; }; + BAA6E3F11BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA6E3F01BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift */; }; + BAC60E0E1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC60E0D1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.m */; }; + BAC7B6BE1BD9C29900165B84 /* BRPhoneWCSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7B6BD1BD9C29900165B84 /* BRPhoneWCSessionManager.m */; }; BAE12BF21B2DEE7F00895CC5 /* TodayExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; BAE12C061B2DEEF700895CC5 /* BRTodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BAE12C051B2DEEF700895CC5 /* BRTodayViewController.m */; }; /* End PBXBuildFile section */ @@ -98,6 +119,20 @@ remoteGlobalIDString = 75D5F3BD191EC270004AB296; remoteInfo = BreadWallet; }; + BA913BEC1BD57E4D005A7C0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 75D5F3B6191EC270004AB296 /* Project object */; + proxyType = 1; + remoteGlobalIDString = BA913BE91BD57E4D005A7C0E; + remoteInfo = "WatchApp Extension"; + }; + BA913BF81BD57E4D005A7C0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 75D5F3B6191EC270004AB296 /* Project object */; + proxyType = 1; + remoteGlobalIDString = BA913BDD1BD57E4D005A7C0E; + remoteInfo = WatchApp; + }; BAE12BF01B2DEE7F00895CC5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 75D5F3B6191EC270004AB296 /* Project object */; @@ -108,6 +143,28 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + BA913C001BD57E4E005A7C0E /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + BA913BEB1BD57E4D005A7C0E /* WatchApp Extension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + BA913C021BD57E4E005A7C0E /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + BA913BFA1BD57E4D005A7C0E /* WatchApp.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; BAE12BF71B2DEE7F00895CC5 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -126,6 +183,10 @@ 2204604D1B9F61E600D07B61 /* BREventManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BREventManager.m; sourceTree = ""; }; 220460531BA0BEEE00D07B61 /* BREventConfirmView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BREventConfirmView.h; sourceTree = ""; }; 220460541BA0BEEE00D07B61 /* BREventConfirmView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BREventConfirmView.m; sourceTree = ""; }; + 22F45A0A1C30EAB700B07A15 /* BRAWKeypad.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWKeypad.swift; sourceTree = ""; }; + 22F45A0C1C3326CC00B07A15 /* BRUserDefaultsSwitchCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRUserDefaultsSwitchCell.h; sourceTree = ""; }; + 22F45A0D1C3326CC00B07A15 /* BRUserDefaultsSwitchCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRUserDefaultsSwitchCell.m; sourceTree = ""; }; + 22F45A0F1C3329EB00B07A15 /* BRUserDefaultsConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BRUserDefaultsConstants.h; sourceTree = ""; }; 31D68B501C23B6C10030FAAA /* BreadWalletUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BreadWalletUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 31D68B521C23B6C10030FAAA /* BreadWalletUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadWalletUITests.swift; sourceTree = ""; }; 31D68B541C23B6C10030FAAA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -336,10 +397,40 @@ 75FD2FB51BFAB97A00D56BBA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/BREventConfirmView.strings; sourceTree = ""; }; 84F68C881975E9E2002FC300 /* BRScanViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRScanViewController.h; sourceTree = ""; }; 84F68C891975E9E2002FC300 /* BRScanViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRScanViewController.m; sourceTree = ""; }; + BA4A72661BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWWeakTimerTarget.swift; sourceTree = ""; }; BA4EECDF1B351AE200D443A3 /* BRAppGroupConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRAppGroupConstants.h; sourceTree = ""; }; BA54D3CD1B2EA74000C9CB28 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + BA6645951BD924EB007A6BB1 /* BRAWTransactionRowControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWTransactionRowControl.swift; sourceTree = ""; }; + BA7B534E1BDA9A2800355E8D /* BRAppleWatchData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRAppleWatchData.h; sourceTree = ""; }; + BA7B534F1BDA9A2800355E8D /* BRAppleWatchData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRAppleWatchData.m; sourceTree = ""; }; + BA7B53521BDAA0A800355E8D /* BRAppleWatchTransactionData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRAppleWatchTransactionData.h; sourceTree = ""; }; + BA7B53531BDAA0A800355E8D /* BRAppleWatchTransactionData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRAppleWatchTransactionData.m; sourceTree = ""; }; + BA7B535A1BDBC36700355E8D /* BRTransaction+Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BRTransaction+Utils.h"; sourceTree = ""; }; + BA7B535B1BDBC36700355E8D /* BRTransaction+Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BRTransaction+Utils.m"; sourceTree = ""; }; + BA7B53651BDBD04F00355E8D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + BA7B53671BDBD07F00355E8D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + BA7B53691BDBD08A00355E8D /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/ImageIO.framework; sourceTree = DEVELOPER_DIR; }; + BA7B53741BDBDB0500355E8D /* BRAWWatchDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWWatchDataManager.swift; sourceTree = ""; }; + BA7B537A1BDD1DCF00355E8D /* LoadingIndicator.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = LoadingIndicator.xcassets; sourceTree = ""; }; + BA913BDE1BD57E4D005A7C0E /* WatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BA913BE11BD57E4D005A7C0E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; + BA913BE31BD57E4D005A7C0E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BA913BE51BD57E4D005A7C0E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "WatchApp Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + BA913BF11BD57E4D005A7C0E /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = ""; }; + BA913BF71BD57E4D005A7C0E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BAA0E8C91BD9273700DF4236 /* BRAWGlanceInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWGlanceInterfaceController.swift; sourceTree = ""; }; BAA4843A1B3EFFAF0075C749 /* UIImage+Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Utils.h"; sourceTree = ""; }; BAA4843B1B3EFFAF0075C749 /* UIImage+Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Utils.m"; sourceTree = ""; }; + BAA6E3EA1BD5C78500773205 /* BRAWRootInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWRootInterfaceController.swift; sourceTree = ""; }; + BAA6E3EE1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWBalanceInterfaceController.swift; sourceTree = ""; }; + BAA6E3F01BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BRAWReceiveMoneyInterfaceController.swift; sourceTree = ""; }; + BAC60E0C1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BRAppleWatchTransactionData+Factory.h"; sourceTree = ""; }; + BAC60E0D1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BRAppleWatchTransactionData+Factory.m"; sourceTree = ""; }; + BAC7B6BC1BD9C29900165B84 /* BRPhoneWCSessionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRPhoneWCSessionManager.h; sourceTree = ""; }; + BAC7B6BD1BD9C29900165B84 /* BRPhoneWCSessionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRPhoneWCSessionManager.m; sourceTree = ""; }; + BAC7B6C41BD9C9B600165B84 /* WatchApp Extension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WatchApp Extension-Bridging-Header.h"; sourceTree = ""; }; + BAC7B6C81BD9CA8400165B84 /* BRAppleWatchSharedConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRAppleWatchSharedConstants.h; sourceTree = ""; }; BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TodayExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; BAE12BE61B2DEE7F00895CC5 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; BAE12BEA1B2DEE7F00895CC5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -369,6 +460,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BA913BE71BD57E4D005A7C0E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; BAE12BE21B2DEE7F00895CC5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -405,6 +503,8 @@ 755FC9EF1BF1DF1B00164ABB /* BREventConfirmView.xib */, 220460531BA0BEEE00D07B61 /* BREventConfirmView.h */, 220460541BA0BEEE00D07B61 /* BREventConfirmView.m */, + 22F45A0C1C3326CC00B07A15 /* BRUserDefaultsSwitchCell.h */, + 22F45A0D1C3326CC00B07A15 /* BRUserDefaultsSwitchCell.m */, ); name = Views; sourceTree = ""; @@ -481,6 +581,8 @@ 752E28E219234DA200DB5A3C /* NSManagedObject+Sugar.m */, BAA4843A1B3EFFAF0075C749 /* UIImage+Utils.h */, BAA4843B1B3EFFAF0075C749 /* UIImage+Utils.m */, + BA7B535A1BDBC36700355E8D /* BRTransaction+Utils.h */, + BA7B535B1BDBC36700355E8D /* BRTransaction+Utils.m */, ); name = Categories; sourceTree = ""; @@ -586,6 +688,8 @@ BAE12BE81B2DEE7F00895CC5 /* TodayExtension */, 75D5F3EC191EC270004AB296 /* BreadWalletTests */, 31D68B511C23B6C10030FAAA /* BreadWalletUITests */, + BA913BDF1BD57E4D005A7C0E /* WatchApp */, + BA913BEE1BD57E4D005A7C0E /* WatchApp Extension */, 75D5F3C0191EC270004AB296 /* Frameworks */, 75D5F3BF191EC270004AB296 /* Products */, ); @@ -598,6 +702,8 @@ 75D5F3E5191EC270004AB296 /* BreadWalletTests.xctest */, BAE12BE51B2DEE7F00895CC5 /* TodayExtension.appex */, 31D68B501C23B6C10030FAAA /* BreadWalletUITests.xctest */, + BA913BDE1BD57E4D005A7C0E /* WatchApp.app */, + BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */, ); name = Products; sourceTree = ""; @@ -606,6 +712,9 @@ isa = PBXGroup; children = ( 75F2E0B61BE2D5F000EAE861 /* Accelerate.framework */, + BA7B53691BDBD08A00355E8D /* ImageIO.framework */, + BA7B53671BDBD07F00355E8D /* UIKit.framework */, + BA7B53651BDBD04F00355E8D /* CoreGraphics.framework */, BAE12BE61B2DEE7F00895CC5 /* NotificationCenter.framework */, ); name = Frameworks; @@ -614,6 +723,7 @@ 75D5F3C7191EC270004AB296 /* BreadWallet */ = { isa = PBXGroup; children = ( + BAC7B6BF1BD9C2B500165B84 /* AppleWatch */, 752E289C19234A4700DB5A3C /* Models */, 752E289B19234A3F00DB5A3C /* Views */, 75BA5D02192296010040304C /* Controllers */, @@ -621,6 +731,7 @@ 75D5F3D0191EC270004AB296 /* BRAppDelegate.h */, 75D5F3D1191EC270004AB296 /* BRAppDelegate.m */, BA4EECDF1B351AE200D443A3 /* BRAppGroupConstants.h */, + 22F45A0F1C3329EB00B07A15 /* BRUserDefaultsConstants.h */, 75D5F3DF191EC270004AB296 /* Images.xcassets */, 75D5F3DC191EC270004AB296 /* Main.storyboard */, 75F2E0D21BE9803900EAE861 /* Launch Screen.storyboard */, @@ -691,6 +802,51 @@ path = schnorr; sourceTree = ""; }; + BA913BDF1BD57E4D005A7C0E /* WatchApp */ = { + isa = PBXGroup; + children = ( + BA913BE01BD57E4D005A7C0E /* Interface.storyboard */, + BA913BE31BD57E4D005A7C0E /* Assets.xcassets */, + BA7B537A1BDD1DCF00355E8D /* LoadingIndicator.xcassets */, + BA913BE51BD57E4D005A7C0E /* Info.plist */, + ); + path = WatchApp; + sourceTree = ""; + }; + BA913BEE1BD57E4D005A7C0E /* WatchApp Extension */ = { + isa = PBXGroup; + children = ( + BAC7B6C41BD9C9B600165B84 /* WatchApp Extension-Bridging-Header.h */, + BA913BF11BD57E4D005A7C0E /* ExtensionDelegate.swift */, + BAA6E3EA1BD5C78500773205 /* BRAWRootInterfaceController.swift */, + BAA6E3EE1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift */, + BAA6E3F01BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift */, + BA6645951BD924EB007A6BB1 /* BRAWTransactionRowControl.swift */, + BAA0E8C91BD9273700DF4236 /* BRAWGlanceInterfaceController.swift */, + BA7B53741BDBDB0500355E8D /* BRAWWatchDataManager.swift */, + BA4A72661BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift */, + BA913BF71BD57E4D005A7C0E /* Info.plist */, + 22F45A0A1C30EAB700B07A15 /* BRAWKeypad.swift */, + ); + path = "WatchApp Extension"; + sourceTree = ""; + }; + BAC7B6BF1BD9C2B500165B84 /* AppleWatch */ = { + isa = PBXGroup; + children = ( + BAC7B6C81BD9CA8400165B84 /* BRAppleWatchSharedConstants.h */, + BAC7B6BC1BD9C29900165B84 /* BRPhoneWCSessionManager.h */, + BAC7B6BD1BD9C29900165B84 /* BRPhoneWCSessionManager.m */, + BA7B534E1BDA9A2800355E8D /* BRAppleWatchData.h */, + BA7B534F1BDA9A2800355E8D /* BRAppleWatchData.m */, + BA7B53521BDAA0A800355E8D /* BRAppleWatchTransactionData.h */, + BA7B53531BDAA0A800355E8D /* BRAppleWatchTransactionData.m */, + BAC60E0C1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.h */, + BAC60E0D1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.m */, + ); + name = AppleWatch; + sourceTree = ""; + }; BAE12BE81B2DEE7F00895CC5 /* TodayExtension */ = { isa = PBXGroup; children = ( @@ -758,12 +914,14 @@ 75D5F3BB191EC270004AB296 /* Frameworks */, 75D5F3BC191EC270004AB296 /* Resources */, BAE12BF71B2DEE7F00895CC5 /* Embed App Extensions */, + BA913C021BD57E4E005A7C0E /* Embed Watch Content */, ); buildRules = ( ); dependencies = ( 757FC2531ADA30D100A1FDF7 /* PBXTargetDependency */, BAE12BF11B2DEE7F00895CC5 /* PBXTargetDependency */, + BA913BF91BD57E4D005A7C0E /* PBXTargetDependency */, ); name = breadwallet; productName = BreadWallet; @@ -788,6 +946,40 @@ productReference = 75D5F3E5191EC270004AB296 /* BreadWalletTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + BA913BDD1BD57E4D005A7C0E /* WatchApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = BA913C011BD57E4E005A7C0E /* Build configuration list for PBXNativeTarget "WatchApp" */; + buildPhases = ( + BA913BDC1BD57E4D005A7C0E /* Resources */, + BA913C001BD57E4E005A7C0E /* Embed App Extensions */, + ); + buildRules = ( + ); + dependencies = ( + BA913BED1BD57E4D005A7C0E /* PBXTargetDependency */, + ); + name = WatchApp; + productName = WatchApp; + productReference = BA913BDE1BD57E4D005A7C0E /* WatchApp.app */; + productType = "com.apple.product-type.application.watchapp2"; + }; + BA913BE91BD57E4D005A7C0E /* WatchApp Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = BA913BFF1BD57E4E005A7C0E /* Build configuration list for PBXNativeTarget "WatchApp Extension" */; + buildPhases = ( + BA913BE61BD57E4D005A7C0E /* Sources */, + BA913BE71BD57E4D005A7C0E /* Frameworks */, + BA913BE81BD57E4D005A7C0E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "WatchApp Extension"; + productName = "WatchApp Extension"; + productReference = BA913BEA1BD57E4D005A7C0E /* WatchApp Extension.appex */; + productType = "com.apple.product-type.watchkit2-extension"; + }; BAE12BE41B2DEE7F00895CC5 /* TodayExtension */ = { isa = PBXNativeTarget; buildConfigurationList = BAE12BF61B2DEE7F00895CC5 /* Build configuration list for PBXNativeTarget "TodayExtension" */; @@ -841,6 +1033,13 @@ 75D5F3E4191EC270004AB296 = { TestTargetID = 75D5F3BD191EC270004AB296; }; + BA913BDD1BD57E4D005A7C0E = { + CreatedOnToolsVersion = 7.0.1; + DevelopmentTeam = 4R7S6N88W9; + }; + BA913BE91BD57E4D005A7C0E = { + CreatedOnToolsVersion = 7.0.1; + }; BAE12BE41B2DEE7F00895CC5 = { CreatedOnToolsVersion = 6.3.2; DevelopmentTeam = 4R7S6N88W9; @@ -888,6 +1087,8 @@ 75D5F3E4191EC270004AB296 /* BreadWalletTests */, 31D68B4F1C23B6C10030FAAA /* BreadWalletUITests */, 75001D3D1A1EE89500585F55 /* submodules */, + BA913BDD1BD57E4D005A7C0E /* WatchApp */, + BA913BE91BD57E4D005A7C0E /* WatchApp Extension */, ); }; /* End PBXProject section */ @@ -922,6 +1123,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BA913BDC1BD57E4D005A7C0E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BA7B537B1BDD1DCF00355E8D /* LoadingIndicator.xcassets in Resources */, + BA913BE41BD57E4D005A7C0E /* Assets.xcassets in Resources */, + BA913BE21BD57E4D005A7C0E /* Interface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BA913BE81BD57E4D005A7C0E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; BAE12BE31B2DEE7F00895CC5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -951,11 +1169,13 @@ 752E28AA19234AE300DB5A3C /* BRAddressEntity.m in Sources */, 75AA5F55194143D5003F23BD /* BRBouncyBurgerButton.m in Sources */, 752E28AB19234AE300DB5A3C /* BRMerkleBlockEntity.m in Sources */, + BA7B53501BDA9A2800355E8D /* BRAppleWatchData.m in Sources */, 752E28AC19234AE300DB5A3C /* BRPeerEntity.m in Sources */, 752E28AD19234AE300DB5A3C /* BRTransactionEntity.m in Sources */, 752E28AE19234AE300DB5A3C /* BRTxInputEntity.m in Sources */, 752E28AF19234AE300DB5A3C /* BRTxOutputEntity.m in Sources */, BAA4843C1B3EFFAF0075C749 /* UIImage+Utils.m in Sources */, + BA7B535C1BDBC36700355E8D /* BRTransaction+Utils.m in Sources */, 758DBD0E1980634900389E2A /* BRTxDetailViewController.m in Sources */, 7548CF3719556FD700EF827F /* BRCopyLabel.m in Sources */, 752E28D119234C3600DB5A3C /* BRKey.m in Sources */, @@ -965,6 +1185,7 @@ 220460551BA0BEEE00D07B61 /* BREventConfirmView.m in Sources */, 752E28D919234C3600DB5A3C /* BRTransaction.m in Sources */, 752E28CE19234C3600DB5A3C /* BRBIP32Sequence.m in Sources */, + BAC7B6BE1BD9C29900165B84 /* BRPhoneWCSessionManager.m in Sources */, 752E28CF19234C3600DB5A3C /* BRBIP39Mnemonic.m in Sources */, 752E28DA19234C3600DB5A3C /* BRWallet.m in Sources */, 752E28DB19234C3600DB5A3C /* BRWalletManager.m in Sources */, @@ -973,6 +1194,7 @@ 752E28D619234C3600DB5A3C /* BRPeer.m in Sources */, 752E28D819234C3600DB5A3C /* BRPeerManager.m in Sources */, 752E28D419234C3600DB5A3C /* BRPaymentProtocol.m in Sources */, + BAC60E0E1BDD25F100721C3C /* BRAppleWatchTransactionData+Factory.m in Sources */, 752E28D519234C3600DB5A3C /* BRPaymentRequest.m in Sources */, 7525B619192453840041C0F2 /* BRNavigationBar.m in Sources */, 75F2E0D61BEAA6A400EAE861 /* BRTxMetadataEntity.m in Sources */, @@ -984,6 +1206,7 @@ 757BE7E9192D4E880061CB20 /* BRSeedViewController.m in Sources */, 752E28E719234DA200DB5A3C /* NSData+Bitcoin.m in Sources */, 752E28E919234DA200DB5A3C /* NSManagedObject+Sugar.m in Sources */, + BA7B53541BDAA0A800355E8D /* BRAppleWatchTransactionData.m in Sources */, 751B2B5C1B68911700EA87FB /* BRTxHistoryViewController.m in Sources */, 752E28EA19234DA200DB5A3C /* NSMutableData+Bitcoin.m in Sources */, 752E28EB19234DA200DB5A3C /* NSString+Bitcoin.m in Sources */, @@ -1005,6 +1228,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BA913BE61BD57E4D005A7C0E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2284B63F1C7BD28F0012ECDF /* BRAWKeypad.swift in Sources */, + BA7B53551BDAA0A800355E8D /* BRAppleWatchTransactionData.m in Sources */, + BA7B53751BDBDB0500355E8D /* BRAWWatchDataManager.swift in Sources */, + BAA6E3F11BD5CA5900773205 /* BRAWReceiveMoneyInterfaceController.swift in Sources */, + BAA0E8CA1BD9273700DF4236 /* BRAWGlanceInterfaceController.swift in Sources */, + BA4A72671BE11AFA00E39C01 /* BRAWWeakTimerTarget.swift in Sources */, + BA7B53511BDA9A2800355E8D /* BRAppleWatchData.m in Sources */, + BAA6E3EF1BD5CA0E00773205 /* BRAWBalanceInterfaceController.swift in Sources */, + BA913BF21BD57E4D005A7C0E /* ExtensionDelegate.swift in Sources */, + BA6645961BD924EB007A6BB1 /* BRAWTransactionRowControl.swift in Sources */, + BAA6E3EB1BD5C78500773205 /* BRAWRootInterfaceController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; BAE12BE11B2DEE7F00895CC5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1034,6 +1275,16 @@ target = 75D5F3BD191EC270004AB296 /* breadwallet */; targetProxy = 75D5F3EA191EC270004AB296 /* PBXContainerItemProxy */; }; + BA913BED1BD57E4D005A7C0E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BA913BE91BD57E4D005A7C0E /* WatchApp Extension */; + targetProxy = BA913BEC1BD57E4D005A7C0E /* PBXContainerItemProxy */; + }; + BA913BF91BD57E4D005A7C0E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BA913BDD1BD57E4D005A7C0E /* WatchApp */; + targetProxy = BA913BF81BD57E4D005A7C0E /* PBXContainerItemProxy */; + }; BAE12BF11B2DEE7F00895CC5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BAE12BE41B2DEE7F00895CC5 /* TodayExtension */; @@ -1147,6 +1398,14 @@ name = Main.storyboard; sourceTree = ""; }; + BA913BE01BD57E4D005A7C0E /* Interface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + BA913BE11BD57E4D005A7C0E /* Base */, + ); + name = Interface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -1355,6 +1614,80 @@ }; name = Release; }; + BA913BFB1BD57E4E005A7C0E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + IBSC_MODULE = WatchApp_Extension; + INFOPLIST_FILE = WatchApp/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.voisine.breadwallet.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + BA913BFC1BD57E4E005A7C0E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + IBSC_MODULE = WatchApp_Extension; + INFOPLIST_FILE = WatchApp/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.voisine.breadwallet.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; + BA913BFD1BD57E4E005A7C0E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + INFOPLIST_FILE = "WatchApp Extension/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.voisine.breadwallet.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "WatchApp Extension/WatchApp Extension-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + BA913BFE1BD57E4E005A7C0E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + INFOPLIST_FILE = "WatchApp Extension/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.voisine.breadwallet.watchkitapp.watchkitextension; + PRODUCT_NAME = "${TARGET_NAME}"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "WatchApp Extension/WatchApp Extension-Bridging-Header.h"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; BAE12BF31B2DEE7F00895CC5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1454,6 +1787,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + BA913BFF1BD57E4E005A7C0E /* Build configuration list for PBXNativeTarget "WatchApp Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BA913BFD1BD57E4E005A7C0E /* Debug */, + BA913BFE1BD57E4E005A7C0E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BA913C011BD57E4E005A7C0E /* Build configuration list for PBXNativeTarget "WatchApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BA913BFB1BD57E4E005A7C0E /* Debug */, + BA913BFC1BD57E4E005A7C0E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; BAE12BF61B2DEE7F00895CC5 /* Build configuration list for PBXNativeTarget "TodayExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/BreadWallet/BRAppDelegate.m b/BreadWallet/BRAppDelegate.m index a6fa1ef23..b74dd84d9 100644 --- a/BreadWallet/BRAppDelegate.m +++ b/BreadWallet/BRAppDelegate.m @@ -27,6 +27,7 @@ #import "BRPeerManager.h" #import "BRWalletManager.h" #import "BREventManager.h" +#import "BRPhoneWCSessionManager.h" #if BITCOIN_TESTNET #pragma message "testnet build" @@ -36,6 +37,16 @@ #pragma message "snapshot build" #endif + +@interface BRAppDelegate () +// balance notification properties - +// the nsnotificationcenter observer for wallet balance +@property id balanceNotificationObserver; +// the most recent balance as received by notification +@property uint64_t balanceNotificationBalance; +@end + + @implementation BRAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions @@ -77,6 +88,14 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( //TODO: implement importing of private keys split with shamir's secret sharing: // https://github.com/cetuscetus/btctool/blob/bip/bip-xxxx.mediawiki + // start WCSession manager + [BRPhoneWCSessionManager sharedInstance]; + + // observe balance and create notifications + [self setupBalanceNotification:application]; + + [self setupPreferenceDefaults]; + return YES; } @@ -152,6 +171,7 @@ - (void)application:(UIApplication *)application [UIApplication sharedApplication].applicationIconBadgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber + 1; } + NSLog(@"background got new balance notification %@ %llu -> %llu", note, balance, manager.wallet.balance); balance = manager.wallet.balance; }]; @@ -180,6 +200,53 @@ - (void)application:(UIApplication *)application [[BREventManager sharedEventManager] sync]; } +- (void)setupBalanceNotification:(UIApplication *)application +{ + BRWalletManager *manager = [BRWalletManager sharedInstance]; + void (^balanceUpdate)(NSNotification * _Nonnull) = ^(NSNotification *_Nonnull _) { + if (self.balanceNotificationBalance < manager.wallet.balance) { + NSString *noteText = [NSString stringWithFormat: + NSLocalizedString(@"received %@ (%@)", nil), + [manager stringForAmount:manager.wallet.balance - self.balanceNotificationBalance], + [manager localCurrencyStringForAmount: + manager.wallet.balance - self.balanceNotificationBalance]]; + + // send a local notification if in the background + BOOL send = [[NSUserDefaults standardUserDefaults] boolForKey:USER_DEFAULTS_LOCAL_NOTIFICATIONS_KEY]; + NSLog(@"local notifications enabled=%d", send); + if ((application.applicationState == UIApplicationStateBackground + || application.applicationState == UIApplicationStateInactive) && send) { + UILocalNotification *note = [[UILocalNotification alloc] init]; + note.alertBody = noteText; + note.soundName = @"coinflip"; + [[UIApplication sharedApplication] presentLocalNotificationNow:note]; + NSLog(@"sent local notification %@", note); + } + // send a custom notification to the watch if the watch app is up + [[BRPhoneWCSessionManager sharedInstance] notifyTransactionString:noteText]; + } + self.balanceNotificationBalance = manager.wallet.balance; + }; + + self.balanceNotificationObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:BRWalletBalanceChangedNotification + object:nil + queue:nil + usingBlock:balanceUpdate]; + self.balanceNotificationBalance = manager.wallet.balance; +} + +- (void)setupPreferenceDefaults { + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + + // turn on local notifications by default + if (![defs boolForKey:USER_DEFAULTS_LOCAL_NOTIFICATIONS_SWITCH_KEY]) { + NSLog(@"enabling local notifications by default"); + [defs setBool:true forKey:USER_DEFAULTS_LOCAL_NOTIFICATIONS_SWITCH_KEY]; + [defs setBool:true forKey:USER_DEFAULTS_LOCAL_NOTIFICATIONS_KEY]; + } +} + - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler diff --git a/BreadWallet/BRAppleWatchData.h b/BreadWallet/BRAppleWatchData.h new file mode 100644 index 000000000..c2b0edbb2 --- /dev/null +++ b/BreadWallet/BRAppleWatchData.h @@ -0,0 +1,39 @@ +// +// BRAppleWatchData.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "BRAppleWatchTransactionData.h" +#import + +@interface BRAppleWatchData : NSObject +@property (nonatomic, strong) NSString *balance; +@property (nonatomic, strong) NSString *balanceInLocalCurrency; +@property (nonatomic, strong) NSString *receiveMoneyAddress; +@property (nonatomic, strong) NSString *lastestTransction; +// There is no cifilter in watchOS 2, so we have to pass image over. +@property (nonatomic, strong) UIImage *receiveMoneyQRCodeImage; +@property (nonatomic, strong) NSArray *transactions; +@property (nonatomic) BOOL hasWallet; +@end diff --git a/BreadWallet/BRAppleWatchData.m b/BreadWallet/BRAppleWatchData.m new file mode 100644 index 000000000..f52568323 --- /dev/null +++ b/BreadWallet/BRAppleWatchData.m @@ -0,0 +1,80 @@ +// +// BRAppleWatchData.m +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRAppleWatchData.h" + +#define AW_DATA_BALANCE_KEY @"AW_DATA_BALANCE_KEY" +#define AW_DATA_BALANCE_LOCAL_KEY @"AW_DATA_BALANCE_LOCAL_KEY" +#define AW_DATA_RECEIVE_MONEY_ADDRESS @"AW_DATA_RECEIVE_MONEY_ADDRESS" +#define AW_DATA_RECEIVE_MONEY_QR_CODE @"AW_DATA_RECEIVE_MONEY_QR_CODE" +#define AW_DATA_TRANSACTIONS @"AW_DATA_TRANSACTIONS" +#define AW_DATA_LATEST_TRANSACTION @"AW_DATA_LATEST_TRANSACTION" +#define AW_DATA_HAS_WALLET @"AW_DATA_HAS_WALLET" + + +@implementation BRAppleWatchData +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + _balance = [decoder decodeObjectForKey:AW_DATA_BALANCE_KEY]; + _balanceInLocalCurrency = [decoder decodeObjectForKey:AW_DATA_BALANCE_LOCAL_KEY]; + _receiveMoneyAddress = [decoder decodeObjectForKey:AW_DATA_RECEIVE_MONEY_ADDRESS]; + _receiveMoneyQRCodeImage = [decoder decodeObjectForKey:AW_DATA_RECEIVE_MONEY_QR_CODE]; + _lastestTransction = [decoder decodeObjectForKey:AW_DATA_LATEST_TRANSACTION]; + _transactions = [decoder decodeObjectForKey:AW_DATA_TRANSACTIONS]; + _hasWallet = [[decoder decodeObjectForKey:AW_DATA_HAS_WALLET] boolValue]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + if (_balance) [encoder encodeObject:_balance forKey:AW_DATA_BALANCE_KEY]; + if (_balanceInLocalCurrency) [encoder encodeObject:_balanceInLocalCurrency forKey:AW_DATA_BALANCE_LOCAL_KEY]; + if (_receiveMoneyAddress) [encoder encodeObject:_receiveMoneyAddress forKey:AW_DATA_RECEIVE_MONEY_ADDRESS]; + if (_receiveMoneyQRCodeImage) [encoder encodeObject:_receiveMoneyQRCodeImage forKey:AW_DATA_RECEIVE_MONEY_QR_CODE]; + if (_lastestTransction) [encoder encodeObject:_lastestTransction forKey:AW_DATA_LATEST_TRANSACTION]; + if (_transactions) [encoder encodeObject:_transactions forKey:AW_DATA_TRANSACTIONS]; + if (_hasWallet) [encoder encodeObject:@(_hasWallet) forKey:AW_DATA_HAS_WALLET]; +} + +- (NSString*)description { + return [NSString stringWithFormat:@"%@,%@,%@,%@,%@,image size:%@", + _balance, _balanceInLocalCurrency, _receiveMoneyAddress, @([_transactions count]), _lastestTransction, + @(_receiveMoneyQRCodeImage.size.height)]; +} + +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[self class]]) { + BRAppleWatchData *otherAppleWatchData = object; + return [self.balance isEqual:otherAppleWatchData.balance] && + [self.balanceInLocalCurrency isEqual:otherAppleWatchData.balanceInLocalCurrency] && + [self.receiveMoneyAddress isEqual:otherAppleWatchData.receiveMoneyAddress] && + [self.lastestTransction isEqual:otherAppleWatchData.lastestTransction] && + [self.transactions isEqual:otherAppleWatchData.transactions] && + self.hasWallet == otherAppleWatchData.hasWallet; + } else { + return NO; + } +} +@end diff --git a/BreadWallet/BRAppleWatchSharedConstants.h b/BreadWallet/BRAppleWatchSharedConstants.h new file mode 100644 index 000000000..30bf9622a --- /dev/null +++ b/BreadWallet/BRAppleWatchSharedConstants.h @@ -0,0 +1,54 @@ +// +// BRAppleWatchSharedConstants.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRAppleWatchData.h" + +#define AW_SESSION_RESPONSE_KEY @"AW_SESSION_RESPONSE_KEY" +#define AW_SESSION_REQUEST_TYPE @"AW_SESSION_REQUEST_TYPE" +#define AW_SESSION_QR_CODE_BITS_KEY @"AW_QR_CODE_BITS_KEY" + +#define AW_SESSION_REQUEST_DATA_TYPE_KEY @"AW_SESSION_REQUEST_DATA_TYPE_KEY" + +#define AW_APPLICATION_CONTEXT_KEY @"AW_APPLICATION_CONTEXT_KEY" +#define AW_QR_CODE_BITS_KEY @"AW_QR_CODE_BITS_KEY" + +#define AW_PHONE_NOTIFICATION_KEY @"AW_PHONE_NOTIFICATION_KEY" +#define AW_PHONE_NOTIFICATION_TYPE_KEY @"AW_PHONE_NOTIFICATION_TYPE_KEY" + + +typedef enum { + AWSessionRquestDataTypeApplicationContextData, + AWSessionRquestDataTypeQRCodeBits +} AWSessionRquestDataType; + +typedef enum { + AWSessionRquestTypeDataUpdateNotification, + AWSessionRquestTypeFetchData, + AWSessionRquestTypeQRCodeBits +} AWSessionRquestType; + +typedef enum { + AWPhoneNotificationTypeTxReceive +} AWPhoneNotificationType; diff --git a/BreadWallet/BRAppleWatchTransactionData+Factory.h b/BreadWallet/BRAppleWatchTransactionData+Factory.h new file mode 100644 index 000000000..a0cc8f2fb --- /dev/null +++ b/BreadWallet/BRAppleWatchTransactionData+Factory.h @@ -0,0 +1,31 @@ +// +// BRAppleWatchTransactionData+Factory.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRAppleWatchTransactionData.h" +#import "BRTransaction.h" + +@interface BRAppleWatchTransactionData (Factory) ++ (instancetype)appleWatchTransactionDataFrom:(BRTransaction*)transaction; +@end diff --git a/BreadWallet/BRAppleWatchTransactionData+Factory.m b/BreadWallet/BRAppleWatchTransactionData+Factory.m new file mode 100644 index 000000000..7acd308c8 --- /dev/null +++ b/BreadWallet/BRAppleWatchTransactionData+Factory.m @@ -0,0 +1,54 @@ +// +// BRAppleWatchTransactionData+Factory.m +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRAppleWatchTransactionData+Factory.h" +#import "BRTransaction+Utils.h" + +@implementation BRAppleWatchTransactionData (Factory) ++ (instancetype)appleWatchTransactionDataFrom:(BRTransaction*)transaction { + BRAppleWatchTransactionData *appleWatchTransactionData; + if (transaction) { + appleWatchTransactionData = [[BRAppleWatchTransactionData alloc] init]; + appleWatchTransactionData.amountText = transaction.amountText; + appleWatchTransactionData.amountTextInLocalCurrency = transaction.localCurrencyTextForAmount; + appleWatchTransactionData.dateText = transaction.dateText; + switch (transaction.transactionType) { + case BRTransactionTypeSent: + appleWatchTransactionData.type = BRAWTransactionTypeSent; + break; + case BRTransactionTypeReceive: + appleWatchTransactionData.type = BRAWTransactionTypeReceive; + break; + case BRTransactionTypeMove: + appleWatchTransactionData.type = BRAWTransactionTypeMove; + break; + case BRTransactionTypeInvalid: + appleWatchTransactionData.type = BRAWTransactionTypeInvalid; + break; + } + } + return appleWatchTransactionData; +} +@end diff --git a/BreadWallet/BRAppleWatchTransactionData.h b/BreadWallet/BRAppleWatchTransactionData.h new file mode 100644 index 000000000..f82e5f4b5 --- /dev/null +++ b/BreadWallet/BRAppleWatchTransactionData.h @@ -0,0 +1,40 @@ +// +// BRAppleWatchTransactionData.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +typedef enum : NSInteger { + BRAWTransactionTypeSent, + BRAWTransactionTypeReceive, + BRAWTransactionTypeMove, + BRAWTransactionTypeInvalid +} BRAWTransactionType; + +@interface BRAppleWatchTransactionData : NSObject +@property (nonatomic, strong) NSString *amountText; +@property (nonatomic, strong) NSString *amountTextInLocalCurrency; +@property (nonatomic, strong) NSString *dateText; +@property (nonatomic) BRAWTransactionType type; +@end diff --git a/BreadWallet/BRAppleWatchTransactionData.m b/BreadWallet/BRAppleWatchTransactionData.m new file mode 100644 index 000000000..323e4cc62 --- /dev/null +++ b/BreadWallet/BRAppleWatchTransactionData.m @@ -0,0 +1,64 @@ +// +// BRAppleWatchTransactionData.m +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRAppleWatchTransactionData.h" + +#define AW_TRANSACTION_DATA_AMOUNT_KEY @"AW_TRANSACTION_DATA_AMOUNT_KEY" +#define AW_TRANSACTION_DATA_AMOUNT_IN_LOCAL_CURRENCY_KEY @"AW_TRANSACTION_DATA_AMOUNT_IN_LOCAL_CURRENCY_KEY" +#define AW_TRANSACTION_DATA_DATE_KEY @"AW_TRANSACTION_DATA_DATE_KEY" +#define AW_TRANSACTION_DATA_TYPE_KEY @"AW_TRANSACTION_DATA_TYPE_KEY" + +@implementation BRAppleWatchTransactionData +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + _amountText = [decoder decodeObjectForKey:AW_TRANSACTION_DATA_AMOUNT_KEY]; + _amountTextInLocalCurrency = [decoder decodeObjectForKey:AW_TRANSACTION_DATA_AMOUNT_IN_LOCAL_CURRENCY_KEY]; + _dateText = [decoder decodeObjectForKey:AW_TRANSACTION_DATA_DATE_KEY]; + _type = [[decoder decodeObjectForKey:AW_TRANSACTION_DATA_TYPE_KEY] intValue]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + if (_amountText) [encoder encodeObject:_amountText forKey:AW_TRANSACTION_DATA_AMOUNT_KEY]; + if (_amountTextInLocalCurrency) [encoder encodeObject:_amountTextInLocalCurrency + forKey:AW_TRANSACTION_DATA_AMOUNT_IN_LOCAL_CURRENCY_KEY]; + if (_dateText) [encoder encodeObject:_dateText forKey:AW_TRANSACTION_DATA_DATE_KEY]; + if (_type) [encoder encodeObject:@(_type) forKey:AW_TRANSACTION_DATA_TYPE_KEY]; +} + +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[self class]]) { + BRAppleWatchTransactionData *otherTransaction = object; + return [self.amountText isEqual:otherTransaction.amountText] && + [self.amountTextInLocalCurrency isEqual:otherTransaction.amountTextInLocalCurrency] && + [self.dateText isEqual:otherTransaction.dateText] && + self.type == otherTransaction.type; + } else { + return NO; + } +} + +@end diff --git a/BreadWallet/BRPeerManager.m b/BreadWallet/BRPeerManager.m index 3cad026d4..90233791d 100644 --- a/BreadWallet/BRPeerManager.m +++ b/BreadWallet/BRPeerManager.m @@ -36,6 +36,7 @@ #import "NSString+Bitcoin.h" #import "NSData+Bitcoin.h" #import "NSManagedObject+Sugar.h" +#import "BREventManager.h" #import #if ! PEER_LOGGING @@ -522,6 +523,7 @@ - (void)rescan // adds transaction to list of tx to be published, along with any unconfirmed inputs - (void)addTransactionToPublishList:(BRTransaction *)transaction { + NSLog(@"[BRPeerManager] add transaction to publish list %@", transaction); if (transaction.blockHeight == TX_UNCONFIRMED) { self.publishedTx[uint256_obj(transaction.txHash)] = transaction; @@ -536,8 +538,10 @@ - (void)addTransactionToPublishList:(BRTransaction *)transaction - (void)publishTransaction:(BRTransaction *)transaction completion:(void (^)(NSError *error))completion { + NSLog(@"[BRPeerManager] publish transaction %@", transaction); if (! transaction.isSigned) { if (completion) { + [[BREventManager sharedEventManager] saveEvent:@"peer_manager:not_signed"]; completion([NSError errorWithDomain:@"BreadWallet" code:401 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"bitcoin transaction not signed", nil)}]); } @@ -546,6 +550,7 @@ - (void)publishTransaction:(BRTransaction *)transaction completion:(void (^)(NSE } else if (! self.connected && self.connectFailures >= MAX_CONNECT_FAILURES) { if (completion) { + [[BREventManager sharedEventManager] saveEvent:@"peer_manager:not_connected"]; completion([NSError errorWithDomain:@"BreadWallet" code:-1009 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"not connected to the bitcoin network", nil)}]); } @@ -646,6 +651,7 @@ - (void)txTimeout:(NSValue *)txHash [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(txTimeout:) object:txHash]; if (callback) { + [[BREventManager sharedEventManager] saveEvent:@"peer_manager:tx_canceled_timeout"]; callback([NSError errorWithDomain:@"BreadWallet" code:BITCOIN_TIMEOUT_CODE userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"transaction canceled, network timeout", nil)}]); } @@ -748,6 +754,7 @@ - (void)removeUnrelayedTransactions if ([self.txRelays[hash] count] == 0 && [self.txRequests[hash] count] == 0) { // if this is for a transaction we sent, and it wasn't already known to be invalid, notify user of failure if (! rescan && [manager.wallet amountSentByTransaction:tx] > 0 && [manager.wallet transactionIsValid:tx]) { + NSLog(@"failed transaction %@", tx); rescan = notify = YES; for (NSValue *hash in tx.inputHashes) { // only recommend a rescan if all inputs are confirmed @@ -769,6 +776,7 @@ - (void)removeUnrelayedTransactions if (notify) { dispatch_async(dispatch_get_main_queue(), ^{ if (rescan) { + [[BREventManager sharedEventManager] saveEvent:@"peer_manager:tx_rejected_rescan"]; [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"transaction rejected", nil) message:NSLocalizedString(@"Your wallet may be out of sync.\n" "This can often be fixed by rescanning the blockchain.", nil) delegate:self @@ -776,6 +784,7 @@ - (void)removeUnrelayedTransactions otherButtonTitles:NSLocalizedString(@"rescan", nil), nil] show]; } else { + [[BREventManager sharedEventManager] saveEvent:@"peer_manager_tx_rejected"]; [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"transaction rejected", nil) message:nil delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", nil) otherButtonTitles:nil] show]; } @@ -848,6 +857,7 @@ - (void)sortPeers - (void)savePeers { + NSLog(@"[BRPeerManager] save peers"); NSMutableSet *peers = [[self.peers.set setByAddingObjectsFromSet:self.misbehavinPeers] mutableCopy]; NSMutableSet *addrs = [NSMutableSet set]; @@ -883,6 +893,7 @@ - (void)savePeers - (void)saveBlocks { + NSLog(@"[BRPeerManager] save blocks"); NSMutableDictionary *blocks = [NSMutableDictionary dictionary]; BRMerkleBlock *b = self.lastBlock; diff --git a/BreadWallet/BRPhoneWCSessionManager.h b/BreadWallet/BRPhoneWCSessionManager.h new file mode 100644 index 000000000..138768184 --- /dev/null +++ b/BreadWallet/BRPhoneWCSessionManager.h @@ -0,0 +1,35 @@ +// +// BRPhoneWCSessionManager.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@interface BRPhoneWCSessionManager : NSObject + +@property (nonatomic, readonly) BOOL reachable; + ++ (instancetype)sharedInstance; +- (void)notifyTransactionString:(NSString *)notification; + +@end diff --git a/BreadWallet/BRPhoneWCSessionManager.m b/BreadWallet/BRPhoneWCSessionManager.m new file mode 100644 index 000000000..7529f5adc --- /dev/null +++ b/BreadWallet/BRPhoneWCSessionManager.m @@ -0,0 +1,231 @@ +// +// BRPhoneWCSessionManager.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRPhoneWCSessionManager.h" +#import +#import "BRAppleWatchSharedConstants.h" +#import "BRWalletManager.h" +#import "BRPaymentRequest.h" +#import "UIImage+Utils.h" +#import "BRAppleWatchTransactionData+Factory.h" +#import "BRPeerManager.h" +#import "BRTransaction+Utils.h" + + +@interface BRPhoneWCSessionManager() +@property WCSession *session; +@end + + +@implementation BRPhoneWCSessionManager + ++ (instancetype)sharedInstance { + static BRPhoneWCSessionManager *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + if (self = [super init]) { + // prevent pre watchOS iOS access the feature + if ([WCSession class] && [WCSession isSupported]) { + self.session = [WCSession defaultSession]; + self.session.delegate = self; + [self.session activateSession]; + [self sendApplicationContext]; + [[NSNotificationCenter defaultCenter] + addObserver:self selector:@selector(sendDataUpdateNotificationToWatch) + name:BRWalletBalanceChangedNotification object:nil]; + } + } + return self; +} + +- (BOOL)reachable +{ + return self.session.reachable; +} + +- (void)notifyTransactionString:(NSString *)notification +{ + if (self.reachable) { + NSDictionary *msg = @{ + AW_PHONE_NOTIFICATION_KEY: notification, + AW_PHONE_NOTIFICATION_TYPE_KEY: @(AWPhoneNotificationTypeTxReceive) + }; + + [self.session sendMessage:msg replyHandler:^(NSDictionary * _Nonnull replyMessage) { + NSLog(@"received response from balance update notification to watch: %@", replyMessage); + } errorHandler:^(NSError * _Nonnull error) { + NSLog(@"got an error sending a balance update notification to watch"); + }]; + NSLog(@"sent a balance update notification to watch: %@", msg); + } +} + +#pragma mark - WKSession delegate +- (void)session:(WCSession *)session +didReceiveMessage:(NSDictionary *)message + replyHandler:(void(^)(NSDictionary *replyMessage))replyHandler { + + NSLog(@"BRPhoneWCSessionManager didReceiveMessage %@", message); + + if ([message[AW_SESSION_REQUEST_TYPE] integerValue] == AWSessionRquestTypeFetchData) { + switch ([message[AW_SESSION_REQUEST_DATA_TYPE_KEY] integerValue]) { + case AWSessionRquestDataTypeApplicationContextData: + [self handleApplicationContextDataRequest:message replyHandler:replyHandler]; + // sync with peer whenever there is a request coming, so we can update watch side. + [(id)[UIApplication sharedApplication].delegate + application:[UIApplication sharedApplication] + performFetchWithCompletionHandler:^(UIBackgroundFetchResult result) { + NSLog(@"watch triggered background fetch completed with result %lu", (unsigned long)result); + }]; + break; + case AWSessionRquestDataTypeQRCodeBits: + { + BRWalletManager *manager = [BRWalletManager sharedInstance]; + BRPaymentRequest *req = [BRPaymentRequest requestWithString:manager.wallet.receiveAddress]; + req.amount = [message[AW_SESSION_QR_CODE_BITS_KEY] integerValue]; + NSLog(@"watch requested a qr code amount %lld", req.amount); + UIImage *img = [UIImage imageWithQRCodeData:req.data + size:CGSizeMake(150, 150) + color:[CIColor colorWithRed:0.0 green:0.0 blue:0.0]]; + NSData *dat = UIImagePNGRepresentation(img); + replyHandler(@{AW_QR_CODE_BITS_KEY: dat}); + break; + } + default: + replyHandler(@{}); + } + } else { + replyHandler(@{}); + } +} + +#pragma mark - request handlers + +- (void)handleApplicationContextDataRequest:(NSDictionary*)request + replyHandler:(void(^)(NSDictionary *replyMessage))replyHandler { + NSDictionary *replay = @{AW_SESSION_RESPONSE_KEY: + [NSKeyedArchiver archivedDataWithRootObject:[self applicationContextData]]}; + replyHandler(replay); +} + +- (void)sendApplicationContext { + BRAppleWatchData *appleWatchData = [self applicationContextData]; + [self.session updateApplicationContext:@{AW_APPLICATION_CONTEXT_KEY: + [NSKeyedArchiver archivedDataWithRootObject:appleWatchData]} + error:nil]; +} + +- (void)sendDataUpdateNotificationToWatch { + [self sendApplicationContext]; +} + +- (BRAppleWatchData *)applicationContextData { + BRWalletManager *manager = [BRWalletManager sharedInstance]; + NSArray *transactions = manager.wallet.recentTransactions; + UIImage *qrCodeImage = self.qrCode; + BRAppleWatchData *appleWatchData = [[BRAppleWatchData alloc] init]; + appleWatchData.balance = [manager stringForAmount:manager.wallet.balance]; + appleWatchData.balanceInLocalCurrency = [manager localCurrencyStringForAmount:manager.wallet.balance]; + appleWatchData.receiveMoneyAddress = [BRWalletManager sharedInstance].wallet.receiveAddress; + appleWatchData.transactions = [[self recentTransactionListFromTransactions:transactions] copy]; + appleWatchData.receiveMoneyQRCodeImage = qrCodeImage; + appleWatchData.hasWallet = !manager.noWallet; + if (transactions.count > 0) { + appleWatchData.lastestTransction = [self lastTransactionStringFromTransaction:transactions[0]]; + } + return appleWatchData; +} + +- (NSString *)lastTransactionStringFromTransaction:(BRTransaction*)transaction { + if (transaction) { + NSString *timeDescriptionString = [self timeDescriptionStringFrom:transaction.transactionDate]; + if (timeDescriptionString == nil) { + timeDescriptionString = transaction.dateText; + } + NSString *transactionTypeString; + switch (transaction.transactionType) { + case BRAWTransactionTypeSent: + transactionTypeString = @"sent"; + break; + case BRAWTransactionTypeReceive: + transactionTypeString = @"received"; + break; + case BRAWTransactionTypeMove: + transactionTypeString = @"moved"; + break; + case BRAWTransactionTypeInvalid: + transactionTypeString = @"invalid transaction"; + break; + } + + return [NSString stringWithFormat:@"%@ %@ %@ , %@", + transactionTypeString, + [transaction.amountText stringByReplacingOccurrencesOfString:@"-" withString:@""], + (transaction.localCurrencyTextForAmount.length > 2) ? transaction.localCurrencyTextForAmount: @"", + timeDescriptionString]; + } + return @"no transaction"; +} + +- (NSString *)timeDescriptionStringFrom:(NSDate*) date{ + if (date) { + NSDate *now = [NSDate date]; + NSTimeInterval secondsSinceTransaction = [now timeIntervalSinceDate:date]; + if (secondsSinceTransaction < 60) { + return @"just now"; + } else if ( secondsSinceTransaction / 60 < 60) { + return [NSString stringWithFormat:@"%@ minutes agao", @((NSInteger) (secondsSinceTransaction / 60))]; + } else if ( secondsSinceTransaction / 60 / 60 < 24 ) { + return [NSString stringWithFormat:@"%@ hours agao", @((NSInteger) (secondsSinceTransaction / 60 / 60))]; + } + } + return nil; +} + +- (UIImage *)qrCode { + BRWalletManager *manager = [BRWalletManager sharedInstance]; + NSData *req = [BRPaymentRequest requestWithString:manager.wallet.receiveAddress].data; + return [UIImage imageWithQRCodeData:req + size:CGSizeMake(150, 150) + color:[CIColor colorWithRed:0.0 green:0.0 blue:0.0]]; +} + +#pragma mark - data helper methods + +- (NSArray *)recentTransactionListFromTransactions:(NSArray*)transactions { + NSMutableArray *transactionListData = [[NSMutableArray alloc] init]; + for ( BRTransaction *transaction in transactions) { + [transactionListData addObject:[BRAppleWatchTransactionData appleWatchTransactionDataFrom:transaction]]; + } + return transactionListData; +} + +@end diff --git a/BreadWallet/BRReceiveViewController.m b/BreadWallet/BRReceiveViewController.m index 69b4b19f4..5c78d396b 100644 --- a/BreadWallet/BRReceiveViewController.m +++ b/BreadWallet/BRReceiveViewController.m @@ -281,6 +281,7 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger [title isEqual:NSLocalizedString(@"copy request to clipboard", nil)]) { [UIPasteboard generalPasteboard].string = (self.paymentRequest.amount > 0) ? self.paymentRequest.string : self.paymentAddress; + NSLog(@"\n\nCOPIED PAYMENT REQUEST/ADDRESS:\n\n%@", [UIPasteboard generalPasteboard].string); [self.view addSubview:[[[BRBubbleView viewWithText:NSLocalizedString(@"copied", nil) center:CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height/2.0 - 130.0)] popIn] diff --git a/BreadWallet/BRSettingsViewController.m b/BreadWallet/BRSettingsViewController.m index 6295f482d..18362d992 100644 --- a/BreadWallet/BRSettingsViewController.m +++ b/BreadWallet/BRSettingsViewController.m @@ -29,6 +29,7 @@ #import "BRBubbleView.h" #import "BREventManager.h" #include +#import "BRUserDefaultsSwitchCell.h" @interface BRSettingsViewController () @@ -201,7 +202,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger switch (section) { case 0: return 2; - case 1: return (self.touchId) ? 2 : 1; + case 1: return (self.touchId) ? 3 : 2; case 2: return 2; } @@ -256,9 +257,19 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = [tableView dequeueReusableCellWithIdentifier:selectorIdent]; cell.textLabel.text = NSLocalizedString(@"touch id limit", nil); cell.detailTextLabel.text = [manager stringForAmount:manager.spendingLimit]; - break; + } else { + goto _switch_cell; } - // passthrough if ! self.touchId + case 2: + { +_switch_cell: + cell = [tableView dequeueReusableCellWithIdentifier:@"SwitchCell"]; + BRUserDefaultsSwitchCell *switchCell = (BRUserDefaultsSwitchCell *)cell; + switchCell.titleLabel.text = NSLocalizedString(@"enable receive notifications", nil); + [switchCell setUserDefaultsKey:USER_DEFAULTS_LOCAL_NOTIFICATIONS_KEY]; + break; + } + } break; @@ -476,8 +487,14 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath if (self.touchId) { [self performSelector:@selector(touchIdLimit:) withObject:nil afterDelay:0.0]; break; + } else { + goto _deselect_switch; } - // passthrough if ! self.touchId + case 2: +_deselect_switch: + { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + } } break; diff --git a/BreadWallet/BRTransaction+Utils.h b/BreadWallet/BRTransaction+Utils.h new file mode 100644 index 000000000..e20d0e1cb --- /dev/null +++ b/BreadWallet/BRTransaction+Utils.h @@ -0,0 +1,41 @@ +// +// BRTransaction+Utils.h +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRTransaction.h" + +typedef enum : NSInteger { + BRTransactionTypeSent, + BRTransactionTypeReceive, + BRTransactionTypeMove, + BRTransactionTypeInvalid +} BRTransactionType; + +@interface BRTransaction (Utils) +- (BRTransactionType)transactionType; +- (NSString*)amountText; +- (NSString*)localCurrencyTextForAmount; +- (NSString*)dateText; +- (NSDate*)transactionDate; +@end diff --git a/BreadWallet/BRTransaction+Utils.m b/BreadWallet/BRTransaction+Utils.m new file mode 100644 index 000000000..020566f2a --- /dev/null +++ b/BreadWallet/BRTransaction+Utils.m @@ -0,0 +1,121 @@ +// +// BRTransaction+Utils.m +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BRTransaction+Utils.h" +#import "BRWalletManager.h" +#import "BRPeerManager.h" + +@implementation BRTransaction (Utils) +- (BRTransactionType)transactionType { + BRWalletManager *manager = [BRWalletManager sharedInstance]; + uint64_t received = [manager.wallet amountReceivedFromTransaction:self], + sent = [manager.wallet amountSentByTransaction:self]; + uint32_t blockHeight = self.blockHeight; + uint32_t confirms = ([self lastBlockHeight] > blockHeight) ? 0 : (blockHeight - [self lastBlockHeight]) + 1; + if (confirms == 0 && ! [manager.wallet transactionIsValid:self]) { + return BRTransactionTypeInvalid; + } + + if (sent > 0 && received == sent) { + return BRTransactionTypeMove; + } + else if (sent > 0) { + return BRTransactionTypeSent; + } + else { + return BRTransactionTypeReceive; + } +} + +- (NSString*)localCurrencyTextForAmount { + BRWalletManager *manager = [BRWalletManager sharedInstance]; + uint64_t received = [manager.wallet amountReceivedFromTransaction:self], + sent = [manager.wallet amountSentByTransaction:self]; + if (sent > 0 && received == sent) { + return [NSString stringWithFormat:@"(%@)", + [manager localCurrencyStringForAmount:sent]]; + } + else if (sent > 0) { + return [NSString stringWithFormat:@"(%@)", + [manager localCurrencyStringForAmount:received - sent]]; + } + else { + return [NSString stringWithFormat:@"(%@)", + [manager localCurrencyStringForAmount:received]]; + } +} + +- (NSString*)amountText { + BRWalletManager *manager = [BRWalletManager sharedInstance]; + uint64_t received = [manager.wallet amountReceivedFromTransaction:self], + sent = [manager.wallet amountSentByTransaction:self]; + if (sent > 0 && received == sent) { + return [manager stringForAmount:sent]; + } + else if (sent > 0) { + return [manager stringForAmount:received - sent]; + } + else { + return [manager stringForAmount:received]; + } +} + +- (uint32_t)lastBlockHeight { + static uint32_t height = 0; + uint32_t h = [BRPeerManager sharedInstance].lastBlockHeight; + if (h > height) height = h; + return height; +} + +- (NSString*)dateText { + NSDateFormatter *df = [[NSDateFormatter alloc] init]; + df.dateFormat = dateFormat(@"Mdja"); + NSTimeInterval t = (self.timestamp > 1) ? self.timestamp : + [[BRPeerManager sharedInstance] timestampForBlockHeight:self.blockHeight] - 5*60; + return [[[df stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:t]].lowercaseString + stringByReplacingOccurrencesOfString:@"am" withString:@"a"] + stringByReplacingOccurrencesOfString:@"pm" withString:@"p"]; + + +} + +- (NSDate*)transactionDate { + return [NSDate dateWithTimeIntervalSinceReferenceDate:self.timestamp]; +} + +static NSString *dateFormat(NSString *template) { + NSString *format = [NSDateFormatter dateFormatFromTemplate:template options:0 locale:[NSLocale currentLocale]]; + format = [format stringByReplacingOccurrencesOfString:@", " withString:@" "]; + format = [format stringByReplacingOccurrencesOfString:@" a" withString:@"a"]; + format = [format stringByReplacingOccurrencesOfString:@"hh" withString:@"h"]; + format = [format stringByReplacingOccurrencesOfString:@" ha" withString:@"@ha"]; + format = [format stringByReplacingOccurrencesOfString:@"HH" withString:@"H"]; + format = [format stringByReplacingOccurrencesOfString:@"H 'h'" withString:@"H'h'"]; + format = [format stringByReplacingOccurrencesOfString:@"H " withString:@"H'h' "]; + format = [format stringByReplacingOccurrencesOfString:@"H" withString:@"H'h'" + options:NSBackwardsSearch|NSAnchoredSearch range:NSMakeRange(0, format.length)]; + return format; +} +@end diff --git a/BreadWallet/BRTransaction.h b/BreadWallet/BRTransaction.h index 1f1f9bf3c..1b0826a30 100644 --- a/BreadWallet/BRTransaction.h +++ b/BreadWallet/BRTransaction.h @@ -57,6 +57,8 @@ typedef union _UInt256 UInt256; @property (nonatomic, readonly) BOOL isSigned; // checks if all signatures exist, but does not verify them @property (nonatomic, readonly, getter = toData) NSData *data; +@property (nonatomic, readonly) NSString *longDescription; + + (instancetype)transactionWithMessage:(NSData *)message; - (instancetype)initWithMessage:(NSData *)message; diff --git a/BreadWallet/BRTransaction.m b/BreadWallet/BRTransaction.m index 8c28c649f..32fc7bb99 100644 --- a/BreadWallet/BRTransaction.m +++ b/BreadWallet/BRTransaction.m @@ -199,6 +199,23 @@ - (NSArray *)outputScripts return self.outScripts; } +- (NSString *)description +{ + NSString *txid = [NSString hexWithData:[NSData dataWithBytes:self.txHash.u8 length:sizeof(UInt256)].reverse]; + return [NSString stringWithFormat:@"%@(id=%@)", [self class], txid]; +} + +- (NSString *)longDescription +{ + NSString *txid = [NSString hexWithData:[NSData dataWithBytes:self.txHash.u8 length:sizeof(UInt256)].reverse]; + return [NSString stringWithFormat: + @"%@(id=%@, inputHashes=%@, inputIndexes=%@, inputScripts=%@, inputSignatures=%@, inputSequences=%@, " + "outputAmounts=%@, outputAddresses=%@, outputScripts=%@)", + [[self class] description], txid, + self.inputHashes, self.inputIndexes, self.inputScripts, self.inputSignatures, self.inputSequences, + self.outputAmounts, self.outputAddresses, self.outputScripts]; +} + // size in bytes if signed, or estimated size assuming compact pubkey sigs - (size_t)size { diff --git a/BreadWallet/BRUserDefaultsConstants.h b/BreadWallet/BRUserDefaultsConstants.h new file mode 100644 index 000000000..0f82246fe --- /dev/null +++ b/BreadWallet/BRUserDefaultsConstants.h @@ -0,0 +1,15 @@ +// +// BRUserDefaultsConstants.h +// BreadWallet +// +// Created by Samuel Sutch on 12/29/15. +// Copyright © 2015 Aaron Voisine. All rights reserved. +// + +#ifndef BRUserDefaultsConstants_h +#define BRUserDefaultsConstants_h + +#define USER_DEFAULTS_LOCAL_NOTIFICATIONS_KEY @"USER_DEFAULTS_LOCAL_NOTIFICATIONS_KEY" +#define USER_DEFAULTS_LOCAL_NOTIFICATIONS_SWITCH_KEY @"USER_DEFAULTS_LOCAL_NOTIFICATIONS_SWITCH_KEY" + +#endif /* BRUserDefaultsConstants_h */ diff --git a/BreadWallet/BRUserDefaultsSwitchCell.h b/BreadWallet/BRUserDefaultsSwitchCell.h new file mode 100644 index 000000000..767230ced --- /dev/null +++ b/BreadWallet/BRUserDefaultsSwitchCell.h @@ -0,0 +1,18 @@ +// +// BRUserDefaultsSwitchCell.h +// BreadWallet +// +// Created by Samuel Sutch on 12/29/15. +// Copyright © 2015 Aaron Voisine. All rights reserved. +// + +#import + +@interface BRUserDefaultsSwitchCell : UITableViewCell + +@property (nonatomic, weak) IBOutlet UISwitch *theSwitch; +@property (nonatomic, weak) IBOutlet UILabel *titleLabel; +- (IBAction)didUpdateSwitch:(id)sender; +- (void)setUserDefaultsKey:(NSString *)key; + +@end diff --git a/BreadWallet/BRUserDefaultsSwitchCell.m b/BreadWallet/BRUserDefaultsSwitchCell.m new file mode 100644 index 000000000..20e1e1a41 --- /dev/null +++ b/BreadWallet/BRUserDefaultsSwitchCell.m @@ -0,0 +1,47 @@ +// +// BRUserDefaultsSwitchCell.m +// BreadWallet +// +// Created by Samuel Sutch on 12/29/15. +// Copyright © 2015 Aaron Voisine. All rights reserved. +// + +#import "BRUserDefaultsSwitchCell.h" + +@interface BRUserDefaultsSwitchCell () + +@property (nonatomic, copy) NSString *_userDefaultsKey; +- (void)updateUi; + +@end + +@implementation BRUserDefaultsSwitchCell + +- (void)setUserDefaultsKey:(NSString *)key +{ + self._userDefaultsKey = key; + [self updateUi]; +} + +- (void)didUpdateSwitch:(id)sender +{ + if (self._userDefaultsKey) + [[NSUserDefaults standardUserDefaults] setBool:self.theSwitch.on forKey:self._userDefaultsKey]; +} + +- (void)updateUi +{ + if (self._userDefaultsKey) + self.theSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:self._userDefaultsKey]; +} + +- (void)awakeFromNib { + if (self._userDefaultsKey) + self.theSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:self._userDefaultsKey]; +} + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated { + [super setSelected:selected animated:animated]; +} + +@end diff --git a/BreadWallet/BRWallet.m b/BreadWallet/BRWallet.m index e1820adc1..04b18df2d 100644 --- a/BreadWallet/BRWallet.m +++ b/BreadWallet/BRWallet.m @@ -531,6 +531,7 @@ - (BOOL)registerTransaction:(BRTransaction *)transaction if (self.allTx[hash] != nil) return YES; //TODO: handle tx replacement with input sequence numbers (now replacements appear invalid until confirmation) + NSLog(@"[BRWallet] received unseen transaction %@", transaction); self.allTx[hash] = transaction; [self.transactions insertObject:transaction atIndex:0]; diff --git a/BreadWallet/Base.lproj/Main.storyboard b/BreadWallet/Base.lproj/Main.storyboard index 7c9cdb68d..d05f7c97b 100644 --- a/BreadWallet/Base.lproj/Main.storyboard +++ b/BreadWallet/Base.lproj/Main.storyboard @@ -81,6 +81,7 @@ HelveticaNeue-Light HelveticaNeue-Light HelveticaNeue-Light + HelveticaNeue-Light @@ -2171,6 +2172,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2185,7 +2221,7 @@ - + diff --git a/BreadWallet/BreadWallet-Prefix.pch b/BreadWallet/BreadWallet-Prefix.pch index 9f5e194c7..498280025 100644 --- a/BreadWallet/BreadWallet-Prefix.pch +++ b/BreadWallet/BreadWallet-Prefix.pch @@ -5,6 +5,7 @@ // #import +#import "BRUserDefaultsConstants.h" #ifndef __IPHONE_7_0 #warning "This project uses features only available in iOS SDK 7.0 and later." diff --git a/WatchApp Extension/BRAWBalanceInterfaceController.swift b/WatchApp Extension/BRAWBalanceInterfaceController.swift new file mode 100644 index 000000000..4b9b01d38 --- /dev/null +++ b/WatchApp Extension/BRAWBalanceInterfaceController.swift @@ -0,0 +1,133 @@ +// +// BRAWBalanceInterfaceController.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit + +class BRAWBalanceInterfaceController: WKInterfaceController { + @IBOutlet var table: WKInterfaceTable! + var transactionList = [BRAppleWatchTransactionData]() + + @IBOutlet var balanceTextContainer: WKInterfaceGroup! + @IBOutlet var balanceLoadingIndicator: WKInterfaceGroup! + @IBOutlet var balanceLabel: WKInterfaceLabel! + @IBOutlet var balanceInLocalCurrencyLabel: WKInterfaceLabel! + @IBOutlet var transactionHeaderContainer: WKInterfaceGroup! { + didSet { + transactionHeaderContainer.setHidden(true) // hide header as default + } + } + + var showBalanceLoadingIndicator = false { + didSet{ + self.balanceTextContainer.setHidden(showBalanceLoadingIndicator) + self.balanceLoadingIndicator.setHidden(!showBalanceLoadingIndicator) + } + } + + // MARK: View life cycle + + override func awakeWithContext(context: AnyObject?) { + super.awakeWithContext(context) + updateBalance() + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + updateBalance() + updateTransactionList() + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "updateUI", name: BRAWWatchDataManager.ApplicationDataDidUpdateNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "txReceive:", name: BRAWWatchDataManager.WalletTxReceiveNotification, object: nil) + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + @objc func txReceive(notification: NSNotification?) { + print("balance view controller received notification: \(notification)") + if let userData = notification?.userInfo, + noteString = userData[NSLocalizedDescriptionKey] as? String { + self.presentAlertControllerWithTitle( + noteString, message: nil, preferredStyle: .Alert, actions: [ + WKAlertAction(title: NSLocalizedString("OK", comment: ""), + style: .Cancel, handler: { self.dismissController() })]) + } + } + + // MARK: UI update + func updateUI() { + updateBalance() + updateTransactionList() + } + + func updateBalance() { + if let balanceInLocalizationString = BRAWWatchDataManager.sharedInstance.balanceInLocalCurrency as String? { + if (BRAWWatchDataManager.sharedInstance.balanceAttributedString() != nil){ + balanceLabel.setAttributedText(BRAWWatchDataManager.sharedInstance.balanceAttributedString()) + } + balanceInLocalCurrencyLabel.setText(balanceInLocalizationString) + showBalanceLoadingIndicator = false; + } else { + showBalanceLoadingIndicator = true; + } + } + + func updateTransactionList() { + transactionList = BRAWWatchDataManager.sharedInstance.transactionHistory + let currentTableRowCount = table.numberOfRows + let newTransactionCount = transactionList.count + let numberRowsToInsertOrDelete = newTransactionCount - currentTableRowCount + self.transactionHeaderContainer.setHidden(newTransactionCount == 0) + // insert or delete rows to match number of transactions + if (numberRowsToInsertOrDelete > 0) { + let ixs = NSIndexSet(indexesInRange: NSMakeRange(currentTableRowCount, numberRowsToInsertOrDelete)) + table.insertRowsAtIndexes(ixs, withRowType: "BRAWTransactionRowControl") + } else { + let ixs = NSIndexSet(indexesInRange: NSMakeRange(newTransactionCount, abs(numberRowsToInsertOrDelete))) + table.removeRowsAtIndexes(ixs) + } + // update row content + for var index = 0; index < newTransactionCount; index++ { + if let rowControl = table.rowControllerAtIndex(index) as? BRAWTransactionRowControl { + updateRow(rowControl, transaction: self.transactionList[index]) + } + } + } + + func updateRow(rowControl: BRAWTransactionRowControl, transaction: BRAppleWatchTransactionData) { + let localCurrencyAmount + = (transaction.amountTextInLocalCurrency.characters.count > 2) ? transaction.amountTextInLocalCurrency : " " + rowControl.amountLabel.setText(transaction.amountText) + rowControl.localCurrencyAmount.setText(localCurrencyAmount) + rowControl.dateLabel.setText(transaction.dateText) + rowControl.type = transaction.type + rowControl.seperatorGroup.setHeight(0.5) + } +} diff --git a/WatchApp Extension/BRAWGlanceInterfaceController.swift b/WatchApp Extension/BRAWGlanceInterfaceController.swift new file mode 100644 index 000000000..27199fd83 --- /dev/null +++ b/WatchApp Extension/BRAWGlanceInterfaceController.swift @@ -0,0 +1,92 @@ +// +// BRAWGlanceInterfaceController.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit + +class BRAWGlanceInterfaceController: WKInterfaceController { + + @IBOutlet var setupWalletContainer: WKInterfaceGroup! + @IBOutlet var balanceAmountLabel: WKInterfaceLabel! + @IBOutlet var balanceInLocalCurrencyLabel: WKInterfaceLabel! + @IBOutlet var lastTransactionLabel: WKInterfaceLabel! + @IBOutlet var balanceInfoContainer: WKInterfaceGroup! + @IBOutlet var loadingIndicator: WKInterfaceGroup! + override func awakeWithContext(context: AnyObject?) { + super.awakeWithContext(context) + // Configure interface objects here. + updateUI() + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + BRAWWatchDataManager.sharedInstance.setupTimer() + updateUI() + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "updateUI", name: BRAWWatchDataManager.ApplicationDataDidUpdateNotification, object: nil) + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + BRAWWatchDataManager.sharedInstance.destoryTimer() + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + func updateUI() { + // when local currency rate is no avaliable, use empty string + updateContainerVisibility() + + if (BRAWWatchDataManager.sharedInstance.balanceInLocalCurrency?.characters.count <= 2) { + balanceInLocalCurrencyLabel.setHidden(true) + } else { + balanceInLocalCurrencyLabel.setHidden(false) + } + balanceAmountLabel.setAttributedText(BRAWWatchDataManager.sharedInstance.balanceAttributedString()) + balanceInLocalCurrencyLabel.setText(BRAWWatchDataManager.sharedInstance.balanceInLocalCurrency) + lastTransactionLabel.setText(BRAWWatchDataManager.sharedInstance.lastestTransction) + } + + func shouldShowSetupWalletInterface()->Bool { + return false; + } + + func updateContainerVisibility() { + switch BRAWWatchDataManager.sharedInstance.walletStatus { + case .Unknown: + loadingIndicator.setHidden(false) + balanceInfoContainer.setHidden(true) + setupWalletContainer.setHidden(true) + case .NotSetup: + loadingIndicator.setHidden(true) + balanceInfoContainer.setHidden(true) + setupWalletContainer.setHidden(false) + case .HasSetup: + loadingIndicator.setHidden(true) + balanceInfoContainer.setHidden(false) + setupWalletContainer.setHidden(true) + } + } +} \ No newline at end of file diff --git a/WatchApp Extension/BRAWKeypad.swift b/WatchApp Extension/BRAWKeypad.swift new file mode 100644 index 000000000..2d8654ec3 --- /dev/null +++ b/WatchApp Extension/BRAWKeypad.swift @@ -0,0 +1,97 @@ +// +// BRAWKeypad.swift +// BreadWallet +// +// Created by Samuel Sutch on 12/27/15. +// Copyright © 2015 Aaron Voisine. All rights reserved. +// + +import Foundation +import WatchKit + +protocol BRAWKeypadDelegate { + func keypadDidFinish(stringValueBits: String) +} + +class BRAWKeypadModel { + var delegate: BRAWKeypadDelegate? = nil + var valueInBits: String = "0" + + init(delegate d: BRAWKeypadDelegate?) { + delegate = d + } +} + +class BRAWKeypad: WKInterfaceController { + var digits: [String] = [String]() + var ctx: BRAWKeypadModel? + + override func awakeWithContext(context: AnyObject?) { + ctx = context as? BRAWKeypadModel + digits = [String]() + if let c = ctx { + for s in c.valueInBits.componentsSeparatedByString("") { + digits.append(s) + } + } + } + + override func willDisappear() { + ctx = nil + } + + @IBOutlet var display: WKInterfaceLabel! + + @IBAction func one(sender: AnyObject?) { append("1") } + + @IBAction func two(sender: AnyObject?) { append("2") } + + @IBAction func three(sender: AnyObject?) { append("3") } + + @IBAction func four(sender: AnyObject?) { append("4") } + + @IBAction func five(sender: AnyObject?) { append("5") } + + @IBAction func six(sender: AnyObject?) { append("6") } + + @IBAction func seven(sender: AnyObject?) { append("7") } + + @IBAction func eight(sender: AnyObject?) { append("8") } + + @IBAction func nine(sender: AnyObject?) { append("9") } + + @IBAction func zero(sender: AnyObject?) { append("0") } + + @IBAction func del(sender: AnyObject?) { + if digits.count > 0 { + digits.removeLast() + fmt() + } + } + + @IBAction func ok(sender: AnyObject?) { + ctx?.delegate?.keypadDidFinish(ctx!.valueInBits) + } + + func append(digit: String) { + digits.append(digit) + fmt() + } + + func fmt() { + var s = "ƀ" + var d = digits + while d.count > 0 && d[0] == "0" { d.removeFirst() } // remove remove forward zero padding + while d.count < 3 { d.insert("0", atIndex: 0) } // add it back correctly + for i in 0...(d.count - 1) { + if i == d.count - 2 { + s.appendContentsOf(".") + } + s.appendContentsOf(d[i]) + } + display.setText(s) + ctx?.valueInBits = s + .stringByReplacingOccurrencesOfString(".", withString: "") + .stringByReplacingOccurrencesOfString("ƀ", withString: "") + } +} diff --git a/WatchApp Extension/BRAWReceiveMoneyInterfaceController.swift b/WatchApp Extension/BRAWReceiveMoneyInterfaceController.swift new file mode 100644 index 000000000..db6c3c01b --- /dev/null +++ b/WatchApp Extension/BRAWReceiveMoneyInterfaceController.swift @@ -0,0 +1,105 @@ +// +// BRAWReceiveMoneyInterfaceController.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit +import WatchConnectivity + +class BRAWReceiveMoneyInterfaceController: WKInterfaceController, WCSessionDelegate, BRAWKeypadDelegate { + + @IBOutlet var loadingIndicator: WKInterfaceGroup! + @IBOutlet var imageContainer: WKInterfaceGroup! + @IBOutlet var qrCodeImage: WKInterfaceImage! + @IBOutlet var qrCodeButton: WKInterfaceButton! + var customQR: UIImage? + + override func awakeWithContext(context: AnyObject?) { + super.awakeWithContext(context) + } + + override func willActivate() { + super.willActivate() + customQR = nil + updateReceiveUI() + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "updateReceiveUI", + name: BRAWWatchDataManager.ApplicationDataDidUpdateNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "txReceive:", name: BRAWWatchDataManager.WalletTxReceiveNotification, object: nil) + } + + override func didDeactivate() { + super.didDeactivate() + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + @objc func txReceive(notification: NSNotification?) { + print("receive view controller received notification: \(notification)") + if let userData = notification?.userInfo, + noteString = userData[NSLocalizedDescriptionKey] as? String { + self.presentAlertControllerWithTitle( + noteString, message: nil, preferredStyle: .Alert, actions: [ + WKAlertAction(title: NSLocalizedString("OK", comment: ""), + style: .Cancel, handler: { self.dismissController() })]) + } + } + + func updateReceiveUI() { + if BRAWWatchDataManager.sharedInstance.receiveMoneyQRCodeImage == nil { + loadingIndicator.setHidden(false) + qrCodeButton.setHidden(true) + } else { + loadingIndicator.setHidden(true) + qrCodeButton.setHidden(false) + var qrImg = BRAWWatchDataManager.sharedInstance.receiveMoneyQRCodeImage + if customQR != nil { + print("Using custom qr image") + qrImg = customQR + } + qrCodeButton.setBackgroundImage(qrImg) + } + } + + @IBAction func qrCodeTap(sender: AnyObject?) { + let ctx = BRAWKeypadModel(delegate: self) + self.presentControllerWithName("Keypad", context: ctx) + } + + // - MARK: Keypad delegate + + func keypadDidFinish(stringValueBits: String) { + qrCodeButton.setHidden(true) + loadingIndicator.setHidden(false) + BRAWWatchDataManager.sharedInstance.requestQRCodeForBalance(stringValueBits) { (qrImage, error) -> Void in + if let qrImage = qrImage { + self.customQR = qrImage + } + self.updateReceiveUI() + print("Got new qr image: \(qrImage) error: \(error)") + } + self.dismissController() + } + + +} diff --git a/WatchApp Extension/BRAWRootInterfaceController.swift b/WatchApp Extension/BRAWRootInterfaceController.swift new file mode 100644 index 000000000..6d056af25 --- /dev/null +++ b/WatchApp Extension/BRAWRootInterfaceController.swift @@ -0,0 +1,80 @@ +// +// BRAWRootInterfaceController.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit + +class BRAWRootInterfaceController: WKInterfaceController { + @IBOutlet var setupWalletMessageLabel: WKInterfaceLabel! { + didSet{ + setupWalletMessageLabel.setHidden(true) + } + } + @IBOutlet var loadingIndicator: WKInterfaceGroup! + + override func awakeWithContext(context: AnyObject?) { + super.awakeWithContext(context) + } + + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + updateUI() + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "updateUI", name: BRAWWatchDataManager.WalletStatusDidChangeNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver( + self, selector: "txReceive:", name: BRAWWatchDataManager.WalletTxReceiveNotification, object: nil) + } + + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + func updateUI() { + switch BRAWWatchDataManager.sharedInstance.walletStatus { + case .Unknown: + loadingIndicator.setHidden(false) + setupWalletMessageLabel.setHidden(true) + case .NotSetup: + loadingIndicator.setHidden(true) + setupWalletMessageLabel.setHidden(false) + case .HasSetup: + WKInterfaceController.reloadRootControllersWithNames( + ["BRAWBalanceInterfaceController","BRAWReceiveMoneyInterfaceController"], contexts: []) + } + } + + @objc func txReceive(notification: NSNotification?) { + print("root view controller received notification: \(notification)") + if let userData = notification?.userInfo, + noteString = userData[NSLocalizedDescriptionKey] as? String { + self.presentAlertControllerWithTitle( + noteString, message: nil, preferredStyle: .Alert, actions: [ + WKAlertAction(title: NSLocalizedString("OK", comment: ""), + style: .Cancel, handler: { self.dismissController() })]) + } + } +} diff --git a/WatchApp Extension/BRAWTransactionRowControl.swift b/WatchApp Extension/BRAWTransactionRowControl.swift new file mode 100644 index 000000000..e9060a637 --- /dev/null +++ b/WatchApp Extension/BRAWTransactionRowControl.swift @@ -0,0 +1,59 @@ +// +// BRAWTransactionRowControl.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit + +class BRAWTransactionRowControl: NSObject { + + @IBOutlet var statusIcon: WKInterfaceImage! { + didSet { + statusIcon.setImage(nil) + } + } + @IBOutlet var amountLabel: WKInterfaceLabel! + @IBOutlet var dateLabel: WKInterfaceLabel! + @IBOutlet var seperatorGroup: WKInterfaceGroup! + @IBOutlet var localCurrencyAmount: WKInterfaceLabel! + var type = BRAWTransactionTypeReceive { + didSet { + switch type { + case BRAWTransactionTypeReceive: + statusIcon.setImageNamed("ReceiveMoneyIcon") + break; + case BRAWTransactionTypeSent: + statusIcon.setImageNamed("SentMoneyIcon") + break; + case BRAWTransactionTypeMove: + statusIcon.setImageNamed("MoveMoneyIcon") + break; + case BRAWTransactionTypeInvalid: + statusIcon.setImageNamed("InvalidTransactionIcon") + break; + default: + statusIcon.setImage(nil) + } + } + } +} diff --git a/WatchApp Extension/BRAWWatchDataManager.swift b/WatchApp Extension/BRAWWatchDataManager.swift new file mode 100644 index 000000000..bad5b7e1c --- /dev/null +++ b/WatchApp Extension/BRAWWatchDataManager.swift @@ -0,0 +1,224 @@ +// +// BRAWWatchDataManager.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit +import WatchConnectivity + +enum WalletStatus { + case Unknown + case HasSetup + case NotSetup +} + +class BRAWWatchDataManager: NSObject, WCSessionDelegate { + static let sharedInstance = BRAWWatchDataManager() + static let ApplicationDataDidUpdateNotification = "ApplicationDataDidUpdateNotification" + static let WalletStatusDidChangeNotification = "WalletStatusDidChangeNotification" + static let WalletTxReceiveNotification = "WalletTxReceiveNotification" + static let applicationContextDataFileName = "applicationContextData.txt" + + let session : WCSession = WCSession.defaultSession() + let timerFireInterval : NSTimeInterval = 7; // have iphone app sync with peer every 7 seconds + + var timer : NSTimer? + + private var appleWatchData : BRAppleWatchData? + + var balance : String? { return appleWatchData?.balance } + var balanceInLocalCurrency : String? { return appleWatchData?.balanceInLocalCurrency } + var receiveMoneyAddress : String? { return appleWatchData?.receiveMoneyAddress } + var receiveMoneyQRCodeImage : UIImage? { return appleWatchData?.receiveMoneyQRCodeImage } + var lastestTransction : String? { return appleWatchData?.lastestTransction } + var transactionHistory : [BRAppleWatchTransactionData] { + if let unwrappedAppleWatchData: BRAppleWatchData = appleWatchData, + let transactions :[BRAppleWatchTransactionData] = unwrappedAppleWatchData.transactions{ + return transactions + } else { + return [BRAppleWatchTransactionData]() + } + } + var walletStatus : WalletStatus { + if appleWatchData == nil { + return WalletStatus.Unknown + } else if appleWatchData!.hasWallet { + return WalletStatus.HasSetup + } else { + return WalletStatus.NotSetup + } + } + + lazy var dataFilePath: NSURL = { + let filemgr = NSFileManager.defaultManager() + let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true) + let docsDir = dirPaths[0] as String + return NSURL(fileURLWithPath: docsDir).URLByAppendingPathComponent(applicationContextDataFileName) + }() + + override init() { + super.init() + if appleWatchData == nil { + unarchiveData() + } + session.delegate = self + session.activateSession() + } + + func requestAllData() { + if self.session.reachable { + WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Click) + let messageToSend = [ + AW_SESSION_REQUEST_TYPE: NSNumber(unsignedInt:AWSessionRquestTypeFetchData.rawValue), + AW_SESSION_REQUEST_DATA_TYPE_KEY: + NSNumber(unsignedInt:AWSessionRquestDataTypeApplicationContextData.rawValue) + ] + session.sendMessage(messageToSend, replyHandler: { [unowned self] replyMessage in + if let data = replyMessage[AW_SESSION_RESPONSE_KEY] as? NSData { + if let unwrappedAppleWatchData + = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? BRAppleWatchData { + let previousAppleWatchData = self.appleWatchData + let previousWalletStatus = self.walletStatus + self.appleWatchData = unwrappedAppleWatchData + if previousAppleWatchData != self.appleWatchData { + self.archiveData(unwrappedAppleWatchData) + WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Click) + NSNotificationCenter.defaultCenter().postNotificationName( + BRAWWatchDataManager.ApplicationDataDidUpdateNotification, object: nil) + } + if self.walletStatus != previousWalletStatus { + NSNotificationCenter.defaultCenter().postNotificationName( + BRAWWatchDataManager.WalletStatusDidChangeNotification, object: nil) + } + } + } + }, errorHandler: {error in + WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Failure) + print(error) + }) + } + } + + func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) { + if let applicationContextData = applicationContext[AW_APPLICATION_CONTEXT_KEY] as? NSData { + if let transferedAppleWatchData + = NSKeyedUnarchiver.unarchiveObjectWithData(applicationContextData) as? BRAppleWatchData { + let previousWalletStatus = self.walletStatus + appleWatchData = transferedAppleWatchData + archiveData(transferedAppleWatchData) + if self.walletStatus != previousWalletStatus { + NSNotificationCenter.defaultCenter().postNotificationName( + BRAWWatchDataManager.WalletStatusDidChangeNotification, object: nil) + } + NSNotificationCenter.defaultCenter().postNotificationName( + BRAWWatchDataManager.ApplicationDataDidUpdateNotification, object: nil) + + } + } + } + + func session( + session: WCSession, didReceiveMessage message: [String : AnyObject], + replyHandler: ([String : AnyObject]) -> Void) { + print("Handle message from phone \(message)") + if let noteV = message[AW_PHONE_NOTIFICATION_KEY], + noteStr = noteV as? String, + noteTypeV = message[AW_PHONE_NOTIFICATION_TYPE_KEY], + noteTypeN = noteTypeV as? NSNumber + where noteTypeN.unsignedIntValue == AWPhoneNotificationTypeTxReceive.rawValue { + let note = NSNotification( + name: BRAWWatchDataManager.WalletTxReceiveNotification, object: nil, userInfo: [ + NSLocalizedDescriptionKey: noteStr]); + NSNotificationCenter.defaultCenter().postNotification(note) + } + } + + func requestQRCodeForBalance(bits: String, responseHandler: (qrImage: UIImage?, error: NSError?) -> Void) { + if self.session.reachable { + let msg = [ + AW_SESSION_REQUEST_TYPE: NSNumber(unsignedInt: AWSessionRquestTypeFetchData.rawValue), + AW_SESSION_REQUEST_DATA_TYPE_KEY: NSNumber(unsignedInt: AWSessionRquestDataTypeQRCodeBits.rawValue), + AW_SESSION_QR_CODE_BITS_KEY: bits + ] + session.sendMessage(msg, + replyHandler: { (ctx) -> Void in + if let dat = ctx[AW_QR_CODE_BITS_KEY], + datDat = dat as? NSData, + img = UIImage(data: datDat) { + responseHandler(qrImage: img, error: nil) + return + } + responseHandler(qrImage: nil, error: NSError(domain: "", code: 500, + userInfo: [NSLocalizedDescriptionKey: "Unable to get new qr code"])) + }, errorHandler: { (err) -> Void in + responseHandler(qrImage: nil, error: err) + }) + } + } + + func balanceAttributedString() -> NSAttributedString? { + if let originalBalanceString = BRAWWatchDataManager.sharedInstance.balance { + var balanceString = originalBalanceString.stringByReplacingOccurrencesOfString("ƀ", withString: "") + balanceString = balanceString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) + return attributedStringForBalance(balanceString) + } + return nil + } + + private func attributedStringForBalance(balance: String?)-> NSAttributedString { + let attributedString = NSMutableAttributedString() + + attributedString.appendAttributedString( + NSAttributedString(string: "ƀ", attributes: [NSForegroundColorAttributeName : UIColor.grayColor()])) + + attributedString.appendAttributedString( + NSAttributedString(string: balance ?? "0", attributes: + [NSForegroundColorAttributeName : UIColor.whiteColor()])) + + return attributedString + } + + func archiveData(appleWatchData: BRAppleWatchData){ + NSKeyedArchiver.archivedDataWithRootObject(appleWatchData).writeToURL(dataFilePath, atomically: true) + } + + func unarchiveData() { + if let data = NSData(contentsOfURL: dataFilePath) { + appleWatchData = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? BRAppleWatchData + } + } + + func setupTimer() { + destoryTimer() + let weakTimerTarget = BRAWWeakTimerTarget(initTarget: self, initSelector: "requestAllData") + timer = NSTimer.scheduledTimerWithTimeInterval( + timerFireInterval, target: weakTimerTarget, selector: "timerDidFire", userInfo: nil, repeats: true) + } + + func destoryTimer() { + if let currentTimer : NSTimer = timer { + currentTimer.invalidate(); + timer = nil + } + } +} diff --git a/WatchApp Extension/BRAWWeakTimerTarget.swift b/WatchApp Extension/BRAWWeakTimerTarget.swift new file mode 100644 index 000000000..4db41d8ee --- /dev/null +++ b/WatchApp Extension/BRAWWeakTimerTarget.swift @@ -0,0 +1,43 @@ +// +// BRAWWeakTimerTarget.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit + +class BRAWWeakTimerTarget: NSObject { + weak var target : AnyObject? = nil + var selector : Selector? = nil + + init(initTarget: AnyObject , initSelector : Selector) { + super.init() + target = initTarget + selector = initSelector + } + + func timerDidFire() { + if target != nil && selector != nil && target!.respondsToSelector(selector!) { + target!.performSelector(selector!) + } + } +} diff --git a/WatchApp Extension/ExtensionDelegate.swift b/WatchApp Extension/ExtensionDelegate.swift new file mode 100644 index 000000000..2cb4880d7 --- /dev/null +++ b/WatchApp Extension/ExtensionDelegate.swift @@ -0,0 +1,41 @@ +// +// ExtensionDelegate.swift +// BreadWallet +// +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import WatchKit + +class ExtensionDelegate: NSObject, WKExtensionDelegate { + func applicationDidFinishLaunching() { + // Perform any final initialization of your application. + } + + func applicationDidBecomeActive() { + BRAWWatchDataManager.sharedInstance.setupTimer() + BRAWWatchDataManager.sharedInstance.requestAllData() + } + + func applicationWillResignActive() { + BRAWWatchDataManager.sharedInstance.destoryTimer() + } +} diff --git a/WatchApp Extension/Info.plist b/WatchApp Extension/Info.plist new file mode 100644 index 000000000..7c2592e36 --- /dev/null +++ b/WatchApp Extension/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + WatchApp Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 0.5.4 + CFBundleSignature + ???? + CFBundleVersion + 15 + NSExtension + + NSExtensionAttributes + + WKAppBundleIdentifier + org.voisine.breadwallet.watchkitapp + + NSExtensionPointIdentifier + com.apple.watchkit + + RemoteInterfacePrincipalClass + $(PRODUCT_MODULE_NAME).InterfaceController + WKExtensionDelegateClassName + $(PRODUCT_MODULE_NAME).ExtensionDelegate + + diff --git a/WatchApp Extension/WatchApp Extension-Bridging-Header.h b/WatchApp Extension/WatchApp Extension-Bridging-Header.h new file mode 100644 index 000000000..d0f443051 --- /dev/null +++ b/WatchApp Extension/WatchApp Extension-Bridging-Header.h @@ -0,0 +1,26 @@ +// Created by Henry on 10/27/15. +// Copyright (c) 2015 Aaron Voisine +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "BRAppleWatchSharedConstants.h" \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..8edacf1a5 --- /dev/null +++ b/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,70 @@ +{ + "images" : [ + { + "size" : "24x24", + "idiom" : "watch", + "filename" : "icon48.png", + "scale" : "2x", + "role" : "notificationCenter", + "subtype" : "38mm" + }, + { + "size" : "27.5x27.5", + "idiom" : "watch", + "filename" : "icon55.png", + "scale" : "2x", + "role" : "notificationCenter", + "subtype" : "42mm" + }, + { + "size" : "29x29", + "idiom" : "watch", + "filename" : "icon58.png", + "role" : "companionSettings", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "watch", + "filename" : "icon87.png", + "role" : "companionSettings", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "watch", + "filename" : "icon80.png", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "38mm" + }, + { + "size" : "44x44", + "idiom" : "watch", + "filename" : "icon88.png", + "scale" : "2x", + "role" : "longLook", + "subtype" : "42mm" + }, + { + "size" : "86x86", + "idiom" : "watch", + "filename" : "icon172.png", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "38mm" + }, + { + "size" : "98x98", + "idiom" : "watch", + "filename" : "icon196.png", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "42mm" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon172.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon172.png new file mode 100644 index 000000000..5bdc4462c Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon172.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon196.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon196.png new file mode 100644 index 000000000..a6823bc15 Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon196.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon48.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon48.png new file mode 100644 index 000000000..49888b2ac Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon48.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon55.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon55.png new file mode 100644 index 000000000..af342832c Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon55.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon58.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon58.png new file mode 100644 index 000000000..cf2587545 Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon58.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon80.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon80.png new file mode 100644 index 000000000..8d3721d19 Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon80.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon87.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon87.png new file mode 100644 index 000000000..beb6aeb68 Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon87.png differ diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/icon88.png b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon88.png new file mode 100644 index 000000000..c50a7663d Binary files /dev/null and b/WatchApp/Assets.xcassets/AppIcon.appiconset/icon88.png differ diff --git a/WatchApp/Assets.xcassets/Contents.json b/WatchApp/Assets.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/WatchApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/GalanceIcon.imageset/Contents.json b/WatchApp/Assets.xcassets/GalanceIcon.imageset/Contents.json new file mode 100644 index 000000000..68266cc07 --- /dev/null +++ b/WatchApp/Assets.xcassets/GalanceIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "icon80.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/GalanceIcon.imageset/icon80.png b/WatchApp/Assets.xcassets/GalanceIcon.imageset/icon80.png new file mode 100644 index 000000000..8d3721d19 Binary files /dev/null and b/WatchApp/Assets.xcassets/GalanceIcon.imageset/icon80.png differ diff --git a/WatchApp/Assets.xcassets/InvalidTransactionIcon.imageset/Contents.json b/WatchApp/Assets.xcassets/InvalidTransactionIcon.imageset/Contents.json new file mode 100644 index 000000000..8833ecf1d --- /dev/null +++ b/WatchApp/Assets.xcassets/InvalidTransactionIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "invalid.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/InvalidTransactionIcon.imageset/invalid.png b/WatchApp/Assets.xcassets/InvalidTransactionIcon.imageset/invalid.png new file mode 100644 index 000000000..d7e4674ee Binary files /dev/null and b/WatchApp/Assets.xcassets/InvalidTransactionIcon.imageset/invalid.png differ diff --git a/WatchApp/Assets.xcassets/MoveMoneyIcon.imageset/Contents.json b/WatchApp/Assets.xcassets/MoveMoneyIcon.imageset/Contents.json new file mode 100644 index 000000000..ee5763816 --- /dev/null +++ b/WatchApp/Assets.xcassets/MoveMoneyIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "move.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/MoveMoneyIcon.imageset/move.png b/WatchApp/Assets.xcassets/MoveMoneyIcon.imageset/move.png new file mode 100644 index 000000000..2e6e00636 Binary files /dev/null and b/WatchApp/Assets.xcassets/MoveMoneyIcon.imageset/move.png differ diff --git a/WatchApp/Assets.xcassets/ReceiveMoneyIcon.imageset/Contents.json b/WatchApp/Assets.xcassets/ReceiveMoneyIcon.imageset/Contents.json new file mode 100644 index 000000000..89309e05a --- /dev/null +++ b/WatchApp/Assets.xcassets/ReceiveMoneyIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "recv.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/ReceiveMoneyIcon.imageset/recv.png b/WatchApp/Assets.xcassets/ReceiveMoneyIcon.imageset/recv.png new file mode 100644 index 000000000..ede3a0772 Binary files /dev/null and b/WatchApp/Assets.xcassets/ReceiveMoneyIcon.imageset/recv.png differ diff --git a/WatchApp/Assets.xcassets/SentMoneyIcon.imageset/Contents.json b/WatchApp/Assets.xcassets/SentMoneyIcon.imageset/Contents.json new file mode 100644 index 000000000..66c2bebd6 --- /dev/null +++ b/WatchApp/Assets.xcassets/SentMoneyIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "send.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/SentMoneyIcon.imageset/send.png b/WatchApp/Assets.xcassets/SentMoneyIcon.imageset/send.png new file mode 100644 index 000000000..918f2b483 Binary files /dev/null and b/WatchApp/Assets.xcassets/SentMoneyIcon.imageset/send.png differ diff --git a/WatchApp/Assets.xcassets/transactionHeaderBackground.imageset/Contents.json b/WatchApp/Assets.xcassets/transactionHeaderBackground.imageset/Contents.json new file mode 100644 index 000000000..d66fd6da6 --- /dev/null +++ b/WatchApp/Assets.xcassets/transactionHeaderBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "transactionHeaderBackground.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/transactionHeaderBackground.imageset/transactionHeaderBackground.png b/WatchApp/Assets.xcassets/transactionHeaderBackground.imageset/transactionHeaderBackground.png new file mode 100644 index 000000000..2fe24eeee Binary files /dev/null and b/WatchApp/Assets.xcassets/transactionHeaderBackground.imageset/transactionHeaderBackground.png differ diff --git a/WatchApp/Base.lproj/Interface.storyboard b/WatchApp/Base.lproj/Interface.storyboard new file mode 100644 index 000000000..fc7cef849 --- /dev/null +++ b/WatchApp/Base.lproj/Interface.storyboard @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/WatchApp/Info.plist b/WatchApp/Info.plist new file mode 100644 index 000000000..63f57d56a --- /dev/null +++ b/WatchApp/Info.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ƀreadwallet + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.5.4 + CFBundleSignature + ???? + CFBundleVersion + 15 + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + WKCompanionAppBundleIdentifier + org.voisine.breadwallet + WKWatchKitApp + + + diff --git a/WatchApp/LoadingIndicator.xcassets/Contents.json b/WatchApp/LoadingIndicator.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator1.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator1.imageset/Contents.json new file mode 100644 index 000000000..95619943e --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator1@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator1.imageset/LoadingIndicator1@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator1.imageset/LoadingIndicator1@2x.png new file mode 100644 index 000000000..84ab9e971 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator1.imageset/LoadingIndicator1@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator10.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator10.imageset/Contents.json new file mode 100644 index 000000000..025ade0f2 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator10.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator10@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator10.imageset/LoadingIndicator10@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator10.imageset/LoadingIndicator10@2x.png new file mode 100644 index 000000000..e22f9634c Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator10.imageset/LoadingIndicator10@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator11.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator11.imageset/Contents.json new file mode 100644 index 000000000..84e153a75 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator11.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator11@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator11.imageset/LoadingIndicator11@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator11.imageset/LoadingIndicator11@2x.png new file mode 100644 index 000000000..0e129f078 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator11.imageset/LoadingIndicator11@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator12.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator12.imageset/Contents.json new file mode 100644 index 000000000..295a5c2eb --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator12.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator12@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator12.imageset/LoadingIndicator12@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator12.imageset/LoadingIndicator12@2x.png new file mode 100644 index 000000000..78c8b4aa7 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator12.imageset/LoadingIndicator12@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator13.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator13.imageset/Contents.json new file mode 100644 index 000000000..22b05261e --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator13.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator13@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator13.imageset/LoadingIndicator13@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator13.imageset/LoadingIndicator13@2x.png new file mode 100644 index 000000000..2c6b99d8b Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator13.imageset/LoadingIndicator13@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator14.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator14.imageset/Contents.json new file mode 100644 index 000000000..e5a5ce705 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator14.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator14@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator14.imageset/LoadingIndicator14@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator14.imageset/LoadingIndicator14@2x.png new file mode 100644 index 000000000..4f279c726 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator14.imageset/LoadingIndicator14@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator15.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator15.imageset/Contents.json new file mode 100644 index 000000000..1c54b131c --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator15.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator15@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator15.imageset/LoadingIndicator15@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator15.imageset/LoadingIndicator15@2x.png new file mode 100644 index 000000000..5b8d5a632 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator15.imageset/LoadingIndicator15@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator16.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator16.imageset/Contents.json new file mode 100644 index 000000000..fa41a9d68 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator16.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator16@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator16.imageset/LoadingIndicator16@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator16.imageset/LoadingIndicator16@2x.png new file mode 100644 index 000000000..9eb448595 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator16.imageset/LoadingIndicator16@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator17.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator17.imageset/Contents.json new file mode 100644 index 000000000..14d109251 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator17.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator17@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator17.imageset/LoadingIndicator17@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator17.imageset/LoadingIndicator17@2x.png new file mode 100644 index 000000000..93cf58d25 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator17.imageset/LoadingIndicator17@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator18.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator18.imageset/Contents.json new file mode 100644 index 000000000..79c249cd6 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator18.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator18@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator18.imageset/LoadingIndicator18@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator18.imageset/LoadingIndicator18@2x.png new file mode 100644 index 000000000..b879a22e4 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator18.imageset/LoadingIndicator18@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator19.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator19.imageset/Contents.json new file mode 100644 index 000000000..e2bee3cbd --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator19.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator19@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator19.imageset/LoadingIndicator19@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator19.imageset/LoadingIndicator19@2x.png new file mode 100644 index 000000000..ed6b37ebb Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator19.imageset/LoadingIndicator19@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator2.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator2.imageset/Contents.json new file mode 100644 index 000000000..1a6ada92f --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator2.imageset/LoadingIndicator2@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator2.imageset/LoadingIndicator2@2x.png new file mode 100644 index 000000000..9776ca42b Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator2.imageset/LoadingIndicator2@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator20.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator20.imageset/Contents.json new file mode 100644 index 000000000..88ee2f7bb --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator20.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator20@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator20.imageset/LoadingIndicator20@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator20.imageset/LoadingIndicator20@2x.png new file mode 100644 index 000000000..9c6a0a961 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator20.imageset/LoadingIndicator20@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator21.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator21.imageset/Contents.json new file mode 100644 index 000000000..8d464ab4d --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator21.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator21@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator21.imageset/LoadingIndicator21@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator21.imageset/LoadingIndicator21@2x.png new file mode 100644 index 000000000..16dd653b7 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator21.imageset/LoadingIndicator21@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator22.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator22.imageset/Contents.json new file mode 100644 index 000000000..74dba2692 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator22.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator22@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator22.imageset/LoadingIndicator22@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator22.imageset/LoadingIndicator22@2x.png new file mode 100644 index 000000000..afb66d848 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator22.imageset/LoadingIndicator22@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator23.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator23.imageset/Contents.json new file mode 100644 index 000000000..e30b0a432 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator23.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator23@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator23.imageset/LoadingIndicator23@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator23.imageset/LoadingIndicator23@2x.png new file mode 100644 index 000000000..dd5323a4b Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator23.imageset/LoadingIndicator23@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator24.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator24.imageset/Contents.json new file mode 100644 index 000000000..5b3e18dad --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator24.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator24@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator24.imageset/LoadingIndicator24@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator24.imageset/LoadingIndicator24@2x.png new file mode 100644 index 000000000..a85375e18 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator24.imageset/LoadingIndicator24@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator25.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator25.imageset/Contents.json new file mode 100644 index 000000000..f0182654c --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator25.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator25@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator25.imageset/LoadingIndicator25@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator25.imageset/LoadingIndicator25@2x.png new file mode 100644 index 000000000..fcc5662e2 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator25.imageset/LoadingIndicator25@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator26.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator26.imageset/Contents.json new file mode 100644 index 000000000..19ec0ed96 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator26.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator26@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator26.imageset/LoadingIndicator26@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator26.imageset/LoadingIndicator26@2x.png new file mode 100644 index 000000000..f785ace8c Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator26.imageset/LoadingIndicator26@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator27.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator27.imageset/Contents.json new file mode 100644 index 000000000..5c6f16784 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator27.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator27@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator27.imageset/LoadingIndicator27@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator27.imageset/LoadingIndicator27@2x.png new file mode 100644 index 000000000..c7149d91b Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator27.imageset/LoadingIndicator27@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator28.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator28.imageset/Contents.json new file mode 100644 index 000000000..c48db42ed --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator28.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator28@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator28.imageset/LoadingIndicator28@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator28.imageset/LoadingIndicator28@2x.png new file mode 100644 index 000000000..5b9cec2c7 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator28.imageset/LoadingIndicator28@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator29.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator29.imageset/Contents.json new file mode 100644 index 000000000..704b19f35 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator29.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator29@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator29.imageset/LoadingIndicator29@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator29.imageset/LoadingIndicator29@2x.png new file mode 100644 index 000000000..6d4234934 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator29.imageset/LoadingIndicator29@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator3.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator3.imageset/Contents.json new file mode 100644 index 000000000..df32cec28 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator3@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator3.imageset/LoadingIndicator3@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator3.imageset/LoadingIndicator3@2x.png new file mode 100644 index 000000000..5def777f4 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator3.imageset/LoadingIndicator3@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator30.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator30.imageset/Contents.json new file mode 100644 index 000000000..0aa8a80e9 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator30.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator30@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator30.imageset/LoadingIndicator30@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator30.imageset/LoadingIndicator30@2x.png new file mode 100644 index 000000000..593953e62 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator30.imageset/LoadingIndicator30@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator4.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator4.imageset/Contents.json new file mode 100644 index 000000000..b20aa17ab --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator4@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator4.imageset/LoadingIndicator4@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator4.imageset/LoadingIndicator4@2x.png new file mode 100644 index 000000000..f9efb8764 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator4.imageset/LoadingIndicator4@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator5.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator5.imageset/Contents.json new file mode 100644 index 000000000..cdf5fd303 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator5@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator5.imageset/LoadingIndicator5@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator5.imageset/LoadingIndicator5@2x.png new file mode 100644 index 000000000..d881f96d6 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator5.imageset/LoadingIndicator5@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator6.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator6.imageset/Contents.json new file mode 100644 index 000000000..a74518087 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator6@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator6.imageset/LoadingIndicator6@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator6.imageset/LoadingIndicator6@2x.png new file mode 100644 index 000000000..e0671f423 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator6.imageset/LoadingIndicator6@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator7.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator7.imageset/Contents.json new file mode 100644 index 000000000..32b0924ad --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator7@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator7.imageset/LoadingIndicator7@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator7.imageset/LoadingIndicator7@2x.png new file mode 100644 index 000000000..8400bdadb Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator7.imageset/LoadingIndicator7@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator8.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator8.imageset/Contents.json new file mode 100644 index 000000000..bf0c585a2 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator8@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator8.imageset/LoadingIndicator8@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator8.imageset/LoadingIndicator8@2x.png new file mode 100644 index 000000000..90e59188b Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator8.imageset/LoadingIndicator8@2x.png differ diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator9.imageset/Contents.json b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator9.imageset/Contents.json new file mode 100644 index 000000000..b416407c2 --- /dev/null +++ b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator9.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LoadingIndicator9@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WatchApp/LoadingIndicator.xcassets/LoadingIndicator9.imageset/LoadingIndicator9@2x.png b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator9.imageset/LoadingIndicator9@2x.png new file mode 100644 index 000000000..6a014bc34 Binary files /dev/null and b/WatchApp/LoadingIndicator.xcassets/LoadingIndicator9.imageset/LoadingIndicator9@2x.png differ