diff --git a/.gitignore b/.gitignore index 9d12ca082..28b294117 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ litewallet-partner-api-ios /partner-keys.plist partner-keys.plist GoogleService-Info.plist +*.gpx diff --git a/Dakar, Senegal.gpx b/Dakar, Senegal.gpx new file mode 100644 index 000000000..25366a782 --- /dev/null +++ b/Dakar, Senegal.gpx @@ -0,0 +1,19 @@ + + + + new + + gpx.studio + + + + + new + Cycling + + + 23.1 + + + + \ No newline at end of file diff --git a/litewallet.xcodeproj/project.pbxproj b/litewallet.xcodeproj/project.pbxproj index d8cc600ca..1e7078aed 100644 --- a/litewallet.xcodeproj/project.pbxproj +++ b/litewallet.xcodeproj/project.pbxproj @@ -76,7 +76,6 @@ 24470E4723A6B6E900ADDA27 /* MockSeeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24470E4623A6B6E900ADDA27 /* MockSeeds.swift */; }; 24670EAE2368EDE7006093E0 /* LFColorPalette.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 24670EAC2368EDE7006093E0 /* LFColorPalette.xcassets */; }; 2485F7D023728C19005962F1 /* RELEASE_NOTES.md in Resources */ = {isa = PBXBuildFile; fileRef = 2485F7CE23728C19005962F1 /* RELEASE_NOTES.md */; }; - 248BFE2423AAD53700CE1A71 /* BuyTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248BFE2323AAD53700CE1A71 /* BuyTableViewController.swift */; }; 248BFE2623AB302200CE1A71 /* BuyWKWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248BFE2523AB302200CE1A71 /* BuyWKWebViewController.swift */; }; 2494037623AD35C000369261 /* BuyWKWebVCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2494037523AD35C000369261 /* BuyWKWebVCTests.swift */; }; 2494037823AD53B900369261 /* ChildViewTransitioningDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2494037723AD53B900369261 /* ChildViewTransitioningDelegate.swift */; }; @@ -89,8 +88,6 @@ 24B523AD238A53DC0030594D /* BIP39Words.plist in Resources */ = {isa = PBXBuildFile; fileRef = 24B523AF238A53DC0030594D /* BIP39Words.plist */; }; 24B8FAC4216128A000A155B1 /* PartnerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B8FAC3216128A000A155B1 /* PartnerData.swift */; }; 24B8FAD22162B10200A155B1 /* BuyCenterWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B8FAD12162B10200A155B1 /* BuyCenterWebViewController.swift */; }; - 24B8FAD72162B6FB00A155B1 /* bitrefill_index.html in Resources */ = {isa = PBXBuildFile; fileRef = 24B8FAD62162B6FB00A155B1 /* bitrefill_index.html */; }; - 24B8FADC2162D29100A155B1 /* general.css in Resources */ = {isa = PBXBuildFile; fileRef = 24B8FADB2162D29100A155B1 /* general.css */; }; 24B8FADF2163C4D400A155B1 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B8FADE2163C4D400A155B1 /* Currency.swift */; }; 24BA90C62410129E001E3825 /* FeeSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BA90C52410129E001E3825 /* FeeSelectorView.swift */; }; 24D5F23822599C0B00225462 /* BarlowSemiCondensed-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 24D5F22522599C0900225462 /* BarlowSemiCondensed-Italic.ttf */; }; @@ -279,7 +276,6 @@ C350788C27DCB10700A50819 /* TextView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C350788B27DCB10700A50819 /* TextView+Extension.swift */; }; C3543A27264AFE490005D17A /* LocaleChangeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3543A26264AFE490005D17A /* LocaleChangeView.swift */; }; C3543A29264AFE720005D17A /* LocaleChangeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3543A28264AFE720005D17A /* LocaleChangeViewModel.swift */; }; - C354C4632590059500675E0E /* TransactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354C4622590059500675E0E /* TransactionsViewModel.swift */; }; C35ABD232574070A002BB9BB /* PartnersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35ABD222574070A002BB9BB /* PartnersView.swift */; }; C35ABD332574073F002BB9BB /* PartnersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35ABD322574073F002BB9BB /* PartnersViewModel.swift */; }; C35C1220293D464A0009022D /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = C35C121F293D464A0009022D /* FirebaseAnalytics */; }; @@ -293,11 +289,15 @@ C36DBF6128F1988900FBCB24 /* LocalWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36DBF6028F1988900FBCB24 /* LocalWebViewModel.swift */; }; C39443F9269DDAD3002703E9 /* LitewalletIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39443F8269DDAD3002703E9 /* LitewalletIconView.swift */; }; C39A71472608CB4300E7B640 /* EmptyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39A71462608CB4300E7B640 /* EmptyTableViewCell.swift */; }; + C3B419CB2BFCF14100EBD935 /* BuyHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B419CA2BFCF14100EBD935 /* BuyHostingController.swift */; }; + C3B419CD2BFCF17600EBD935 /* BuyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B419CC2BFCF17600EBD935 /* BuyView.swift */; }; C3B7C3B9255EABBF00E98A64 /* SupportSafariViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B7C3B8255EABBF00E98A64 /* SupportSafariViewModel.swift */; }; C3B7C3EE255FF59200E98A64 /* ConstantsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B7C3ED255FF59200E98A64 /* ConstantsTests.swift */; }; C3BD4A5325975C6000D97079 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BD4A5225975C6000D97079 /* View+Extension.swift */; }; C3C8973825CD6B9300241FBE /* HostingTransactionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C8973725CD6B9300241FBE /* HostingTransactionCell.swift */; }; C3D4379F2566EA3E00F423E1 /* LWActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4379E2566EA3E00F423E1 /* LWActivityIndicator.swift */; }; + C3DBBE312BFE15AF00B95939 /* BuyTileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DBBE302BFE15AF00B95939 /* BuyTileView.swift */; }; + C3E5A9052BFDEEF1002FBE04 /* BuyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5A9042BFDEEF1002FBE04 /* BuyViewModel.swift */; }; C3E751C22AF689BA005571CA /* BRKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E751C12AF689BA005571CA /* BRKeyExtension.swift */; }; C3E751C42AF68A50005571CA /* BRAddressExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E751C32AF68A50005571CA /* BRAddressExtension.swift */; }; C3E751C62AF68A8E005571CA /* BRTxInputExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E751C52AF68A8E005571CA /* BRTxInputExtension.swift */; }; @@ -310,12 +310,14 @@ C3EFA9A12650807B005C59B5 /* LockScreenHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3EFA9A02650807B005C59B5 /* LockScreenHeaderView.swift */; }; C3EFA9A3265080FF005C59B5 /* LockScreenHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3EFA9A2265080FF005C59B5 /* LockScreenHeaderViewModel.swift */; }; C3EFA9A62651A808005C59B5 /* LockScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3EFA9A52651A808005C59B5 /* LockScreenTests.swift */; }; - C3F7BCDC25FEC6AD00694C28 /* FailedAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F7BCD925FEC6AC00694C28 /* FailedAlertView.swift */; }; - C3F7BCDE25FEC6AD00694C28 /* AlertFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F7BCDB25FEC6AC00694C28 /* AlertFailureView.swift */; }; C3F7BD0325FEC77100694C28 /* TransactionModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F7BD0225FEC77100694C28 /* TransactionModalView.swift */; }; + C3F8F13C2C049A4A006C3211 /* LocaleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F8F13B2C049A4A006C3211 /* LocaleTests.swift */; }; + C3F8F13E2C04C3A7006C3211 /* MoonpayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F8F13D2C04C3A7006C3211 /* MoonpayHelper.swift */; }; + C3F8F1422C04DEA2006C3211 /* NoBuyTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F8F1412C04DEA2006C3211 /* NoBuyTabBarViewController.swift */; }; + C3F8F1442C04F6BE006C3211 /* Dakar, Senegal.gpx in Resources */ = {isa = PBXBuildFile; fileRef = C3F8F1432C04F6BE006C3211 /* Dakar, Senegal.gpx */; }; + C3F8F1462C05269A006C3211 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C3F8F1452C05269A006C3211 /* GoogleService-Info.plist */; settings = {ASSET_TAGS = ("initial-resources", ); }; }; C3FF4D5F28AC5A5800713139 /* SendAddressCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FF4D5E28AC5A5800713139 /* SendAddressCellView.swift */; }; C3FF4D6128AC5AC100713139 /* SendAddressCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3FF4D6028AC5AC100713139 /* SendAddressCellViewModel.swift */; }; - C738969F2BEA3C0200029095 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C738969E2BEA3C0200029095 /* GoogleService-Info.plist */; settings = {ASSET_TAGS = ("initial-resources", ); }; }; C73896A12BEA3C4900029095 /* partner-keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = C73896A02BEA3C4900029095 /* partner-keys.plist */; settings = {ASSET_TAGS = ("initial-resources", ); }; }; CE03EC741EF256AC0038E3A8 /* SimpleUTXO.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE03EC731EF256AC0038E3A8 /* SimpleUTXO.swift */; }; CE0CD1591DBFBCF5004023DA /* ModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0CD1581DBFBCF5004023DA /* ModalPresenter.swift */; }; @@ -751,7 +753,6 @@ 2465873A23A5AAD100A32E9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24670EAC2368EDE7006093E0 /* LFColorPalette.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = LFColorPalette.xcassets; sourceTree = ""; }; 2485F7CE23728C19005962F1 /* RELEASE_NOTES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = RELEASE_NOTES.md; sourceTree = ""; }; - 248BFE2323AAD53700CE1A71 /* BuyTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyTableViewController.swift; sourceTree = ""; }; 248BFE2523AB302200CE1A71 /* BuyWKWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyWKWebViewController.swift; sourceTree = ""; }; 2494037523AD35C000369261 /* BuyWKWebVCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyWKWebVCTests.swift; sourceTree = ""; }; 2494037723AD53B900369261 /* ChildViewTransitioningDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildViewTransitioningDelegate.swift; sourceTree = ""; }; @@ -766,8 +767,6 @@ 24B523B0238A53E40030594D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hans"; path = "zh-Hans.lproj/BIP39Words.plist"; sourceTree = ""; }; 24B8FAC3216128A000A155B1 /* PartnerData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerData.swift; sourceTree = ""; }; 24B8FAD12162B10200A155B1 /* BuyCenterWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyCenterWebViewController.swift; sourceTree = ""; }; - 24B8FAD62162B6FB00A155B1 /* bitrefill_index.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = bitrefill_index.html; sourceTree = ""; }; - 24B8FADB2162D29100A155B1 /* general.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = general.css; sourceTree = ""; }; 24B8FADE2163C4D400A155B1 /* Currency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = ""; }; 24B9621723BA66CC00ECD938 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/MainInterface.strings; sourceTree = ""; }; 24B9621923BA66CE00ECD938 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainInterface.strings"; sourceTree = ""; }; @@ -1428,7 +1427,6 @@ C350788B27DCB10700A50819 /* TextView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextView+Extension.swift"; sourceTree = ""; }; C3543A26264AFE490005D17A /* LocaleChangeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleChangeView.swift; sourceTree = ""; }; C3543A28264AFE720005D17A /* LocaleChangeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleChangeViewModel.swift; sourceTree = ""; }; - C354C4622590059500675E0E /* TransactionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsViewModel.swift; sourceTree = ""; }; C35ABD222574070A002BB9BB /* PartnersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnersView.swift; sourceTree = ""; }; C35ABD322574073F002BB9BB /* PartnersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnersViewModel.swift; sourceTree = ""; }; C361F48128B368BC00E9798F /* AddressFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressFieldView.swift; sourceTree = ""; }; @@ -1439,6 +1437,8 @@ C39443F8269DDAD3002703E9 /* LitewalletIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LitewalletIconView.swift; sourceTree = ""; }; C39A71462608CB4300E7B640 /* EmptyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTableViewCell.swift; sourceTree = ""; }; C3ACF2DE25DED601008671D4 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + C3B419CA2BFCF14100EBD935 /* BuyHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyHostingController.swift; sourceTree = ""; }; + C3B419CC2BFCF17600EBD935 /* BuyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyView.swift; sourceTree = ""; }; C3B7C3B8255EABBF00E98A64 /* SupportSafariViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportSafariViewModel.swift; sourceTree = ""; }; C3B7C3ED255FF59200E98A64 /* ConstantsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsTests.swift; sourceTree = ""; }; C3BD4A5225975C6000D97079 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; @@ -1447,6 +1447,8 @@ C3BDB42826CC0338004DAE77 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; C3C8973725CD6B9300241FBE /* HostingTransactionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingTransactionCell.swift; sourceTree = ""; }; C3D4379E2566EA3E00F423E1 /* LWActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LWActivityIndicator.swift; sourceTree = ""; }; + C3DBBE302BFE15AF00B95939 /* BuyTileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyTileView.swift; sourceTree = ""; }; + C3E5A9042BFDEEF1002FBE04 /* BuyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyViewModel.swift; sourceTree = ""; }; C3E751C12AF689BA005571CA /* BRKeyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRKeyExtension.swift; sourceTree = ""; }; C3E751C32AF68A50005571CA /* BRAddressExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAddressExtension.swift; sourceTree = ""; }; C3E751C52AF68A8E005571CA /* BRTxInputExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRTxInputExtension.swift; sourceTree = ""; }; @@ -1459,12 +1461,14 @@ C3EFA9A02650807B005C59B5 /* LockScreenHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenHeaderView.swift; sourceTree = ""; }; C3EFA9A2265080FF005C59B5 /* LockScreenHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenHeaderViewModel.swift; sourceTree = ""; }; C3EFA9A52651A808005C59B5 /* LockScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenTests.swift; sourceTree = ""; }; - C3F7BCD925FEC6AC00694C28 /* FailedAlertView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FailedAlertView.swift; sourceTree = ""; }; - C3F7BCDB25FEC6AC00694C28 /* AlertFailureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertFailureView.swift; sourceTree = ""; }; C3F7BD0225FEC77100694C28 /* TransactionModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionModalView.swift; sourceTree = ""; }; + C3F8F13B2C049A4A006C3211 /* LocaleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleTests.swift; sourceTree = ""; }; + C3F8F13D2C04C3A7006C3211 /* MoonpayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoonpayHelper.swift; sourceTree = ""; }; + C3F8F1412C04DEA2006C3211 /* NoBuyTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoBuyTabBarViewController.swift; sourceTree = ""; }; + C3F8F1432C04F6BE006C3211 /* Dakar, Senegal.gpx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "Dakar, Senegal.gpx"; sourceTree = ""; }; + C3F8F1452C05269A006C3211 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; C3FF4D5E28AC5A5800713139 /* SendAddressCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendAddressCellView.swift; sourceTree = ""; }; C3FF4D6028AC5AC100713139 /* SendAddressCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendAddressCellViewModel.swift; sourceTree = ""; }; - C738969E2BEA3C0200029095 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; C73896A02BEA3C4900029095 /* partner-keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "partner-keys.plist"; sourceTree = ""; }; CE03EC731EF256AC0038E3A8 /* SimpleUTXO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SimpleUTXO.swift; path = Models/SimpleUTXO.swift; sourceTree = ""; }; CE0CD1581DBFBCF5004023DA /* ModalPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ModalPresenter.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -2038,6 +2042,7 @@ children = ( 2427342B2381C21800E2D22F /* MainViewController.swift */, C32DB42E26488CAA00017D26 /* TabBarViewController.swift */, + C3F8F1412C04DEA2006C3211 /* NoBuyTabBarViewController.swift */, ); name = Main; sourceTree = ""; @@ -2112,36 +2117,10 @@ path = Storyboards; sourceTree = ""; }; - 24B8FAD52162B6D400A155B1 /* Bitrefill_Web */ = { - isa = PBXGroup; - children = ( - 24B8FAD62162B6FB00A155B1 /* bitrefill_index.html */, - 24B8FADA2162CF7600A155B1 /* css */, - 24B8FAD92162CF7000A155B1 /* js */, - ); - name = Bitrefill_Web; - sourceTree = ""; - }; - 24B8FAD92162CF7000A155B1 /* js */ = { - isa = PBXGroup; - children = ( - ); - name = js; - sourceTree = ""; - }; - 24B8FADA2162CF7600A155B1 /* css */ = { - isa = PBXGroup; - children = ( - 24B8FADB2162D29100A155B1 /* general.css */, - ); - name = css; - sourceTree = ""; - }; 24C516502158820E007CE038 /* Buy */ = { isa = PBXGroup; children = ( 24B8FAD12162B10200A155B1 /* BuyCenterWebViewController.swift */, - 248BFE2323AAD53700CE1A71 /* BuyTableViewController.swift */, 248BFE2523AB302200CE1A71 /* BuyWKWebViewController.swift */, ); name = Buy; @@ -2160,6 +2139,7 @@ isa = PBXGroup; children = ( 584E25002951DAAA005E0E8B /* LanguageSelectionTests.swift */, + C3F8F13B2C049A4A006C3211 /* LocaleTests.swift */, ); path = "Language Selection Tests"; sourceTree = ""; @@ -2859,6 +2839,7 @@ 75A2A7871DA5934300A983D8 = { isa = PBXGroup; children = ( + C3F8F1432C04F6BE006C3211 /* Dakar, Senegal.gpx */, C33685082BECE8B10069CBC7 /* PrivacyInfo.xcprivacy */, 75A2A7921DA5934300A983D8 /* litewallet */, 2465873723A5AAD100A32E9E /* litewalletTests */, @@ -2916,7 +2897,6 @@ CE20C90F1DBE5B5100C8397A /* Views */, CEBF33021DDE177F00348FC6 /* ViewModels */, CE6D0E5A1E14BF8400137DF1 /* Models */, - 24B8FAD52162B6D400A155B1 /* Bitrefill_Web */, CE20C8F01DBAF6E100C8397A /* Extensions */, 22A9A9311DF61930000F0016 /* Platform */, CEAA9E9C1DC2F8270066731D /* Fonts */, @@ -2995,7 +2975,7 @@ isa = PBXGroup; children = ( C73896A02BEA3C4900029095 /* partner-keys.plist */, - C738969E2BEA3C0200029095 /* GoogleService-Info.plist */, + C3F8F1452C05269A006C3211 /* GoogleService-Info.plist */, C3188E2526431E750008ADD1 /* Debug-GoogleService-Info.plist */, ); name = LaunchDataResources; @@ -3045,6 +3025,7 @@ C35ABD07257404C6002BB9BB /* SwiftUI+UIKit */ = { isa = PBXGroup; children = ( + C3B419C92BFCF0C900EBD935 /* Buy */, C3423C272B7905330051BD6D /* SafariServices+Extension.swift */, C3423C292B7905330051BD6D /* SignupWebView.swift */, C3423C282B7905330051BD6D /* SignupWebViewModel.swift */, @@ -3057,7 +3038,6 @@ C3423C192B79039D0051BD6D /* LaunchCardHostingController.swift */, C3FF4D5D28AC5A2000713139 /* Send */, C3543A25264AFE190005D17A /* Settings */, - C3F7BCD825FEC69B00694C28 /* Alerts */, C32142E825C97CB900BECCD0 /* Transactions */, C35ABD08257404D2002BB9BB /* Partners */, C35ABD0925740518002BB9BB /* About */, @@ -3087,6 +3067,17 @@ name = About; sourceTree = ""; }; + C3B419C92BFCF0C900EBD935 /* Buy */ = { + isa = PBXGroup; + children = ( + C3B419CA2BFCF14100EBD935 /* BuyHostingController.swift */, + C3B419CC2BFCF17600EBD935 /* BuyView.swift */, + C3DBBE302BFE15AF00B95939 /* BuyTileView.swift */, + C3E5A9042BFDEEF1002FBE04 /* BuyViewModel.swift */, + ); + name = Buy; + sourceTree = ""; + }; C3B7C3EC255FF56100E98A64 /* Constants Tests */ = { isa = PBXGroup; children = ( @@ -3126,15 +3117,6 @@ path = "Lock Screen Tests"; sourceTree = ""; }; - C3F7BCD825FEC69B00694C28 /* Alerts */ = { - isa = PBXGroup; - children = ( - C3F7BCDB25FEC6AC00694C28 /* AlertFailureView.swift */, - C3F7BCD925FEC6AC00694C28 /* FailedAlertView.swift */, - ); - name = Alerts; - sourceTree = ""; - }; C3FF4D5D28AC5A2000713139 /* Send */ = { isa = PBXGroup; children = ( @@ -3406,7 +3388,6 @@ CEBF33031DDE17A600348FC6 /* Transaction.swift */, CE27F9581E2C8EA300F7F7F2 /* Amount.swift */, CE124CF71E67A8E500DFA146 /* TransactionDirection.swift */, - C354C4622590059500675E0E /* TransactionsViewModel.swift */, ); name = ViewModels; sourceTree = ""; @@ -3449,6 +3430,7 @@ CEBF292F1EF9D76F005C330A /* Environment.swift */, CEE20C331EA5B4550086F724 /* ArticleIds.swift */, CE3D4C581EF743EF0016B1C8 /* Functions.swift */, + C3F8F13D2C04C3A7006C3211 /* MoonpayHelper.swift */, ); name = Constants; sourceTree = ""; @@ -3730,7 +3712,6 @@ buildActionMask = 2147483647; files = ( C3423C492B796D820051BD6D /* Ko.mp3 in Resources */, - 24B8FAD72162B6FB00A155B1 /* bitrefill_index.html in Resources */, 75A2A79E1DA5934300A983D8 /* LaunchScreen.storyboard in Resources */, C33685092BECE8B10069CBC7 /* PrivacyInfo.xcprivacy in Resources */, C3423C3F2B796D820051BD6D /* Pt.mp3 in Resources */, @@ -3741,7 +3722,6 @@ 222319B21F279B3C00008F20 /* POSTBouncer.html in Resources */, C73896A12BEA3C4900029095 /* partner-keys.plist in Resources */, 24670EAE2368EDE7006093E0 /* LFColorPalette.xcassets in Resources */, - C738969F2BEA3C0200029095 /* GoogleService-Info.plist in Resources */, 24313CA523824F5800A83F69 /* Buy.storyboard in Resources */, 24D5F23B22599C0B00225462 /* BarlowSemiCondensed-Bold.ttf in Resources */, 24AF00FE221B331D00FF636F /* WarningConfirmation.storyboard in Resources */, @@ -3755,6 +3735,7 @@ C3423C452B796D820051BD6D /* 中國人.mp3 in Resources */, 24D5F25022599C0B00225462 /* BarlowSemiCondensed-Light.ttf in Resources */, C3423C472B796D820051BD6D /* Tr.mp3 in Resources */, + C3F8F1442C04F6BE006C3211 /* Dakar, Senegal.gpx in Resources */, 2494037F23AE0C7100369261 /* SyncProgressHeaderView.xib in Resources */, C3188E2726431E750008ADD1 /* Debug-GoogleService-Info.plist in Resources */, C3423C432B796D820051BD6D /* Uk.mp3 in Resources */, @@ -3763,7 +3744,6 @@ 24313C9F23824F5800A83F69 /* Animate.storyboard in Resources */, C3423C442B796D820051BD6D /* Fr.mp3 in Resources */, C3423C462B796D820051BD6D /* Id.mp3 in Resources */, - 24B8FADC2162D29100A155B1 /* general.css in Resources */, 24313CA323824F5800A83F69 /* Send.storyboard in Resources */, 2485F7D023728C19005962F1 /* RELEASE_NOTES.md in Resources */, 24B523AD238A53DC0030594D /* BIP39Words.plist in Resources */, @@ -3772,6 +3752,7 @@ C3423C402B796D820051BD6D /* De.mp3 in Resources */, 24DFCE6823B89CDE001F17F8 /* Settings.storyboard in Resources */, 24393B5C23C259400075218D /* Phrase.storyboard in Resources */, + C3F8F1462C05269A006C3211 /* GoogleService-Info.plist in Resources */, C3423C422B796D820051BD6D /* coinflip.aiff in Resources */, 75A2A79B1DA5934300A983D8 /* Assets.xcassets in Resources */, ); @@ -3904,6 +3885,7 @@ 24470E4523A608A700ADDA27 /* AmountTests.swift in Sources */, C3B7C3EE255FF59200E98A64 /* ConstantsTests.swift in Sources */, 24470E2323A5DB7D00ADDA27 /* WalletManagerTests.swift in Sources */, + C3F8F13C2C049A4A006C3211 /* LocaleTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4013,7 +3995,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 248BFE2423AAD53700CE1A71 /* BuyTableViewController.swift in Sources */, CEBF29301EF9D76F005C330A /* Environment.swift in Sources */, C39A71472608CB4300E7B640 /* EmptyTableViewCell.swift in Sources */, CEC6AA4D1DF0741100EE5AFD /* ModalDisplayable.swift in Sources */, @@ -4074,12 +4055,12 @@ 24016D9023F913C1006A6791 /* LWAnalytics.swift in Sources */, C3C8973825CD6B9300241FBE /* HostingTransactionCell.swift in Sources */, CE124CFC1E68932C00DFA146 /* FeeManager.swift in Sources */, + C3B419CB2BFCF14100EBD935 /* BuyHostingController.swift in Sources */, CEE20C381EA5B4680086F724 /* Strings.swift in Sources */, 24AF0101221B349100FF636F /* WarningConfirmationViewController.swift in Sources */, CE25BF8D1DF3B8A500BC67B6 /* InViewAlert.swift in Sources */, 22A9A9511DF61945000F0016 /* TxMetaData.swift in Sources */, CE8CD8E11E31976800785E02 /* LoginViewController.swift in Sources */, - C3F7BCDE25FEC6AD00694C28 /* AlertFailureView.swift in Sources */, CEC6AA391DEE10BA00EE5AFD /* UINavigationController+Extension.swift in Sources */, 584E24FC2951D476005E0E8B /* NSNotificationNameExtension.swift in Sources */, CECCE5B01E04AD7600D99448 /* DescriptionSendCell.swift in Sources */, @@ -4094,9 +4075,11 @@ CEC6F8451E886723000795B8 /* PaymentRequest.swift in Sources */, C3FF4D6128AC5AC100713139 /* SendAddressCellViewModel.swift in Sources */, CE4C1CC81ED88B600063E184 /* URLController.swift in Sources */, + C3DBBE312BFE15AF00B95939 /* BuyTileView.swift in Sources */, C30029EB25D019BC00F08C2B /* CopyButtonView.swift in Sources */, 24313C922382433700A83F69 /* LFModalReceiveQRViewController.swift in Sources */, CE20C9171DBE6F2A00C8397A /* UIButton+BRWAdditions.swift in Sources */, + C3F8F1422C04DEA2006C3211 /* NoBuyTabBarViewController.swift in Sources */, CEAA9E931DC110E70066731D /* WritePaperPhraseViewController.swift in Sources */, CE92F9F41DED59E80046B516 /* UIView+AnimationAdditions.swift in Sources */, 24D5F26F225A5BEA00225462 /* ContainerViewController.swift in Sources */, @@ -4109,12 +4092,12 @@ CE0CD1591DBFBCF5004023DA /* ModalPresenter.swift in Sources */, CEE20C2F1EA3E5820086F724 /* BlinkingView.swift in Sources */, CEC6AA441DEFCDE900EE5AFD /* ModalViewController.swift in Sources */, - C3F7BCDC25FEC6AD00694C28 /* FailedAlertView.swift in Sources */, 24313C8423820C4B00A83F69 /* ReceiveLTCViewController.swift in Sources */, CEEC70831E90C07C00EF788E /* Setting.swift in Sources */, CE45C1F91E74B400002C3847 /* ManageWalletViewController.swift in Sources */, CE92F9F01DED0C790046B516 /* PresentModalAnimator.swift in Sources */, CEAA9E951DC1659F0066731D /* UILabel+BRWAdditions.swift in Sources */, + C3F8F13E2C04C3A7006C3211 /* MoonpayHelper.swift in Sources */, CEBF292E1EF99E55005C330A /* LightWeightAlert.swift in Sources */, CEBF32EE1DDBC30000348FC6 /* ShadowButton.swift in Sources */, CEB909F71E5FE654001804DC /* EnterPhraseCollectionViewController.swift in Sources */, @@ -4130,6 +4113,7 @@ CE27F9591E2C8EA300F7F7F2 /* Amount.swift in Sources */, CE1280F61EEA855C00D27649 /* Date+Additions.swift in Sources */, C350788C27DCB10700A50819 /* TextView+Extension.swift in Sources */, + C3B419CD2BFCF17600EBD935 /* BuyView.swift in Sources */, CEEC708E1E954AAB00EF788E /* AboutCell.swift in Sources */, 22A9A9461DF61945000F0016 /* BRAPIClient.swift in Sources */, 24313C7E23820C1900A83F69 /* TransactionsViewController.swift in Sources */, @@ -4146,7 +4130,6 @@ CEF3E8321DE55540007C0A9E /* CheckView.swift in Sources */, CECCE5A91E0378FB00D99448 /* PinPadViewController.swift in Sources */, 584E24F32951C11A005E0E8B /* Localization.swift in Sources */, - C354C4632590059500675E0E /* TransactionsViewModel.swift in Sources */, C3423C1C2B7903CA0051BD6D /* LaunchView.swift in Sources */, C32DAE0725925B7E003FC978 /* Color+Extension.swift in Sources */, C36DBF5F28F18D2C00FBCB24 /* LocalWebView.swift in Sources */, @@ -4244,6 +4227,7 @@ CE6D0F971DE8B73A00BD4BCF /* ModalTransitionDelegate.swift in Sources */, C3E751C82AF68AEB005571CA /* UnsafeMutablePointerExtension.swift in Sources */, C3BD4A5325975C6000D97079 /* View+Extension.swift in Sources */, + C3E5A9052BFDEEF1002FBE04 /* BuyViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4502,7 +4486,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = litewallet/litewallet.entitlements; - CURRENT_PROJECT_VERSION = 240519.1; + CURRENT_PROJECT_VERSION = 240527.6; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ZV7987N2ZC; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = YES; @@ -4512,6 +4496,7 @@ "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/litewallet/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Litewallet; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -4859,7 +4844,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = litewallet/litewallet.entitlements; - CURRENT_PROJECT_VERSION = 240519.1; + CURRENT_PROJECT_VERSION = 240527.6; DEVELOPMENT_TEAM = ZV7987N2ZC; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -4868,6 +4853,7 @@ "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/litewallet/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Litewallet; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -5000,7 +4986,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = litewallet/litewallet.entitlements; - CURRENT_PROJECT_VERSION = 240519.1; + CURRENT_PROJECT_VERSION = 240527.6; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ZV7987N2ZC; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; @@ -5010,6 +4996,7 @@ "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/litewallet/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Litewallet; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/litewallet/AlertFailureView.swift b/litewallet/AlertFailureView.swift deleted file mode 100644 index 6e55c10cf..000000000 --- a/litewallet/AlertFailureView.swift +++ /dev/null @@ -1,51 +0,0 @@ -import SwiftUI - -struct AlertFailureView: View { - let alertFailureType: AlertFailureType - - let errorMessage: String - - init(alertFailureType: AlertFailureType, errorMessage: String) { - self.alertFailureType = alertFailureType - self.errorMessage = errorMessage - } - - var body: some View { - VStack { - Text(alertFailureType.header) - .foregroundColor(.white) - .font(Font(UIFont.barlowBold(size: 18.0))) - .padding() - - Divider() - .frame(maxHeight: 1.0) - .background(Color(UIColor.transparentWhite)) - - Image(systemName: "nosign") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 40, - height: 40, - alignment: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/) - .foregroundColor(.white) - .padding() - - Text(self.errorMessage.localizedCapitalized) - .foregroundColor(.white) - .font(Font(UIFont.barlowSemiBold(size: 16.0))) - .padding(.bottom, 60) - } - .background(Color(UIColor.gray)) - .cornerRadius(6.0) - } -} - -struct AlertFailureView_Previews: PreviewProvider { - static let alert = AlertFailureType.failedResolution - static let errorMessage = "Test Error" - - static var previews: some View { - AlertFailureView(alertFailureType: alert, errorMessage: errorMessage) - .environment(\.locale, .init(identifier: "en")) - } -} diff --git a/litewallet/ApplicationController.swift b/litewallet/ApplicationController.swift index 7e03eb85c..53cc08e21 100644 --- a/litewallet/ApplicationController.swift +++ b/litewallet/ApplicationController.swift @@ -1,5 +1,6 @@ import BackgroundTasks import StoreKit +import SwiftUI import UIKit let timeSinceLastExitKey = "TimeSinceLastExit" @@ -73,6 +74,8 @@ class ApplicationController: Subscriber, Trackable { } private func setup() { + setupDefaults() + countLaunches() setupRootViewController() window?.makeKeyAndVisible() offMainInitialization() @@ -316,3 +319,40 @@ class ApplicationController: Subscriber, Trackable { } } } + +extension ApplicationController { + func setupDefaults() { + if UserDefaults.standard.object(forKey: shouldRequireLoginTimeoutKey) == nil { + UserDefaults.standard.set(60.0 * 3.0, forKey: shouldRequireLoginTimeoutKey) // Default 3 min timeout + } + if UserDefaults.standard.object(forKey: hasSeenAnnounceView) == nil { + UserDefaults.standard.set(false, forKey: hasSeenAnnounceView) // Hasnt seen the Announce View + } + } + + func countLaunches() { + if var launchNumber = UserDefaults.standard.object(forKey: numberOfLitewalletLaunches) as? Int { + launchNumber += 1 + UserDefaults.standard.set(NSNumber(value: launchNumber), forKey: numberOfLitewalletLaunches) + if launchNumber == 5 { + if #available(iOS 14, *) { + if self.window != nil, + let scene = self.window?.windowScene + { + SKStoreReviewController.requestReview(in: scene) + } + + } else { + SKStoreReviewController.requestReview() + } + + LWAnalytics.logEventWithParameters(itemName: ._20200125_DSRR) + } + } else { + UserDefaults.standard.set(NSNumber(value: 1), forKey: numberOfLitewalletLaunches) + } + } + + func willResignActive() + {} +} diff --git a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/Contents.json b/litewallet/Assets.xcassets/Partners/moonpay-white-logo.imageset/Contents.json similarity index 65% rename from litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/Contents.json rename to litewallet/Assets.xcassets/Partners/moonpay-white-logo.imageset/Contents.json index abe66c16b..007d4b52e 100644 --- a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/Contents.json +++ b/litewallet/Assets.xcassets/Partners/moonpay-white-logo.imageset/Contents.json @@ -1,17 +1,15 @@ { "images" : [ { - "filename" : "ud-Logo-Full-Light@1x.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "ud-Logo-Full-Light@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "ud-Logo-Full-Light@3x.png", + "filename" : "moonpay-white.png", "idiom" : "universal", "scale" : "3x" } diff --git a/litewallet/Assets.xcassets/Partners/moonpay-white-logo.imageset/moonpay-white.png b/litewallet/Assets.xcassets/Partners/moonpay-white-logo.imageset/moonpay-white.png new file mode 100644 index 000000000..cd873e0a3 Binary files /dev/null and b/litewallet/Assets.xcassets/Partners/moonpay-white-logo.imageset/moonpay-white.png differ diff --git a/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/Contents.json b/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/Contents.json deleted file mode 100644 index 2f61daa27..000000000 --- a/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "simplexLogo@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "simplexLogo@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/simplexLogo@1x.png b/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/simplexLogo@1x.png deleted file mode 100644 index de62d20e2..000000000 Binary files a/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/simplexLogo@1x.png and /dev/null differ diff --git a/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/simplexLogo@3x.png b/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/simplexLogo@3x.png deleted file mode 100644 index ceffea6fb..000000000 Binary files a/litewallet/Assets.xcassets/Partners/simplexLogo.imageset/simplexLogo@3x.png and /dev/null differ diff --git a/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/Contents.json b/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/Contents.json deleted file mode 100644 index 9967ae866..000000000 --- a/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "simplexColor@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "simplexColor@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/simplexColor@1x.png b/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/simplexColor@1x.png deleted file mode 100644 index a5f2cfcdc..000000000 Binary files a/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/simplexColor@1x.png and /dev/null differ diff --git a/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/simplexColor@3x.png b/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/simplexColor@3x.png deleted file mode 100644 index 2aa7afe31..000000000 Binary files a/litewallet/Assets.xcassets/Partners/simplexLogoTypeColor.imageset/simplexColor@3x.png and /dev/null differ diff --git a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@1x.png b/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@1x.png deleted file mode 100644 index 222a66f75..000000000 Binary files a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@1x.png and /dev/null differ diff --git a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@2x.png b/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@2x.png deleted file mode 100644 index 703d1468b..000000000 Binary files a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@2x.png and /dev/null differ diff --git a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@3x.png b/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@3x.png deleted file mode 100644 index 448f8c6b0..000000000 Binary files a/litewallet/Assets.xcassets/Partners/ud-color-logo.imageset/ud-Logo-Full-Light@3x.png and /dev/null differ diff --git a/litewallet/BartyCrouch.swift b/litewallet/BartyCrouch.swift index ee4ad5dde..af4fa82ad 100644 --- a/litewallet/BartyCrouch.swift +++ b/litewallet/BartyCrouch.swift @@ -3,25 +3,20 @@ import Foundation enum BartyCrouch { enum SupportedLanguage: String { // TODO: remove unsupported languages from the following cases list & add any missing languages - case arabic = "ar" case chineseSimplified = "zh-Hans" case chineseTraditional = "zh-Hant" case english = "en" case french = "fr" case german = "de" - case hindi = "hi" case indonesian = "id" case italian = "it" case japanese = "ja" case korean = "ko" - case malay = "ms" case portuguese = "pt" case russian = "ru" case spanish = "es" - case danish = "da" - case dutch = "nl" - case swedish = "sv" case turkey = "tr" + case ukrainian = "uk" } static func translate(key: String, translations: [SupportedLanguage: String], comment _: String? = nil) -> String { diff --git a/litewallet/BuyHostingController.swift b/litewallet/BuyHostingController.swift new file mode 100644 index 000000000..4e7426b77 --- /dev/null +++ b/litewallet/BuyHostingController.swift @@ -0,0 +1,51 @@ +import Foundation +import SwiftUI + +/// Moonpay: List supported countries endpoint +/// https://api.moonpay.com/v3/countries +/// - Parameter alphaCode2Char: String +/// - Parameter alphaCode3Char: String +/// - Parameter isBuyAllowed: Bool +/// - Parameter isSellAllowed: Bool +/// - Parameter countryName: String (name) +/// - Parameter isAllowedInCountry: Bool (isAllowed) +/// =================================== +/// Unused JSON parameters +/// "isNftAllowed": false +/// "isBalanceLedgerWithdrawAllowed": true, +/// "isSelfServeHighRisk": true, +/// "continent": "Asia", +/// "supportedDocuments": [ +/// "passport", +/// "driving_licence", +/// "national_identity_card", +/// "residence_permit", +/// ], +/// "suggestedDocument": "national_identity_card" +/// - Returns: MoonpayCountryData +public struct MoonpayCountryData: Codable, Hashable { + var alphaCode2Char: String + var alphaCode3Char: String + var isBuyAllowed: Bool + var isSellAllowed: Bool + var countryName: String + var isAllowedInCountry: Bool +} + +class BuyHostingController: UIHostingController { + var contentView: BuyView + + var isLoaded: Bool = false + + init() { + let buyViewModel = BuyViewModel() + contentView = BuyView(viewModel: buyViewModel) + + super.init(rootView: contentView) + } + + @available(*, unavailable) + @MainActor dynamic required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/litewallet/BuyTableViewController.swift b/litewallet/BuyTableViewController.swift deleted file mode 100644 index a1a416dc9..000000000 --- a/litewallet/BuyTableViewController.swift +++ /dev/null @@ -1,96 +0,0 @@ -import SafariServices -import SwiftUI -import UIKit -import WebKit - -class BuyTableViewController: UITableViewController, SFSafariViewControllerDelegate { - @IBOutlet var bitrefillLogoImageView: UIImageView! - @IBOutlet var bitrefillHeaderLabel: UILabel! - @IBOutlet var bitrefillDetailsLabel: UILabel! - @IBOutlet var bitrefillCellContainerView: UIView! - @IBAction func didTapBitrefill(_: UIButton) { - guard let url = URL(string: "https://www.bitrefill.com/?ref=bAshL935") - else { - return - } - - let sfSafariVC = SFSafariViewController(url: url) - sfSafariVC.delegate = self - present(sfSafariVC, animated: true) - } - - // MARK: Moonpay UI - - @IBOutlet var moonpayLogoImageView: UIImageView! - @IBOutlet var moonpayHeaderLabel: UILabel! - @IBOutlet var moonpayDetailsLabel: UILabel! - @IBOutlet var moonpayCellContainerView: UIView! - @IBOutlet var moonpaySegmentedControl: UISegmentedControl! - - @IBAction func didTapMoonpay(_: Any) { - let timestamp = Int(Date().timeIntervalSince1970) - - let urlString = APIServer.baseUrl + "moonpay/buy" + "?address=\(currentWalletAddress)&idate=\(timestamp)&uid=\(uuidString)&code=\(currencyCode)" - - guard let url = URL(string: urlString) else { return } - - let sfSafariVC = SFSafariViewController(url: url) - sfSafariVC.delegate = self - present(sfSafariVC, animated: true) - } - - private var currencyCode: String = "USD" - private let uuidString: String = UIDevice.current.identifierForVendor?.uuidString ?? "" - private let currentWalletAddress: String = WalletManager.sharedInstance.wallet?.receiveAddress ?? "" - - var store: Store? - var walletManager: WalletManager? - - override func viewDidLoad() { - super.viewDidLoad() - - let thinHeaderView = UIView() - thinHeaderView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 1.0) - thinHeaderView.backgroundColor = .white - tableView.tableHeaderView = thinHeaderView - tableView.tableFooterView = UIView() - - moonpaySegmentedControl.addTarget(self, action: #selector(didChangeCurrencyMoonpay), for: .valueChanged) - moonpaySegmentedControl.selectedSegmentIndex = PartnerFiatOptions.usd.index - moonpaySegmentedControl.selectedSegmentTintColor = .white - moonpaySegmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .normal) - moonpaySegmentedControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.liteWalletBlue], for: .selected) - - setupWkVCData() - - LWAnalytics.logEventWithParameters(itemName: ._20191105_DTBT) - } - - private func setupWkVCData() { - let bitrefillData = Partner.partnerDataArray()[0] - bitrefillLogoImageView.image = bitrefillData.logo - bitrefillHeaderLabel.text = bitrefillData.headerTitle - bitrefillDetailsLabel.text = bitrefillData.details - bitrefillCellContainerView.layer.cornerRadius = 6.0 - bitrefillCellContainerView.layer.borderColor = UIColor.white.cgColor - bitrefillCellContainerView.layer.borderWidth = 1.0 - bitrefillCellContainerView.clipsToBounds = true - - let moonpayData = Partner.partnerDataArray()[1] - moonpayLogoImageView.image = moonpayData.logo - moonpayHeaderLabel.text = moonpayData.headerTitle - moonpayDetailsLabel.text = moonpayData.details - moonpayCellContainerView.layer.cornerRadius = 6.0 - moonpayCellContainerView.layer.borderColor = UIColor.white.cgColor - moonpayCellContainerView.layer.borderWidth = 1.0 - moonpayCellContainerView.clipsToBounds = true - } - - @objc private func didChangeCurrencyMoonpay() { - if let code = PartnerFiatOptions(rawValue: moonpaySegmentedControl.selectedSegmentIndex)?.description { - currencyCode = code - } else { - print("Error: Code not found: \(moonpaySegmentedControl.selectedSegmentIndex)") - } - } -} diff --git a/litewallet/BuyTileView.swift b/litewallet/BuyTileView.swift new file mode 100644 index 000000000..9ab8be84f --- /dev/null +++ b/litewallet/BuyTileView.swift @@ -0,0 +1,45 @@ +import SwiftUI + +struct BuyTileView: View { + let codeCellHeight = 28.0 + let codeCellWidth = 80.0 + let smallPad = 3.0 + let buttonRegularFont: Font = .barlowSemiBold(size: 18.0) + + private var code: String + + init(code: String) { + self.code = code + } + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 14.0) + .foregroundColor(.litewalletBlue) + .frame(width: codeCellWidth, + height: codeCellHeight, + alignment: .center) + .overlay { + RoundedRectangle(cornerRadius: 14.0) + .stroke(.white, lineWidth: 0.5) + .frame(width: codeCellWidth, + height: codeCellHeight, + alignment: .center) + } + Text(code) + .foregroundColor(.white) + .font(buttonRegularFont) + .frame(width: codeCellWidth, + height: codeCellHeight, + alignment: .center) + } + .frame(width: codeCellWidth, + height: codeCellHeight, + alignment: .center) + .padding(.all, smallPad) + } +} + +#Preview { + BuyTileView(code: "USD") +} diff --git a/litewallet/BuyView.swift b/litewallet/BuyView.swift new file mode 100644 index 000000000..2cbeec278 --- /dev/null +++ b/litewallet/BuyView.swift @@ -0,0 +1,176 @@ +import SafariServices +import SwiftUI + +struct BuyView: View { + @ObservedObject + var viewModel: BuyViewModel + + let paragraphFont: Font = .barlowSemiBold(size: 20.0) + let calloutFont: Font = .barlowLight(size: 12.0) + let smallCalloutFont: Font = .barlowLight(size: 10.0) + + let genericPad = 25.0 + let selectButtonHeight = 35.0 + let smallPad = 6.0 + let buttonHeight = 44.0 + let pageHeight = 145.0 + let hugeFont = Font.barlowBold(size: 30.0) + let buttonLightFont: Font = .barlowLight(size: 15.0) + let buttonRegularFont: Font = .barlowSemiBold(size: 18.0) + let appDelegate = UIApplication.shared.delegate as! AppDelegate + + @State + private var shouldShowSafariVC = false + + @State + private var didTapCopy = false + // https://en.wikipedia.org/wiki/Template:Most_traded_currencies + /// As of 1716366977 + let rankedFiatCodes: [String] = ["USD", "EUR", "JPY", "GBP", + "CNY", "AUD", "CAD", "CHF", + "HKD", "SGD", "SEK", "NOK", + "NZD", "MXN", "TWD", "ZAR", + "BRL", "DKK", "PLN", "THB", + "ILS", "IDR", "CZK", "TRY", + "RON", "PEN"] + + init(viewModel: BuyViewModel) { + self.viewModel = viewModel + } + + var body: some View { + GeometryReader { geometry in + + let width = geometry.size.width + let height = geometry.size.height + + ZStack { + Color.liteWalletBlue.edgesIgnoringSafeArea(.all) + VStack { + Divider() + .frame(height: 1.0) + .background(.white) + .frame(maxWidth: .infinity, alignment: .center) + Text(S.BuyCenter.buyModalTitle.localize()) + .font(hugeFont) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .frame(idealHeight: buttonHeight) + .foregroundColor(.white) + .padding([.leading, .trailing], genericPad) + .padding(.all, genericPad) + + HStack { + VStack { + Picker(S.BuyCenter.buyDetail.localize() + " " + viewModel.receivingAddress, + selection: $viewModel.selectedCode) { + ForEach(rankedFiatCodes, id: \.self) { + BuyTileView(code: $0) + } + } + .pickerStyle(.wheel) + Spacer() + } + + VStack { + Text(S.BuyCenter.buyDetail.localize()) + .font(buttonRegularFont) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(idealHeight: buttonHeight) + .foregroundColor(.white) + .padding([.leading, .trailing], genericPad) + .padding(.top, 0.0) + HStack { + Text(viewModel.receivingAddress) + .font(buttonLightFont) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + .frame(idealHeight: buttonHeight) + .foregroundColor(didTapCopy ? .litewalletBlue : .white) + .padding([.top, .bottom], smallPad) + Image(systemName: "doc.on.doc") + .foregroundColor(didTapCopy ? .litewalletBlue : .white) + } + .onTapGesture { + UIPasteboard.general.string = viewModel.receivingAddress + didTapCopy.toggle() + delay(0.2) { + didTapCopy.toggle() + } + } + .padding([.leading, .trailing], genericPad) + Button(action: { + if viewModel.receivingAddress != "" { + let timestamp = Int(Date().timeIntervalSince1970) + viewModel.urlString = APIServer.baseUrl + "moonpay/buy" + "?address=\(viewModel.receivingAddress)&idate=\(timestamp)&uid=\(viewModel.uuidString)&code=\(viewModel.selectedCode)" + self.shouldShowSafariVC = true + } + + }) { + ZStack { + RoundedRectangle(cornerRadius: bigButtonCornerRadius) + .frame(width: width * 0.4, height: selectButtonHeight, alignment: .center) + .foregroundColor(.litewalletDarkBlue) + + Text(S.BuyCenter.buyButtonTitle.localize() + " \(viewModel.selectedCode)") + .frame(width: width * 0.4, height: selectButtonHeight, alignment: .center) + .font(paragraphFont) + .foregroundColor(.white) + .overlay( + RoundedRectangle(cornerRadius: bigButtonCornerRadius) + .stroke(.white, lineWidth: 1.0) + ) + } + } + .padding([.leading, .trailing], genericPad) + .sheet(isPresented: $shouldShowSafariVC) { + if let url = URL(string: viewModel.urlString) { + MoonpaySafariView(url: url) + } + } + } + } + .frame(height: height * 0.2 + ) + + Divider() + .frame(height: 1.0) + .background(.white) + .frame(maxWidth: .infinity, alignment: .center) + HStack { + Text(S.BuyCenter.buyMoonpayDetail.localize()) + .font(smallCalloutFont) + .multilineTextAlignment(.leading) + .frame(idealHeight: buttonHeight) + .foregroundColor(.white) + .padding(.leading, genericPad) + Image("moonpay-white-logo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 14.0) + .opacity(0.8) + Spacer() + } + .frame(height: 20.0) + Spacer() + } + } + } + } +} + +struct MoonpaySafariView: UIViewControllerRepresentable { + let url: URL + + func makeUIViewController(context _: UIViewControllerRepresentableContext) -> SFSafariViewController { + return SFSafariViewController(url: url) + } + + func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext) {} +} + +#Preview { + BuyView(viewModel: BuyViewModel()) +} diff --git a/litewallet/BuyViewModel.swift b/litewallet/BuyViewModel.swift new file mode 100644 index 000000000..154874aa5 --- /dev/null +++ b/litewallet/BuyViewModel.swift @@ -0,0 +1,73 @@ +import Foundation +import SwiftUI +import UIKit + +class BuyViewModel: ObservableObject { + // MARK: - Combine Variables + + @Published + var receivingAddress: String = "" + + @Published + var urlString: String = "" + + @Published + var selectedCode: String = "USD" + + @Published + var uuidString: String = UIDevice.current.identifierForVendor?.uuidString ?? "" + + init() { + receivingAddress = WalletManager.sharedInstance.wallet?.receiveAddress ?? "" + } + + func fetchCurrenciesCountries(completion: @escaping ([MoonpayCountryData]) -> Void) { + let url = URL(string: "https://api.moonpay.com/v3/countries")! + var request = URLRequest(url: url) + request.httpMethod = "GET" + request.timeoutInterval = 10 + request.allHTTPHeaderFields = ["accept": "application/json"] + + let task = URLSession.shared.dataTask(with: request) { data, _, error in + + if error == nil { + DispatchQueue.main.sync { + if let jsonData = try? JSONSerialization.jsonObject(with: data ?? Data(), options: []), + let jsonArray = jsonData as? [[String: Any]] + { + var dataArray: [MoonpayCountryData] = [] + + /// Filters allowed currencies and the top ranked currencies + for element in jsonArray { + if element["isBuyAllowed"] as? Bool == true && + element["isAllowed"] as? Bool == true + { + let alpha2 = element["alpha2"] as? String + let alpha3 = element["alpha3"] as? String + let name = element["name"] as? String + let isBuyAllowed = element["isBuyAllowed"] as? Bool + let isSellAllowed = element["isSellAllowed"] as? Bool + let isAllowed = element["isAllowed"] as? Bool + + let mpCountryData = MoonpayCountryData(alphaCode2Char: alpha2 ?? "", + alphaCode3Char: alpha3 ?? "", + isBuyAllowed: isBuyAllowed ?? false, + isSellAllowed: isSellAllowed ?? false, + countryName: name ?? "", + isAllowedInCountry: isAllowed ?? false) + + dataArray.append(mpCountryData) + } + } + completion(dataArray) + } + } + } else { + let currencyError: [String: String] = ["error": error?.localizedDescription ?? ""] + LWAnalytics.logEventWithParameters(itemName: ._20200112_ERR, properties: currencyError) + completion([]) + } + } + task.resume() + } +} diff --git a/litewallet/Constants/Constants+Events.swift b/litewallet/Constants/Constants+Events.swift index a0fc630e9..d600fc195 100644 --- a/litewallet/Constants/Constants+Events.swift +++ b/litewallet/Constants/Constants+Events.swift @@ -237,4 +237,7 @@ enum CustomEvent: String { /// Stop Push Notifications case _20240510_SPN = "stopped_push_notifications" + + /// Unsupported by Moonpay + case _20240527_UBM = "unsupported_by_moonpay" } diff --git a/litewallet/Constants/Strings.swift b/litewallet/Constants/Strings.swift index 82a06f58f..019eae46e 100644 --- a/litewallet/Constants/Strings.swift +++ b/litewallet/Constants/Strings.swift @@ -132,17 +132,6 @@ enum S { static let loadingRequest = Localization(key: "Send.loadingRequest", value: "Loading Request", comment: "Loading request activity view message") static let insufficientFunds = Localization(key: "Send.insufficientFunds", value: "Insufficient Funds", comment: "Insufficient funds error") static let barItemTitle = Localization(key: "Send.barItemTitle", value: "Send", comment: "Send Bar Item Title") - - enum UnstoppableDomains { - static let placeholder = Localization(key: "Send.UnstoppableDomains.placeholder", value: "Enter a .crypto or .zil domain", comment: "Enter a .crypto,.zil domain") - static let simplePlaceholder = Localization(key: "Send.UnstoppableDomains.simpleplaceholder", value: "Enter domain", comment: "Enter domain") - static let enterA = Localization(key: "Send.UnstoppableDomains.enterA", value: "Enter a", comment: "Enter a") - static let domain = Localization(key: "Send.UnstoppableDomains.domain", value: "domain", comment: "domain") - static let lookup = Localization(key: "Send.UnstoppableDomains.lookup", value: "Lookup", comment: "Lookup") - static let lookupFailureHeader = Localization(key: "Send.UnstoppableDomains.lookupFailureHeader", value: "LookupFailureHeader", comment: "lookupFailureHeader") - static let lookupDomainError = Localization(key: "Send.UnstoppableDomains.lookupDomainError", value: "LookupDomainError", comment: "LookupDomainError") - static let udSystemError = Localization(key: "Send.UnstoppableDomains.udSystemError", value: "UDSystemError", comment: "UDSystemError") - } } enum Receive { @@ -249,6 +238,11 @@ enum S { enum BuyCenter { static let title = Localization(key: "BuyCenter.title", value: "Buy Litecoin", comment: "Buy Center Title") static let buyModalTitle = Localization(key: "BuyCenter.ModalTitle", value: "Buy Łitecoin", comment: "Buy Modal Title") + static let buyButtonTitle = Localization(key: "BuyCenter.buyButtonTitle", value: "Buy Ł with", comment: "Buy ButtonTitle") + static let buyDetail = Localization(key: "BuyCenter.buyDetail", value: "Ł will be sent to:", comment: "Buy ButtonTitle") + static let buyFiatDetail = Localization(key: "BuyCenter.buyFiatDetail", value: "Using:", comment: "Using fiat") + static let buyMoonpayDetail = Localization(key: "BuyCenter.buyMoonpayDetail", value: "Powered by:", comment: "Powered by") + enum Cells { static let moonpayTitle = Localization(key: "BuyCenter.moonpayTitle", value: "Moonpay", comment: "Moonpay Title") static let moonpayFinancialDetails = Localization(key: "BuyCenter.moonpayFinancialDetails", value: "• Point 1 XXXXX\n• Point 2 XXXXn• XXX Point 3", comment: "Moonpay buy financial details") @@ -689,7 +683,6 @@ enum S { static let processingTime = Localization(key: "Confirmation.processingTime", value: "Processing time: This transaction will take %1$@ minutes to process.", comment: "eg. Processing time: This transaction will take 10-30 minutes to process.") static let processingAndDonationTime = Localization(key: "Confirmation.processingAndDonationTime", value: "Processing time: These transactions will take %1$@ minutes to process.", comment: "eg. Processing with Donation time: This transaction will take 10-30 minutes to process.") static let amountLabel = Localization(key: "Confirmation.amountLabel", value: "Amount to Send:", comment: "Amount to Send: ($1.00)") - static let donateLabel = Localization(key: "Confirmation.donateLabel", value: "Amount to Donate:", comment: "Amount to Donate: ($1.00)") static let totalLabel = Localization(key: "Confirmation.totalLabel", value: "Total Cost:", comment: "Total Cost: ($5.00)") static let amountDetailLabel = Localization(key: "Confirmation.amountDetailLabel", value: "Exchange details:", comment: "$53.09/L + 1.07%") diff --git a/litewallet/Currency.swift b/litewallet/Currency.swift index fccc34fff..8bfb58b01 100644 --- a/litewallet/Currency.swift +++ b/litewallet/Currency.swift @@ -15,7 +15,7 @@ enum PartnerFiatOptions: Int, CustomStringConvertible { case cad case aud case idr - case rub + case tur case jpy case eur case gbp @@ -30,7 +30,7 @@ enum PartnerFiatOptions: Int, CustomStringConvertible { case .cad: return "CAD" case .aud: return "AUD" case .idr: return "IDR" - case .rub: return "RUB" + case .tur: return "TRY" case .jpy: return "JPY" case .eur: return "EUR" case .gbp: return "GBP" @@ -43,7 +43,7 @@ enum PartnerFiatOptions: Int, CustomStringConvertible { case .cad: return 0 case .aud: return 1 case .idr: return 2 - case .rub: return 3 + case .tur: return 3 case .jpy: return 4 case .eur: return 5 case .gbp: return 6 diff --git a/litewallet/Extensions/UserDefaults+Additions.swift b/litewallet/Extensions/UserDefaults+Additions.swift index bb595aa6e..7c618dad3 100644 --- a/litewallet/Extensions/UserDefaults+Additions.swift +++ b/litewallet/Extensions/UserDefaults+Additions.swift @@ -53,7 +53,7 @@ extension UserDefaults { } else { currencyCode = defaults.string(forKey: defaultCurrencyCodeKey)! } - let acceptedCurrencyCodes = ["USD", "EUR", "JPY", "BGN", "CZK", "DKK", "GBP", "HUF", "PLN", "RON", "SEK", "CHF", "NOK", "HRK", "RUB", "TRY", "AUD", "BRL", "CAD", "CNY", "HKD", "IDR", "ILS", "INR", "KRW", "MXN", "MYR", "NZD", "PHP", "SDG", "THB", "ZAR"] + let acceptedCurrencyCodes = ["USD", "EUR", "JPY", "BGN", "CZK", "DKK", "GBP", "HUF", "PLN", "RON", "SEK", "CHF", "NOK", "HRK", "TRY", "AUD", "BRL", "CAD", "CNY", "HKD", "IDR", "ILS", "INR", "KRW", "MXN", "MYR", "NZD", "PHP", "SDG", "THB", "ZAR"] if !(acceptedCurrencyCodes.contains(currencyCode)) { return "USD" diff --git a/litewallet/FailedAlertView.swift b/litewallet/FailedAlertView.swift deleted file mode 100644 index 44cfee369..000000000 --- a/litewallet/FailedAlertView.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// FailedAlertView.swift -// loafwallet -// -// Created by Kerry Washington on 1/29/21. -// Copyright © 2021 Litecoin Foundation. All rights reserved. -// -import UIKit - -enum AlertFailureType { - case failedResolution - - var header: String { - switch self { - case .failedResolution: - return S.Send.UnstoppableDomains.lookupFailureHeader.localize() - } - } - - var subheader: String { - switch self { - case .failedResolution: - return S.SecurityAlerts.resolvedSuccessSubheader.localize() - } - } - - var icon: UIView { - return CheckView() - } -} - -extension AlertFailureType: Equatable {} - -func == (lhs: AlertFailureType, rhs: AlertFailureType) -> Bool { - switch (lhs, rhs) { - case (.failedResolution, .failedResolution): return true - } -} diff --git a/litewallet/ForgotView.swift b/litewallet/ForgotView.swift index 56647869f..8b1378917 100644 --- a/litewallet/ForgotView.swift +++ b/litewallet/ForgotView.swift @@ -1,115 +1 @@ -import SwiftUI -// DEV: To be removed in following issue https://github.com/litecoin-foundation/litewallet-ios/issues/177 -struct ForgotAlertView: View where Presenting: View { - // MARK: - Combine Variables - - @ObservedObject - var viewModel = ForgotAlertViewModel() - - @Binding - var isShowingForgot: Bool - - @State - var email = "" - - let presenting: Presenting - - var mainMessage: String - - @State - var detailMessage: String = S.LitecoinCard.resetPasswordDetail.localize() - - @State - var didCheckEmailAddress = false - - var body: some View { - GeometryReader { (_: GeometryProxy) in - HStack { - Spacer() - ZStack { - self.presenting.disabled(isShowingForgot) - VStack { - // Dismiss button - Button(action: { - viewModel.shouldDismissView { - self.isShowingForgot.toggle() - UIApplication.shared.endEditing() - } - - }) { - Image("whiteCross") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 15, - height: 15) - } - .frame(minWidth: 0, maxWidth: .infinity, alignment: .trailing) - - Text(S.LitecoinCard.forgotPassword.localize()) - .font(Font(UIFont.barlowSemiBold(size: 21.0))) - .padding(.bottom, 8) - .foregroundColor(Color.white) - - Text(detailMessage) - .font(Font(UIFont.barlowRegular(size: 18.0))) - .foregroundColor(Color.white) - .multilineTextAlignment(.leading) - .padding(.bottom, 12) - .padding([.leading, .trailing], 8) - .onReceive(viewModel.$detailMessage, perform: { updatedMessage in - detailMessage = updatedMessage - }) - - TextField(S.Receive.emailButton.localize(), text: $email) - .font(Font(UIFont.barlowMedium(size: 16.0))) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .keyboardType(.emailAddress) - .autocapitalization(.none) - .disableAutocorrection(true) - .padding(.all, 20) - - HStack { - // Reset password button - Button(action: { - withAnimation { - viewModel.emailString = email - viewModel.resetPassword { - DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { - self.isShowingForgot.toggle() - UIApplication.shared.endEditing() - didCheckEmailAddress = true - detailMessage = S.LitecoinCard.resetPasswordDetail.localize() - } - } - } - }) { - Text(S.LitecoinCard.resetPassword.localize()) - .frame(minWidth: 0, maxWidth: .infinity) - .font(Font(UIFont.barlowBold(size: 20.0))) - .foregroundColor(Color.white) - .padding(.all, 8) - .overlay( - RoundedRectangle(cornerRadius: 4) - .stroke(Color(UIColor.white), lineWidth: 1) - ) - .padding([.leading, .trailing], 20) - .padding([.top, .bottom], 10) - } - } - } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(Color.gray, lineWidth: 1.5) - ) - .background(Color(UIColor.liteWalletBlue)) - .cornerRadius(8) - .shadow(color: .black, radius: 10, x: 5, y: 5) - .opacity(self.isShowingForgot ? 1 : 0) - } - Spacer() - } - } - } -} diff --git a/litewallet/MainViewController.swift b/litewallet/MainViewController.swift index 2026678d1..78a3baae1 100644 --- a/litewallet/MainViewController.swift +++ b/litewallet/MainViewController.swift @@ -86,29 +86,68 @@ class MainViewController: UIViewController, Subscriber, LoginViewControllerDeleg func didUnlockLogin() { let hasSeenAnnounce = UserDefaults.standard.bool(forKey: hasSeenAnnounceView) - guard let tabVC = UIStoryboard(name: "Main", bundle: nil) - .instantiateViewController(withIdentifier: "TabBarViewController") - as? TabBarViewController - else { - NSLog("TabBarViewController not intialized") - return + // Check Locale - Assume unsupported if nil + let currentLocaleCountry = Locale.current.regionCode ?? "RU" + var userIsMoonPaySupported = true + for unsupportedLocale in UnsupportedCountries.allCases { + let truncatedCode = unsupportedLocale.localeCode.suffix(2) + + if currentLocaleCountry == truncatedCode { + userIsMoonPaySupported = false + let unsupportedDict: [String: String] = ["unsupported_country": unsupportedLocale.localeCode] + LWAnalytics.logEventWithParameters(itemName: ._20240527_UBM, properties: unsupportedDict) + break + } } - tabVC.store = store - tabVC.walletManager = walletManager + if userIsMoonPaySupported { + guard let tabVC = UIStoryboard(name: "Main", bundle: nil) + .instantiateViewController(withIdentifier: "TabBarViewController") + as? TabBarViewController + else { + NSLog("TabBarViewController not intialized") + return + } - addChildViewController(tabVC, layout: { - tabVC.view.constrain(toSuperviewEdges: nil) - tabVC.view.alpha = 0 - tabVC.view.layoutIfNeeded() - }) + tabVC.store = store + tabVC.walletManager = walletManager + tabVC.userIsMoonPaySupported = userIsMoonPaySupported - UIView.animate(withDuration: 0.3, delay: 0.1, options: .transitionCrossDissolve, animations: { - tabVC.view.alpha = 1 - }) { _ in - NSLog("US MainView Controller presented") - } + addChildViewController(tabVC, layout: { + tabVC.view.constrain(toSuperviewEdges: nil) + tabVC.view.alpha = 0 + tabVC.view.layoutIfNeeded() + }) + UIView.animate(withDuration: 0.3, delay: 0.1, options: .transitionCrossDissolve, animations: { + tabVC.view.alpha = 1 + }) { _ in + NSLog("US MainView Controller presented") + } + } else { + guard let noBuyTabVC = UIStoryboard(name: "Main", bundle: nil) + .instantiateViewController(withIdentifier: "NoBuyTabBarViewController") + as? NoBuyTabBarViewController + else { + NSLog("TabBarViewController not intialized") + return + } + + noBuyTabVC.store = store + noBuyTabVC.walletManager = walletManager + + addChildViewController(noBuyTabVC, layout: { + noBuyTabVC.view.constrain(toSuperviewEdges: nil) + noBuyTabVC.view.alpha = 0 + noBuyTabVC.view.layoutIfNeeded() + }) + + UIView.animate(withDuration: 0.3, delay: 0.1, options: .transitionCrossDissolve, animations: { + noBuyTabVC.view.alpha = 1 + }) { _ in + NSLog("US MainView Controller presented") + } + } delay(4.0) { self.appDelegate.pushNotifications.registerForRemoteNotifications() } diff --git a/litewallet/ModalPresenter.swift b/litewallet/ModalPresenter.swift index f23fe2aed..8a6ef7f5f 100644 --- a/litewallet/ModalPresenter.swift +++ b/litewallet/ModalPresenter.swift @@ -209,42 +209,6 @@ class ModalPresenter: Subscriber, Trackable { }) } - private func presentFailureAlert(_: AlertFailureType, - errorMessage: String, - completion: @escaping () -> Void) - { - let hostingViewController = UIHostingController(rootView: AlertFailureView(alertFailureType: .failedResolution, - errorMessage: errorMessage)) - - guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first, - let failureAlertView = hostingViewController.view else { return } - let size = window.bounds.size - window.addSubview(failureAlertView) - - let topConstraint = failureAlertView.constraint(.top, toView: window, constant: size.height) - failureAlertView.constrain([ - failureAlertView.constraint(.width, constant: size.width), - failureAlertView.constraint(.height, constant: alertHeight + 50.0), - failureAlertView.constraint(.leading, toView: window, constant: nil), - topConstraint, - ]) - window.layoutIfNeeded() - - UIView.spring(0.6, animations: { - topConstraint?.constant = size.height - self.alertHeight - window.layoutIfNeeded() - }, completion: { _ in - UIView.spring(0.6, delay: 5.0, animations: { - topConstraint?.constant = size.height - window.layoutIfNeeded() - }, completion: { _ in - // TODO: - Make these callbacks generic - completion() - failureAlertView.removeFromSuperview() - }) - }) - } - private func rootModalViewController(_ type: RootModal) -> UIViewController? { switch type { case .none: @@ -322,14 +286,6 @@ class ModalPresenter: Subscriber, Trackable { sendVC.onPublishSuccess = { [weak self] in self?.presentAlert(.sendSuccess, completion: {}) } - - sendVC.onResolvedSuccess = { [weak self] in - self?.presentAlert(.resolvedSuccess, completion: {}) - } - - sendVC.onResolutionFailure = { [weak self] failureMessage in - self?.presentFailureAlert(.failedResolution, errorMessage: failureMessage, completion: {}) - } return root } diff --git a/litewallet/MoonpayHelper.swift b/litewallet/MoonpayHelper.swift new file mode 100644 index 000000000..35e76a515 --- /dev/null +++ b/litewallet/MoonpayHelper.swift @@ -0,0 +1,68 @@ +import Foundation + +/// 14 Languages +enum UnsupportedCountries: Int, CaseIterable, Equatable, Identifiable { + case Afghanistan = 0 + case Barbados + case Belarus + case BurkinaFaso + case China + case Iceland + case Iraq + case Jamaica + case Japan + case Kosovo + case Liberia + case Macao + case Malaysia + case Malta + case Mongolia + case Morocco + case Myanmar + case Nicaragua + case Pakistan + case Panama + case Russia + case Senegal + case DemocraticRepCongo + case Uganda + case Ukraine + case Venezuela + case Yemen + case Zimbabwe + + var id: UnsupportedCountries { self } + + var localeCode: String { + switch self { + case .Afghanistan: return "fa_AF" + case .Barbados: return "en_BB" + case .Belarus: return "be_BY" + case .BurkinaFaso: return "fr_BF" + case .China: return "zh_CN" + case .Iceland: return "is_IS" + case .Iraq: return "ar_IQ" + case .Jamaica: return "en_JM" + case .Japan: return "jp_JP" + case .Kosovo: return "sq_XK" + case .Liberia: return "en_LR" + case .Macao: return "zh_MO" + case .Malaysia: return "ms_MY" + case .Malta: return "mt_MT" + case .Mongolia: return "mn_MN" + case .Morocco: return "ar_MA" + case .Myanmar: return "my_MM" + case .Nicaragua: return "es_NI" + case .Pakistan: return "ur_PK" + case .Panama: return "es_PA" + case .Russia: return "ru_RU" + case .Senegal: return "fr_SN" + case .DemocraticRepCongo: return "fr_CD" + case .Uganda: return "en_UG" + case .Ukraine: return "uk_UA" + case .Venezuela: return "es_VE" + case .Yemen: return "ar_YE" + case .Zimbabwe: return "en_ZW" + } + } +} diff --git a/litewallet/NoBuyTabBarViewController.swift b/litewallet/NoBuyTabBarViewController.swift new file mode 100644 index 000000000..b78189ac2 --- /dev/null +++ b/litewallet/NoBuyTabBarViewController.swift @@ -0,0 +1,419 @@ +import Foundation +import UIKit + +class NoBuyTabBarViewController: UIViewController, Subscriber, Trackable, UITabBarDelegate { + let kInitialChildViewControllerIndex = 0 // TransactionsViewController + @IBOutlet var headerView: UIView! + @IBOutlet var containerView: UIView! + @IBOutlet var tabBar: UITabBar! + @IBOutlet var settingsButton: UIButton! + @IBOutlet var walletBalanceLabel: UILabel! + + var primaryBalanceLabel: UpdatingLabel? + var secondaryBalanceLabel: UpdatingLabel? + private let largeFontSize: CGFloat = 24.0 + private let smallFontSize: CGFloat = 12.0 + private var hasInitialized = false + private let dateFormatter = DateFormatter() + private let equalsLabel = UILabel(font: .barlowMedium(size: 12), color: .whiteTint) + private var regularConstraints: [NSLayoutConstraint] = [] + private var swappedConstraints: [NSLayoutConstraint] = [] + private let currencyTapView = UIView() + private let storyboardNames: [String] = ["Transactions", "Send", "Receive"] + var storyboardIDs: [String] = ["TransactionsViewController", "SendLTCViewController", "ReceiveLTCViewController"] + var viewControllers: [UIViewController] = [] + var activeController: UIViewController? + var updateTimer: Timer? + var store: Store? + var walletManager: WalletManager? + var userIsMoonPaySupported: Bool? + var exchangeRate: Rate? { + didSet { setBalances() } + } + + private var balance: UInt64 = 0 { + didSet { setBalances() } + } + + var isLtcSwapped: Bool? { + didSet { setBalances() } + } + + @IBAction func showSettingsAction(_: Any) { + guard let store = store + else { + NSLog("ERROR: Store not set") + return + } + store.perform(action: RootModalActions.Present(modal: .menu)) + } + + override func viewDidLoad() { + super.viewDidLoad() + setupModels() + setupViews() + configurePriceLabels() + addSubscriptions() + dateFormatter.setLocalizedDateFormatFromTemplate("MMM d, h:mm a") + + addViewControllers() + + updateTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { _ in + self.setBalances() + } + + guard let array = tabBar.items + else { + NSLog("ERROR: no items found") + return + } + tabBar.selectedItem = array[kInitialChildViewControllerIndex] + + NotificationCenter.default.addObserver(self, selector: #selector(languageChanged), name: .languageChangedNotification, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: .languageChangedNotification, object: nil) + self.updateTimer = nil + } + + @objc + func languageChanged() { + walletBalanceLabel.text = S.ManageWallet.balance.localize() + ":" + localizeTabBar() + viewControllers = [] + addViewControllers() + guard let array = tabBar.items else { return } + tabBar.selectedItem = array[kInitialChildViewControllerIndex] + displayContentController(contentController: viewControllers[0]) + } + + func addViewControllers() { + for (index, storyboardID) in storyboardIDs.enumerated() { + if storyboardID == "BuyHostingController" && (userIsMoonPaySupported != nil) { + let hostingController = BuyHostingController() + viewControllers.append(hostingController) + } else { + let controller = UIStoryboard(name: storyboardNames[index], bundle: nil).instantiateViewController(withIdentifier: storyboardID) + viewControllers.append(controller) + } + } + } + + private func setupModels() { + guard let store = store else { return } + + isLtcSwapped = store.state.isLtcSwapped + + if let rate = store.state.currentRate { + exchangeRate = rate + let placeholderAmount = Amount(amount: 0, rate: rate, maxDigits: store.state.maxDigits) + secondaryBalanceLabel = UpdatingLabel(formatter: placeholderAmount.localFormat) + primaryBalanceLabel = UpdatingLabel(formatter: placeholderAmount.ltcFormat) + } else { + secondaryBalanceLabel = UpdatingLabel(formatter: NumberFormatter()) + primaryBalanceLabel = UpdatingLabel(formatter: NumberFormatter()) + } + } + + private func setupViews() { + walletBalanceLabel.text = S.ManageWallet.balance.localize() + ":" + + headerView.backgroundColor = .liteWalletBlue + tabBar.barTintColor = .liteWalletBlue + containerView.backgroundColor = .liteWalletBlue + view.backgroundColor = .liteWalletBlue + } + + private func configurePriceLabels() { + // TODO: Debug the reizing of label...very important + guard let primaryLabel = primaryBalanceLabel, + let secondaryLabel = secondaryBalanceLabel + else { + NSLog("ERROR: Price labels not initialized") + return + } + + let priceLabelArray = [primaryBalanceLabel, secondaryBalanceLabel, equalsLabel] + + for (_, view) in priceLabelArray.enumerated() { + view?.backgroundColor = .clear + view?.textColor = .white + } + + primaryLabel.font = UIFont.barlowSemiBold(size: largeFontSize) + secondaryLabel.font = UIFont.barlowSemiBold(size: largeFontSize) + + equalsLabel.text = "=" + headerView.addSubview(primaryLabel) + headerView.addSubview(secondaryLabel) + headerView.addSubview(equalsLabel) + headerView.addSubview(currencyTapView) + + secondaryLabel.constrain([ + secondaryLabel.constraint(.firstBaseline, toView: primaryLabel, constant: 0.0), + ]) + + equalsLabel.translatesAutoresizingMaskIntoConstraints = false + primaryLabel.translatesAutoresizingMaskIntoConstraints = false + regularConstraints = [ + primaryLabel.firstBaselineAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -12), + primaryLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: C.padding[1] * 1.25), + equalsLabel.firstBaselineAnchor.constraint(equalTo: primaryLabel.firstBaselineAnchor, constant: 0), + equalsLabel.leadingAnchor.constraint(equalTo: primaryLabel.trailingAnchor, constant: C.padding[1] / 2.0), + secondaryLabel.leadingAnchor.constraint(equalTo: equalsLabel.trailingAnchor, constant: C.padding[1] / 2.0), + ] + + swappedConstraints = [ + secondaryLabel.firstBaselineAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -12), + secondaryLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: C.padding[1] * 1.25), + equalsLabel.firstBaselineAnchor.constraint(equalTo: secondaryLabel.firstBaselineAnchor, constant: 0), + equalsLabel.leadingAnchor.constraint(equalTo: secondaryLabel.trailingAnchor, constant: C.padding[1] / 2.0), + primaryLabel.leadingAnchor.constraint(equalTo: equalsLabel.trailingAnchor, constant: C.padding[1] / 2.0), + ] + + if let isLTCSwapped = isLtcSwapped { + NSLayoutConstraint.activate(isLTCSwapped ? swappedConstraints : regularConstraints) + } + + currencyTapView.constrain([ + currencyTapView.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 0), + currencyTapView.trailingAnchor.constraint(equalTo: settingsButton.leadingAnchor, constant: -C.padding[5]), + currencyTapView.topAnchor.constraint(equalTo: primaryLabel.topAnchor, constant: 0), + currencyTapView.bottomAnchor.constraint(equalTo: primaryLabel.bottomAnchor, constant: C.padding[1]), + ]) + + let gr = UITapGestureRecognizer(target: self, action: #selector(currencySwitchTapped)) + currencyTapView.addGestureRecognizer(gr) + } + + // MARK: - Adding Subscriptions + + private func addSubscriptions() { + guard let store = store + else { + NSLog("ERROR - Store not passed") + return + } + + guard let primaryLabel = primaryBalanceLabel, + let secondaryLabel = secondaryBalanceLabel + else { + NSLog("ERROR: Price labels not initialized") + return + } + + store.subscribe(self, selector: { $0.walletState.syncProgress != $1.walletState.syncProgress }, + callback: { _ in + self.tabBar.selectedItem = self.tabBar.items?.first + }) + + store.lazySubscribe(self, + selector: { $0.isLtcSwapped != $1.isLtcSwapped }, + callback: { self.isLtcSwapped = $0.isLtcSwapped }) + store.lazySubscribe(self, + selector: { $0.currentRate != $1.currentRate }, + callback: { + if let rate = $0.currentRate { + let placeholderAmount = Amount(amount: 0, rate: rate, maxDigits: $0.maxDigits) + secondaryLabel.formatter = placeholderAmount.localFormat + primaryLabel.formatter = placeholderAmount.ltcFormat + } + self.exchangeRate = $0.currentRate + }) + + store.lazySubscribe(self, + selector: { $0.maxDigits != $1.maxDigits }, + callback: { + if let rate = $0.currentRate { + let placeholderAmount = Amount(amount: 0, rate: rate, maxDigits: $0.maxDigits) + secondaryLabel.formatter = placeholderAmount.localFormat + primaryLabel.formatter = placeholderAmount.ltcFormat + self.setBalances() + } + }) + + store.subscribe(self, + selector: { $0.walletState.balance != $1.walletState.balance }, + callback: { state in + if let balance = state.walletState.balance { + self.balance = balance + self.setBalances() + } + }) + } + + /// This is called when the price changes + private func setBalances() { + guard let rate = exchangeRate, let store = store, let isLTCSwapped = isLtcSwapped + else { + NSLog("ERROR: Rate, Store not initialized") + return + } + guard let primaryLabel = primaryBalanceLabel, + let secondaryLabel = secondaryBalanceLabel + else { + NSLog("ERROR: Price labels not initialized") + return + } + + let amount = Amount(amount: balance, rate: rate, maxDigits: store.state.maxDigits) + + if !hasInitialized { + let amount = Amount(amount: balance, rate: exchangeRate!, maxDigits: store.state.maxDigits) + NSLayoutConstraint.deactivate(isLTCSwapped ? regularConstraints : swappedConstraints) + NSLayoutConstraint.activate(isLTCSwapped ? swappedConstraints : regularConstraints) + primaryLabel.setValue(amount.amountForLtcFormat) + secondaryLabel.setValue(amount.localAmount) + if isLTCSwapped { + primaryLabel.transform = transform(forView: primaryLabel) + } else { + secondaryLabel.transform = transform(forView: secondaryLabel) + } + hasInitialized = true + } else { + if primaryLabel.isHidden { + primaryLabel.isHidden = false + } + + if secondaryLabel.isHidden { + secondaryLabel.isHidden = false + } + } + + primaryLabel.setValue(amount.amountForLtcFormat) + secondaryLabel.setValue(amount.localAmount) + + if !isLTCSwapped { + primaryLabel.transform = .identity + secondaryLabel.transform = transform(forView: secondaryLabel) + } else { + secondaryLabel.transform = .identity + primaryLabel.transform = transform(forView: primaryLabel) + } + } + + /// Transform LTC and Fiat Balances + /// - Parameter forView: Views + /// - Returns: the inverse transform + private func transform(forView: UIView) -> CGAffineTransform { + forView.transform = .identity + let scaleFactor: CGFloat = smallFontSize / largeFontSize + let deltaX = forView.frame.width * (1 - scaleFactor) + let deltaY = forView.frame.height * (1 - scaleFactor) + let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor) + return scale.translatedBy(x: -deltaX, y: deltaY / 2.0) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + localizeTabBar() + } + + func localizeTabBar() { + guard let array = tabBar.items + else { + NSLog("ERROR: no items found") + return + } + + for item in array { + switch item.tag { + case 0: item.title = S.History.barItemTitle.localize() + case 1: item.title = S.Send.barItemTitle.localize() + case 2: item.title = S.Receive.barItemTitle.localize() + default: + item.title = "NO-TITLE" + NSLog("ERROR: UITabbar item count is wrong") + } + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + displayContentController(contentController: viewControllers[kInitialChildViewControllerIndex]) + } + + func displayContentController(contentController: UIViewController) { + // MARK: - Tab View Controllers Configuration + + switch NSStringFromClass(contentController.classForCoder) { + case "litewallet.TransactionsViewController": + + guard let transactionVC = contentController as? TransactionsViewController + else { + return + } + + transactionVC.store = store + transactionVC.walletManager = walletManager + transactionVC.isLtcSwapped = store?.state.isLtcSwapped + + case "litewallet.SendLTCViewController": + guard let sendVC = contentController as? SendLTCViewController + else { + return + } + + sendVC.store = store + + case "litewallet.ReceiveLTCViewController": + guard let receiveVC = contentController as? ReceiveLTCViewController + else { + return + } + receiveVC.store = store + + default: + fatalError("Tab viewController not set") + } + exchangeRate = TransactionManager.sharedInstance.rate + + addChild(contentController) + contentController.view.frame = containerView.frame + view.addSubview(contentController.view) + contentController.didMove(toParent: self) + activeController = contentController + } + + func hideContentController(contentController: UIViewController) { + contentController.willMove(toParent: nil) + contentController.view.removeFromSuperview() + contentController.removeFromParent() + } + + func tabBar(_: UITabBar, didSelect item: UITabBarItem) { + if let tempActiveController = activeController { + hideContentController(contentController: tempActiveController) + } + + // DEV: This happens because it relies on the tab in the storyboard tag + displayContentController(contentController: viewControllers[item.tag]) + } +} + +extension NoBuyTabBarViewController { + @objc private func currencySwitchTapped() { + view.layoutIfNeeded() + guard let store = store else { return } + guard let isLTCSwapped = isLtcSwapped else { return } + guard let primaryLabel = primaryBalanceLabel, + let secondaryLabel = secondaryBalanceLabel + else { + NSLog("ERROR: Price labels not initialized") + return + } + + UIView.spring(0.7, animations: { + primaryLabel.transform = primaryLabel.transform.isIdentity ? self.transform(forView: primaryLabel) : .identity + secondaryLabel.transform = secondaryLabel.transform.isIdentity ? self.transform(forView: secondaryLabel) : .identity + NSLayoutConstraint.deactivate(!isLTCSwapped ? self.regularConstraints : self.swappedConstraints) + NSLayoutConstraint.activate(!isLTCSwapped ? self.swappedConstraints : self.regularConstraints) + self.view.layoutIfNeeded() + + LWAnalytics.logEventWithParameters(itemName: ._20200207_DTHB) + + }) { _ in } + store.perform(action: CurrencyChange.toggle()) + } +} diff --git a/litewallet/Platform/TxMetaData.swift b/litewallet/Platform/TxMetaData.swift index 013289982..45bf54b4e 100644 --- a/litewallet/Platform/TxMetaData.swift +++ b/litewallet/Platform/TxMetaData.swift @@ -52,7 +52,7 @@ open class TxMetaData: BRKVStoreObject, BRCoding { var del: Bool var bytes: [UInt8] - print("[BRTxMetadataObject] find \(txHash.txKey)") + print("[BRTxMetadataObject] find txHash \(txHash.txKey)") do { (ver, date, del, bytes) = try store.get(txHash.txKey) let bytesDat = Data(bytes: &bytes, count: bytes.count) @@ -72,7 +72,7 @@ open class TxMetaData: BRKVStoreObject, BRCoding { var del: Bool var bytes: [UInt8] - print("[BRTxMetadataObject] find \(txKey)") + print("[BRTxMetadataObject] find txKey \(txKey)") do { (ver, date, del, bytes) = try store.get(txKey) let bytesDat = Data(bytes: &bytes, count: bytes.count) diff --git a/litewallet/Storyboards/Buy.storyboard b/litewallet/Storyboards/Buy.storyboard index 874415f3a..d4ec41840 100644 --- a/litewallet/Storyboards/Buy.storyboard +++ b/litewallet/Storyboards/Buy.storyboard @@ -9,9 +9,6 @@ - - BarlowSemiCondensed-Bold - BarlowSemiCondensed-Light @@ -20,247 +17,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -370,10 +126,7 @@ - - - diff --git a/litewallet/Storyboards/Main.storyboard b/litewallet/Storyboards/Main.storyboard index c648ac707..080ef3a9d 100644 --- a/litewallet/Storyboards/Main.storyboard +++ b/litewallet/Storyboards/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -32,7 +32,8 @@ - + + @@ -118,7 +119,6 @@ - @@ -127,7 +127,7 @@ - + @@ -137,6 +137,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -145,7 +234,7 @@ - + diff --git a/litewallet/Strings/Base.lproj/Localizable.strings b/litewallet/Strings/Base.lproj/Localizable.strings index d07f2e339..385d568fb 100644 --- a/litewallet/Strings/Base.lproj/Localizable.strings +++ b/litewallet/Strings/Base.lproj/Localizable.strings @@ -1069,9 +1069,6 @@ /* $53.09/L + 1.07% */ "Confirmation.amountDetailLabel" = "Exchange details:"; -/* Short Network Fee: ($1.00) */ -"Confirmation.shortFeeLabel" = "FEE:"; - /* Address label */ "Confirmation.staticAddressLabel" = "ADDRESS:"; @@ -1429,18 +1426,6 @@ /* sorry */ "Fragment.sorry" = "sorry"; -/* 2FA Error message */ -"LitecoinCard.twoFAErrorMessage" = "There was an error. Please toggle 2FA to *Enabled*, enter the emailed code, and try again."; - -/* Enter domain */ -"Send.UnstoppableDomains.simpleplaceholder" = "Enter domain"; - -/* Enter a */ -"Send.UnstoppableDomains.enterA" = "Enter a"; - -/* domain */ -"Send.UnstoppableDomains.domain" = "domain"; - /* Change language alert message */ "Settings.ChangeLanguage.alertMessage" = "Are you sure you want to change the language to %l?"; diff --git a/litewallet/Strings/it.lproj/Localizable.strings b/litewallet/Strings/it.lproj/Localizable.strings index 699d1997a..985939b1a 100755 --- a/litewallet/Strings/it.lproj/Localizable.strings +++ b/litewallet/Strings/it.lproj/Localizable.strings @@ -718,12 +718,6 @@ /* Fees: $0.01*/ "Send.fee" = "Commissioni: %1$@"; -/* Fees: $0.01*/ -"Send.fee" = "Commissioni: %1$@"; - -/* Fees Blank: */ -"Send.feeBlank" = "Commissioni:"; - /* Fees Blank: */ "Send.feeBlank" = "Commissioni:"; diff --git a/litewallet/SupportLitecoinFoundationView.swift b/litewallet/SupportLitecoinFoundationView.swift index fd2bce07e..1b7acb452 100644 --- a/litewallet/SupportLitecoinFoundationView.swift +++ b/litewallet/SupportLitecoinFoundationView.swift @@ -9,9 +9,6 @@ struct SupportLitecoinFoundationView: View { @ObservedObject var viewModel: SupportLitecoinFoundationViewModel - @State - private var showSupportLFPage: Bool = false - // MARK: - Public var supportSafariView = SupportSafariView(url: FoundationSupport.url, diff --git a/litewallet/TabBarViewController.swift b/litewallet/TabBarViewController.swift index f80e4370a..8af223d8a 100644 --- a/litewallet/TabBarViewController.swift +++ b/litewallet/TabBarViewController.swift @@ -20,12 +20,13 @@ class TabBarViewController: UIViewController, Subscriber, Trackable, UITabBarDel private var swappedConstraints: [NSLayoutConstraint] = [] private let currencyTapView = UIView() private let storyboardNames: [String] = ["Transactions", "Send", "Receive", "Buy"] - var storyboardIDs: [String] = ["TransactionsViewController", "SendLTCViewController", "ReceiveLTCViewController", "BuyTableViewController"] + var storyboardIDs: [String] = ["TransactionsViewController", "SendLTCViewController", "ReceiveLTCViewController", "BuyHostingController"] var viewControllers: [UIViewController] = [] var activeController: UIViewController? var updateTimer: Timer? var store: Store? var walletManager: WalletManager? + var userIsMoonPaySupported: Bool? var exchangeRate: Rate? { didSet { setBalances() } } @@ -89,8 +90,13 @@ class TabBarViewController: UIViewController, Subscriber, Trackable, UITabBarDel func addViewControllers() { for (index, storyboardID) in storyboardIDs.enumerated() { - let controller = UIStoryboard(name: storyboardNames[index], bundle: nil).instantiateViewController(withIdentifier: storyboardID) - viewControllers.append(controller) + if storyboardID == "BuyHostingController" { + let hostingController = BuyHostingController() + viewControllers.append(hostingController) + } else { + let controller = UIStoryboard(name: storyboardNames[index], bundle: nil).instantiateViewController(withIdentifier: storyboardID) + viewControllers.append(controller) + } } } @@ -344,13 +350,13 @@ class TabBarViewController: UIViewController, Subscriber, Trackable, UITabBarDel transactionVC.walletManager = walletManager transactionVC.isLtcSwapped = store?.state.isLtcSwapped - case "litewallet.BuyTableViewController": - guard let buyVC = contentController as? BuyTableViewController + case "litewallet.BuyHostingController": + guard let buyHC = contentController as? BuyHostingController else { return } - buyVC.store = store - buyVC.walletManager = walletManager + + buyHC.isLoaded = true case "litewallet.SendLTCViewController": guard let sendVC = contentController as? SendLTCViewController diff --git a/litewallet/TransactionsViewModel.swift b/litewallet/TransactionsViewModel.swift deleted file mode 100644 index 300ee9af6..000000000 --- a/litewallet/TransactionsViewModel.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -class TransactionsViewModel: ObservableObject { - var store: Store - - var walletManager: WalletManager - - var isLTCSwapped: Bool = false - - init(store: Store, walletManager: WalletManager) { - self.store = store - self.walletManager = walletManager - isLTCSwapped = store.state.isLtcSwapped - } -} diff --git a/litewallet/TransferAmountViewModel.swift b/litewallet/TransferAmountViewModel.swift index 3519d6456..8b1378917 100644 --- a/litewallet/TransferAmountViewModel.swift +++ b/litewallet/TransferAmountViewModel.swift @@ -1,73 +1 @@ -import BRCore -import Foundation -import KeychainAccess -import UIKit -// DEV: To be removed in following issue https://github.com/litecoin-foundation/litewallet-ios/issues/177 - -class TransferAmountViewModel: ObservableObject { - // MARK: - Combine Variables - - @Published - var walletType: WalletType - - // MARK: - Private Variables - - private let walletManager: WalletManager - - private let store: Store - - private var sender: Sender? - - // MARK: - Public Variables - - var litewalletBalance: Double = 0.0 - - var litewalletAddress: String = "" - - var cardBalance: Double = 0.0 - - var cardAddress: String = "" - - var currentBalance: Double = 0.0 - - var transferAmount: Double = 0.0 - - /// This is the LTC address the wallet is sending LTC TO - var destinationAddress: String { - return walletType == .litewallet ? cardAddress : litewalletAddress - } - - var transaction: BRTxRef? - - init(walletType: WalletType, - litewalletBalance: Double, - litewalletAddress: String, - cardBalance: Double, - cardAddress: String, - walletManager: WalletManager, - store: Store) - { - self.walletManager = walletManager - - self.store = store - - self.walletType = walletType - - self.litewalletBalance = litewalletBalance - - self.litewalletAddress = litewalletAddress - - // DEV: The Testnet is not implemented in Loafwallet Core. - // This would be used for the Card testing. - #if DEBUG - self.litewalletAddress = MockData.testLTCAddress - #endif - - self.cardBalance = cardBalance - - self.cardAddress = cardAddress - - currentBalance = walletType == .litewallet ? litewalletBalance : cardBalance - } -} diff --git a/litewallet/Views/AlertView.swift b/litewallet/Views/AlertView.swift index 58a72e7db..a4df9dcbd 100644 --- a/litewallet/Views/AlertView.swift +++ b/litewallet/Views/AlertView.swift @@ -4,7 +4,6 @@ enum AlertType { case pinSet(callback: () -> Void) case paperKeySet(callback: () -> Void) case sendSuccess - case resolvedSuccess case addressesCopied case sweepSuccess(callback: () -> Void) @@ -19,8 +18,6 @@ enum AlertType { return S.SecurityAlerts.paperKeySet.localize() case .sendSuccess: return S.SecurityAlerts.sendSuccess.localize() - case .resolvedSuccess: - return S.SecurityAlerts.resolvedSuccess.localize() case .addressesCopied: return S.SecurityAlerts.copiedAddressesHeader.localize() case .sweepSuccess: @@ -40,8 +37,6 @@ enum AlertType { return S.SecurityAlerts.paperKeySetSubheader.localize() case .sendSuccess: return S.SecurityAlerts.sendSuccessSubheader.localize() - case .resolvedSuccess: - return S.SecurityAlerts.resolvedSuccessSubheader.localize() case .addressesCopied: return S.SecurityAlerts.copiedAddressesSubheader.localize() case .sweepSuccess: @@ -68,8 +63,6 @@ func == (lhs: AlertType, rhs: AlertType) -> Bool { return true case (.sendSuccess, .sendSuccess): return true - case (.resolvedSuccess, .resolvedSuccess): - return true case (.addressesCopied, .addressesCopied): return true case (.sweepSuccess(_), .sweepSuccess(_)): diff --git a/litewallet/Wallet/ExchangeUpdater.swift b/litewallet/Wallet/ExchangeUpdater.swift index 5b52eb0a0..f4f18422f 100644 --- a/litewallet/Wallet/ExchangeUpdater.swift +++ b/litewallet/Wallet/ExchangeUpdater.swift @@ -15,11 +15,13 @@ class ExchangeUpdater: Subscriber { } func refresh(completion: @escaping () -> Void) { - walletManager.apiClient?.exchangeRates { rates, _ in + if walletManager.store.state.walletState.syncState != .syncing { + walletManager.apiClient?.exchangeRates { rates, _ in - guard let currentRate = rates.first(where: { $0.code == self.store.state.defaultCurrencyCode }) else { completion(); return } - self.store.perform(action: ExchangeRates.setRates(currentRate: currentRate, rates: rates)) - completion() + guard let currentRate = rates.first(where: { $0.code == self.store.state.defaultCurrencyCode }) else { completion(); return } + self.store.perform(action: ExchangeRates.setRates(currentRate: currentRate, rates: rates)) + completion() + } } } diff --git a/litewallet/WalletCoordinator.swift b/litewallet/WalletCoordinator.swift index 784c24f27..dc1ab8925 100644 --- a/litewallet/WalletCoordinator.swift +++ b/litewallet/WalletCoordinator.swift @@ -3,8 +3,8 @@ import Foundation import UIKit private let lastBlockHeightKey = "LastBlockHeightKey" -private let progressUpdateInterval: TimeInterval = 1.0 -private let updateDebounceInterval: TimeInterval = 1.0 +private let progressUpdateInterval: TimeInterval = 0.5 +private let updateDebounceInterval: TimeInterval = 0.4 class WalletCoordinator: Subscriber, Trackable { var kvStore: BRReplicatedKVStore? { diff --git a/litewallet/bitrefill_index.html b/litewallet/bitrefill_index.html deleted file mode 100644 index f60fdc9cd..000000000 --- a/litewallet/bitrefill_index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - Bitrefill - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/litewallet/de.lproj/Localizable.strings b/litewallet/de.lproj/Localizable.strings index b5ba77126..b22946220 100755 --- a/litewallet/de.lproj/Localizable.strings +++ b/litewallet/de.lproj/Localizable.strings @@ -904,21 +904,6 @@ /* Fees: $0.01*/ "Send.fee" = "Gebühren: %1$@"; -/* Fees: $0.01*/ -"Send.fee" = "Gebühren: %1$@"; - -/* Fees: $0.01*/ -"Send.fee" = "Gebühren: %1$@"; - -/* Fees Blank: */ -"Send.feeBlank" = "Gebühren:"; - -/* Fees Blank: */ -"Send.feeBlank" = "Gebühren:"; - -/* Fees Blank: */ -"Send.feeBlank" = "Gebühren:"; - /* Fees Blank: */ "Send.feeBlank" = "Gebühren:"; diff --git a/litewallet/en.lproj/Localizable.strings b/litewallet/en.lproj/Localizable.strings index 4cc13379c..d93df6696 100644 --- a/litewallet/en.lproj/Localizable.strings +++ b/litewallet/en.lproj/Localizable.strings @@ -907,18 +907,6 @@ /* Fees: $0.01*/ "Send.fee" = "Fees: %1$@"; -/* Fees: $0.01*/ -"Send.fee" = "Fees: %1$@"; - -/* Fees: $0.01*/ -"Send.fee" = "Fees: %1$@"; - -/* Fees Blank: */ -"Send.feeBlank" = "Fees:"; - -/* Fees Blank: */ -"Send.feeBlank" = "Fees:"; - /* Fees Blank: */ "Send.feeBlank" = "Fees:"; diff --git a/litewallet/general.css b/litewallet/general.css deleted file mode 100644 index c1652674a..000000000 --- a/litewallet/general.css +++ /dev/null @@ -1,55 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) - */ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} - -* { - -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ - -moz-box-sizing: border-box; /* Firefox, other Gecko */ - box-sizing: border-box; /* Opera/IE 8+ */ -} - diff --git a/litewalletTests/Class Tests/TabBarViewControllerTests.swift b/litewalletTests/Class Tests/TabBarViewControllerTests.swift index d8a2fa4c8..053a9493e 100644 --- a/litewalletTests/Class Tests/TabBarViewControllerTests.swift +++ b/litewalletTests/Class Tests/TabBarViewControllerTests.swift @@ -4,6 +4,8 @@ import XCTest class TabBarViewControllerTests: XCTestCase { var viewController: TabBarViewController! + var noBuyVewController: NoBuyTabBarViewController! + override func setUpWithError() throws { viewController = UIStoryboard(name: "Main", bundle: nil) @@ -11,16 +13,26 @@ class TabBarViewControllerTests: XCTestCase { TabBarViewController viewController.loadViewIfNeeded() + + noBuyVewController = UIStoryboard(name: "Main", + bundle: nil) + .instantiateViewController(withIdentifier: "NoBuyTabBarViewController") as? + NoBuyTabBarViewController + + noBuyVewController.loadViewIfNeeded() } override func tearDownWithError() throws { viewController = nil + noBuyVewController = nil } func testTabBarItemCount() throws { // There should be 4 tabs in this version for all users XCTAssertTrue(viewController.tabBar.items?.count == 4) + + XCTAssertTrue(noBuyVewController.tabBar.items?.count == 3) } func testTabBarItemRange() throws { @@ -33,5 +45,11 @@ class TabBarViewControllerTests: XCTestCase { XCTAssertTrue(viewController.tabBar.items?[2].tag == 2) XCTAssertTrue(viewController.tabBar.items?[3].tag == 3) + + XCTAssertTrue(noBuyVewController.tabBar.items?[0].tag == 0) + + XCTAssertTrue(noBuyVewController.tabBar.items?[1].tag == 1) + + XCTAssertTrue(noBuyVewController.tabBar.items?[2].tag == 2) } } diff --git a/litewalletTests/Language Selection Tests/LocaleTests.swift b/litewalletTests/Language Selection Tests/LocaleTests.swift new file mode 100644 index 000000000..efbfc489c --- /dev/null +++ b/litewalletTests/Language Selection Tests/LocaleTests.swift @@ -0,0 +1,140 @@ +@testable import litewallet +import XCTest + +final class LocaleTests: XCTestCase { + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testFilteringUnsupportedCountries() throws { + /// Based on the live list: https://support.moonpay.com/customers/docs/moonpays_unsupported_countries + + // Afghanistan: `fa_AF`, `ps_AF`, `uz_AF` + let afghanistanLocale = Locale(identifier: "fa_AF") + XCTAssertTrue(afghanistanLocale.identifier == "fa_AF") + + // Barbados: `en_BB` + let barbadosLocale = Locale(identifier: "en_BB") + XCTAssertTrue(barbadosLocale.identifier == "en_BB") + + // Belarus: `be_BY`, `ru_BY` + let belarusLocale = Locale(identifier: "be_BY") + XCTAssertTrue(belarusLocale.identifier == "be_BY") + + // Burkina Faso: `fr_BF` + let burkinaFasoLocale = Locale(identifier: "fr_BF") + XCTAssertTrue(burkinaFasoLocale.identifier == "fr_BF") + + // China: `zh_CN`, `zh_Hans_CN`, `zh_Hant_CN`, `ug_CN` + let chinaLocale = Locale(identifier: "zh_CN") + XCTAssertTrue(chinaLocale.identifier == "zh_CN") + + // Iceland: `is_IS` + let icelandLocale = Locale(identifier: "is_IS") + XCTAssertTrue(icelandLocale.identifier == "is_IS") + + // Iraq: `ar_IQ`, `ku_IQ` + let iraqLocale = Locale(identifier: "ar_IQ") + XCTAssertTrue(iraqLocale.identifier == "ar_IQ") + + // Jamaica: `en_JM`, `jam_JM` + let jamaicaLocale = Locale(identifier: "en_JM") + XCTAssertTrue(jamaicaLocale.identifier == "en_JM") + + // Japan: `ja_JP` + let japanLocale = Locale(identifier: "ja_JP") + XCTAssertTrue(japanLocale.identifier == "ja_JP") + + // Kosovo: `sq_XK`, `sr_XK` + let kosovoLocale = Locale(identifier: "sq_XK") + XCTAssertTrue(kosovoLocale.identifier == "sq_XK") + + // Liberia: `en_LR` + let liberiaLocale = Locale(identifier: "en_LR") + XCTAssertTrue(liberiaLocale.identifier == "en_LR") + + // Macao: `zh_MO`, `pt_MO` + let macaoLocale = Locale(identifier: "zh_MO") + XCTAssertTrue(macaoLocale.identifier == "zh_MO") + + // Malaysia: `ms_MY`, `zh_MY`, `ta_MY`, `en_MY` + let malaysiaLocale = Locale(identifier: "ms_MY") + XCTAssertTrue(malaysiaLocale.identifier == "ms_MY") + + // Malta: `mt_MT`, `en_MT` + let maltaLocale = Locale(identifier: "mt_MT") + XCTAssertTrue(maltaLocale.identifier == "mt_MT") + + // Mongolia: `mn_MN` + let mongoliaLocale = Locale(identifier: "mn_MN") + XCTAssertTrue(mongoliaLocale.identifier == "mn_MN") + + // Morocco: `ar_MA`, `fr_MA`, `ber_MA` + let moroccoLocale = Locale(identifier: "ar_MA") + XCTAssertTrue(moroccoLocale.identifier == "ar_MA") + + // Myanmar: `my_MM` + let myanmarLocale = Locale(identifier: "my_MM") + XCTAssertTrue(myanmarLocale.identifier == "my_MM") + + // Nicaragua: `es_NI` + let nicaraguaLocale = Locale(identifier: "es_NI") + XCTAssertTrue(nicaraguaLocale.identifier == "es_NI") + + // Pakistan: `ur_PK`, `en_PK` + let pakistanLocale = Locale(identifier: "ur_PK") + XCTAssertTrue(pakistanLocale.identifier == "ur_PK") + + // Panama: `es_PA` + let panamaLocale = Locale(identifier: "es_PA") + XCTAssertTrue(panamaLocale.identifier == "es_PA") + + // Russia: `ru_RU` + let russiaLocale = Locale(identifier: "ru_RU") + XCTAssertTrue(russiaLocale.identifier == "ru_RU") + + // Senegal: `fr_SN`, `wo_SN` + let senegalLocale = Locale(identifier: "fr_SN") + XCTAssertTrue(senegalLocale.identifier == "fr_SN") + + // The Democratic Republic Of The Congo: `fr_CD`, `ln_CD`, `sw_CD` + let drCongoLocale = Locale(identifier: "fr_CD") + XCTAssertTrue(drCongoLocale.identifier == "fr_CD") + + // Uganda: `en_UG`, `sw_UG` + let ugandaLocale = Locale(identifier: "en_UG") + XCTAssertTrue(ugandaLocale.identifier == "en_UG") + + // Ukraine: `uk_UA`, `ru_UA` + let ukraineLocale = Locale(identifier: "uk_UA") + XCTAssertTrue(ukraineLocale.identifier == "uk_UA") + + // Venezuela: `es_VE` + let venezuelaLocale = Locale(identifier: "es_VE") + XCTAssertTrue(venezuelaLocale.identifier == "es_VE") + + // Yemen: `ar_YE` + let yemenLocale = Locale(identifier: "ar_YE") + XCTAssertTrue(yemenLocale.identifier == "ar_YE") + + // Zimbabwe: `en_ZW`, `sn_ZW`, `nd_ZW` + let zimbabweLocale = Locale(identifier: "en_ZW") + XCTAssertTrue(zimbabweLocale.identifier == "en_ZW") + } + + func testUnsupportedCountriesEnum() throws { + let unsupportedCases = UnsupportedCountries.allCases + XCTAssertTrue(unsupportedCases.count == 28) + + let supportedCountry = Locale(identifier: "en_US").identifier + + for unsupportedLocale in unsupportedCases { + let unsupportedCode = unsupportedLocale.localeCode + XCTAssertTrue(unsupportedCode != supportedCountry) + } + } +}