diff --git a/.github/workflows/deploy_appstore.yml b/.github/workflows/deploy_appstore.yml index 73e68eacfc..5dc87c309c 100644 --- a/.github/workflows/deploy_appstore.yml +++ b/.github/workflows/deploy_appstore.yml @@ -11,10 +11,10 @@ jobs: runs-on: macos-14 steps: - - name: Setup Xcode to 15.3 + - name: Setup Xcode to 15.2 uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '15.3' + xcode-version: '15.2' - name: Checkout repository uses: actions/checkout@v4 @@ -70,3 +70,4 @@ jobs: XCCONFIG_PROD_ONE_INCH_API_KEY: ${{ secrets.XCCONFIG_PROD_ONE_INCH_API_KEY }} XCCONFIG_PROD_ONE_INCH_COMMISSION: ${{ secrets.XCCONFIG_PROD_ONE_INCH_COMMISSION }} XCCONFIG_PROD_ONE_INCH_COMMISSION_ADDRESS: ${{ secrets.XCCONFIG_PROD_ONE_INCH_COMMISSION_ADDRESS }} + XCCONFIG_PROD_REFERRAL_APP_SERVER_URL: ${{ secrets.XCCONFIG_PROD_REFERRAL_APP_SERVER_URL }} diff --git a/.github/workflows/deploy_dev.yml b/.github/workflows/deploy_dev.yml index 9ceb6d75d1..25d892c1a1 100644 --- a/.github/workflows/deploy_dev.yml +++ b/.github/workflows/deploy_dev.yml @@ -71,3 +71,4 @@ jobs: XCCONFIG_DEV_ONE_INCH_API_KEY: ${{ secrets.XCCONFIG_DEV_ONE_INCH_API_KEY }} XCCONFIG_DEV_ONE_INCH_COMMISSION: ${{ secrets.XCCONFIG_DEV_ONE_INCH_COMMISSION }} XCCONFIG_DEV_ONE_INCH_COMMISSION_ADDRESS: ${{ secrets.XCCONFIG_DEV_ONE_INCH_COMMISSION_ADDRESS }} + XCCONFIG_DEV_REFERRAL_APP_SERVER_URL: ${{ secrets.XCCONFIG_DEV_REFERRAL_APP_SERVER_URL }} \ No newline at end of file diff --git a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj index aa791e9cba..58034f3ab8 100644 --- a/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj +++ b/UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj @@ -2680,6 +2680,10 @@ D061A5332AA846FA009AAD57 /* SecuritySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D061A5312AA846FA009AAD57 /* SecuritySettingsView.swift */; }; D06669022A31B559004B048D /* TronRecipientAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06669012A31B559004B048D /* TronRecipientAddressViewModel.swift */; }; D06669032A31B559004B048D /* TronRecipientAddressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06669012A31B559004B048D /* TronRecipientAddressViewModel.swift */; }; + D066A45C2C6CB2E100074E35 /* TelegramUserHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D066A45B2C6CB2E100074E35 /* TelegramUserHandler.swift */; }; + D066A45D2C6CB2E100074E35 /* TelegramUserHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D066A45B2C6CB2E100074E35 /* TelegramUserHandler.swift */; }; + D066A45F2C6CC7E200074E35 /* WelcomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D066A45E2C6CC7E200074E35 /* WelcomeScreenViewModel.swift */; }; + D066A4602C6CC7E200074E35 /* WelcomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D066A45E2C6CC7E200074E35 /* WelcomeScreenViewModel.swift */; }; D06A171B2BA1B1BC0081E312 /* FeeSettingsViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06A171A2BA1B1BC0081E312 /* FeeSettingsViewHelper.swift */; }; D06A171C2BA1B1BC0081E312 /* FeeSettingsViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06A171A2BA1B1BC0081E312 /* FeeSettingsViewHelper.swift */; }; D06B302C2B6A120E0012A161 /* LegacyFeeSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06B302B2B6A120E0012A161 /* LegacyFeeSettingsViewModel.swift */; }; @@ -4607,6 +4611,8 @@ D05F132D2A31FE0D00C3193F /* AddTronTokenBlockchainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTronTokenBlockchainService.swift; sourceTree = ""; }; D061A5312AA846FA009AAD57 /* SecuritySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecuritySettingsView.swift; sourceTree = ""; }; D06669012A31B559004B048D /* TronRecipientAddressViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronRecipientAddressViewModel.swift; sourceTree = ""; }; + D066A45B2C6CB2E100074E35 /* TelegramUserHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelegramUserHandler.swift; sourceTree = ""; }; + D066A45E2C6CC7E200074E35 /* WelcomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenViewModel.swift; sourceTree = ""; }; D06A171A2BA1B1BC0081E312 /* FeeSettingsViewHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeeSettingsViewHelper.swift; sourceTree = ""; }; D06B302B2B6A120E0012A161 /* LegacyFeeSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyFeeSettingsViewModel.swift; sourceTree = ""; }; D07157DA2A2DD968006F141F /* SendTronModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTronModule.swift; sourceTree = ""; }; @@ -7069,6 +7075,7 @@ children = ( 3C7B956E27165EB2D682C2D7 /* WelcomeScreenViewController.swift */, 11B35A05B93CB243B6404C4A /* WelcomeTextView.swift */, + D066A45E2C6CC7E200074E35 /* WelcomeScreenViewModel.swift */, ); path = Welcome; sourceTree = ""; @@ -7432,6 +7439,7 @@ 6B29071C2AF0CB8A006157D6 /* WidgetCoinAppShowWorker */, 6B29071E2AF0CB8A006157D6 /* EventHandler.swift */, ABC9A72A3AB621DE379754C8 /* SendAppShowWorker */, + D066A45B2C6CB2E100074E35 /* TelegramUserHandler.swift */, ); path = Workers; sourceTree = ""; @@ -9786,6 +9794,7 @@ 11B355E3AC79508BEDA18CAE /* BirthdayInputViewController.swift in Sources */, 1A5643BCDAD7CB0230CBB513 /* GradientClippingView.swift in Sources */, 11B3558898EE33B8D6E571CE /* MnemonicPhraseCell.swift in Sources */, + D066A4602C6CC7E200074E35 /* WelcomeScreenViewModel.swift in Sources */, 11B35A33CB6CA5C4A25ECFC9 /* MnemonicWordCell.swift in Sources */, 1A564B1D457A23C7732B76DF /* ReleaseNotesService.swift in Sources */, 1A5642F3BA1892109A596B61 /* MarkdownContentProvider.swift in Sources */, @@ -10202,6 +10211,7 @@ 11B35DBD3329CD28158E4D6A /* NftHeaderViewModel.swift in Sources */, 11B35B36FB559CDEB1B496EC /* NftHeaderView.swift in Sources */, 11B358F2CD17616038016E59 /* NftRecord.swift in Sources */, + D066A45D2C6CB2E100074E35 /* TelegramUserHandler.swift in Sources */, 11B35480CA91E0A62617B83A /* EvmNftRecord.swift in Sources */, 11B35D3102B803096B6EE5B6 /* NftMetadataManager.swift in Sources */, 11B35E34B9E95819B9EA1764 /* OpenSeaNftProvider.swift in Sources */, @@ -11276,6 +11286,7 @@ 11B3553ED96875D0B6E5B5C4 /* BirthdayInputViewController.swift in Sources */, 1A564A2FCE3C764029FECB7B /* GradientClippingView.swift in Sources */, 11B35E67CDA98E004C9C2011 /* MnemonicPhraseCell.swift in Sources */, + D066A45F2C6CC7E200074E35 /* WelcomeScreenViewModel.swift in Sources */, 11B35C22A15045197D511BB2 /* MnemonicWordCell.swift in Sources */, 1A564C10B9782D53375736C8 /* ReleaseNotesService.swift in Sources */, 1A56451ADE50B86E21814347 /* MarkdownContentProvider.swift in Sources */, @@ -11692,6 +11703,7 @@ 11B353A07F9259765D90F3BA /* NftService.swift in Sources */, 11B35D6C50BA6E928A54EDAC /* NftModule.swift in Sources */, 11B356D6A39A05C101B0CB9D /* NftViewModel.swift in Sources */, + D066A45C2C6CB2E100074E35 /* TelegramUserHandler.swift in Sources */, 11B35AF308141DC4CFA45918 /* NftHeaderViewModel.swift in Sources */, 11B351F991634E3E6A0846EF /* NftHeaderView.swift in Sources */, 11B35F2F1770FB757E6FDCD8 /* NftRecord.swift in Sources */, diff --git a/UnstoppableWallet/UnstoppableWallet/Configuration/Development.template.xcconfig b/UnstoppableWallet/UnstoppableWallet/Configuration/Development.template.xcconfig index fd72f9eecd..e3bff28931 100644 --- a/UnstoppableWallet/UnstoppableWallet/Configuration/Development.template.xcconfig +++ b/UnstoppableWallet/UnstoppableWallet/Configuration/Development.template.xcconfig @@ -23,3 +23,4 @@ swap_enabled = true donate_enabled = true default_words = +referral_app_server_url = diff --git a/UnstoppableWallet/UnstoppableWallet/Configuration/Production.template.xcconfig b/UnstoppableWallet/UnstoppableWallet/Configuration/Production.template.xcconfig index e144c49d22..8a936f8b0e 100644 --- a/UnstoppableWallet/UnstoppableWallet/Configuration/Production.template.xcconfig +++ b/UnstoppableWallet/UnstoppableWallet/Configuration/Production.template.xcconfig @@ -21,3 +21,4 @@ one_inch_commission = one_inch_commission_address = swap_enabled = true donate_enabled = true +referral_app_server_url = diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift b/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift index fa4742eba9..ac0d45f8a2 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Managers/DeepLinkManager.swift @@ -1,3 +1,4 @@ +import BigInt import ComponentKit import Foundation import RxRelay @@ -37,6 +38,7 @@ extension DeepLinkManager { do { let address = try parser.parse(url: url.absoluteString) newSchemeRelay.accept(.transfer(addressUri: address)) + return true } catch { HudHelper.instance.show(banner: .error(string: error.localizedDescription)) } @@ -49,8 +51,24 @@ extension DeepLinkManager { return true } + if (scheme == DeepLinkManager.deepLinkScheme && host == "referral") || (scheme == "https" && host == DeepLinkManager.deepLinkScheme && path == "/referral") { + guard let queryItems, queryItems.count == 2, + let userId = queryItems[0].value, + let referralCode = queryItems[1].value + else { + return false + } + + newSchemeRelay.accept(.referral(telegramUserId: userId, referralCode: referralCode)) + return true + } + return false } + + func setDeepLinkShown() { + newSchemeRelay.accept(nil) + } } extension DeepLinkManager { @@ -58,5 +76,6 @@ extension DeepLinkManager { case walletConnect(url: String) case coin(uid: String) case transfer(addressUri: AddressUri) + case referral(telegramUserId: String, referralCode: String) } } diff --git a/UnstoppableWallet/UnstoppableWallet/Core/Providers/AppConfig.swift b/UnstoppableWallet/UnstoppableWallet/Core/Providers/AppConfig.swift index c56967ecd6..49b4f32d64 100644 --- a/UnstoppableWallet/UnstoppableWallet/Core/Providers/AppConfig.swift +++ b/UnstoppableWallet/UnstoppableWallet/Core/Providers/AppConfig.swift @@ -129,6 +129,10 @@ enum AppConfig { (Bundle.main.object(forInfoDictionaryKey: "OneInchCommissionAddress") as? String).flatMap { $0.isEmpty ? nil : $0 } } + static var referralAppServerUrl: String { + (Bundle.main.object(forInfoDictionaryKey: "ReferralAppServerUrl") as? String) ?? "" + } + static var defaultWords: String { Bundle.main.object(forInfoDictionaryKey: "DefaultWords") as? String ?? "" } diff --git a/UnstoppableWallet/UnstoppableWallet/Info.plist b/UnstoppableWallet/UnstoppableWallet/Info.plist index 5f30b213a3..d0fc35d843 100644 --- a/UnstoppableWallet/UnstoppableWallet/Info.plist +++ b/UnstoppableWallet/UnstoppableWallet/Info.plist @@ -164,5 +164,7 @@ ${unstoppable_domains_api_key} WallectConnectV2ProjectKey ${wallet_connect_v2_project_key} + ReferralAppServerUrl + ${referral_app_server_url} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Launch/LaunchModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Launch/LaunchModule.swift index 1739b0e301..95d8206d23 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Launch/LaunchModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Launch/LaunchModule.swift @@ -12,7 +12,7 @@ enum LaunchModule { switch service.launchMode { case .passcodeNotSet: return NoPasscodeViewController(mode: .noPasscode) case .cannotCheckPasscode: return NoPasscodeViewController(mode: .cannotCheckPasscode) - case .intro: return WelcomeScreenViewController() + case .intro: return WelcomeScreenViewController.instance() case .unlock: return UnlockModule.appUnlockView(appStart: true).toViewController() case .main: return MainModule.instance() } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/DeepLinkService.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/DeepLinkService.swift index 71ea738682..0a45bd07eb 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/DeepLinkService.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/DeepLinkService.swift @@ -15,6 +15,6 @@ class DeepLinkService { } func setDeepLinkShown() { - deepLink = nil + deepLinkManager.setDeepLinkShown() } } diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainModule.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainModule.swift index 85deb0efbd..81c3280d2a 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainModule.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/MainModule.swift @@ -50,9 +50,11 @@ enum MainModule { let deepLinkHandler = WalletConnectAppShowModule.handler(parentViewController: viewController) let widgetCoinHandler = WidgetCoinAppShowModule.handler(parentViewController: viewController) let sendAddressHandler = AddressAppShowModule.handler(parentViewController: viewController) + let telegramUserHandler = TelegramUserHandler.handler(parentViewController: viewController) eventHandler.append(handler: deepLinkHandler) eventHandler.append(handler: widgetCoinHandler) eventHandler.append(handler: sendAddressHandler) + eventHandler.append(handler: telegramUserHandler) App.shared.lockDelegate.viewController = viewController diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/TelegramUserHandler.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/TelegramUserHandler.swift new file mode 100644 index 0000000000..d17d49be93 --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Main/Workers/TelegramUserHandler.swift @@ -0,0 +1,43 @@ +import MarketKit +import ObjectMapper +import RxSwift +import UIKit + +class TelegramUserHandler { + private let disposeBag = DisposeBag() + private let parentViewController: UIViewController? + private let marketKit = App.shared.marketKit + private let baseUrl = AppConfig.referralAppServerUrl + + init(parentViewController: UIViewController?) { + self.parentViewController = parentViewController + } +} + +extension TelegramUserHandler: IEventHandler { + @MainActor + func handle(source: StatPage, event: Any, eventType: EventHandler.EventType) async throws { + if eventType.contains(.deepLink), let event = event as? DeepLinkManager.DeepLink { + guard case let .referral(userId, referralCode) = event else { + throw EventHandler.HandleError.noSuitableHandler + } + let urlString = "\(baseUrl)/v1/tasks/registerApp?userId=\(userId)&referralCode=\(referralCode)" + print("Requesting: \(urlString)") + guard let url = URL(string: urlString) else { + return + } + + let _: EmptyResponse = try await App.shared.networkManager.fetch(url: url) + } + } +} + +extension TelegramUserHandler { + static func handler(parentViewController: UIViewController? = nil) -> IEventHandler { + TelegramUserHandler(parentViewController: parentViewController) + } +} + +struct EmptyResponse: ImmutableMappable { + init(map: Map) throws {} +} diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewController.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewController.swift index ea7f55aec0..854dea85b3 100644 --- a/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewController.swift +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewController.swift @@ -4,6 +4,7 @@ import ThemeKit import UIKit class WelcomeScreenViewController: ThemeViewController { + private let viewModel: WelcomeScreenViewModel private let scrollView = UIScrollView() private var textViews = [WelcomeTextView]() private let pageControl: BarPageControl @@ -19,7 +20,8 @@ class WelcomeScreenViewController: ThemeViewController { Slide(title: "intro.stay_private.title".localized, description: "intro.stay_private.description".localized, image: "Intro - Stay Private"), ] - override init() { + init(viewModel: WelcomeScreenViewModel) { + self.viewModel = viewModel pageControl = BarPageControl(barCount: slides.count) super.init() @@ -191,6 +193,12 @@ class WelcomeScreenViewController: ThemeViewController { logoTitleLabel.font = .title2 logoTitleLabel.textColor = .themeLeah logoTitleLabel.text = AppConfig.appName + + NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) + } + + @objc func appDidBecomeActive() { + viewModel.handleDeepLink() } override func viewDidAppear(_ animated: Bool) { @@ -205,6 +213,7 @@ class WelcomeScreenViewController: ThemeViewController { self?.logoWrapperView.removeFromSuperview() }) }) + viewModel.handleDeepLink() } @objc private func onTapStart() { @@ -245,6 +254,21 @@ extension WelcomeScreenViewController: UIScrollViewDelegate { } } +extension WelcomeScreenViewController { + static func instance() -> UIViewController { + let eventHandler = EventHandler() + let deepLinkService = DeepLinkService(deepLinkManager: App.shared.deepLinkManager) + let viewModel = WelcomeScreenViewModel(deepLinkService: deepLinkService, eventHandler: eventHandler) + + let viewController = WelcomeScreenViewController(viewModel: viewModel) + let telegramUserHandler = TelegramUserHandler.handler(parentViewController: viewController) + + eventHandler.append(handler: telegramUserHandler) + + return viewController + } +} + extension WelcomeScreenViewController { private struct Slide { let title: String diff --git a/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewModel.swift b/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewModel.swift new file mode 100644 index 0000000000..2a289006af --- /dev/null +++ b/UnstoppableWallet/UnstoppableWallet/Modules/Welcome/WelcomeScreenViewModel.swift @@ -0,0 +1,24 @@ +class WelcomeScreenViewModel { + private let deepLinkService: DeepLinkService + private let eventHandler: EventHandler + + init(deepLinkService: DeepLinkService, eventHandler: EventHandler) { + self.deepLinkService = deepLinkService + self.eventHandler = eventHandler + } + + func handleDeepLink() { + guard let deepLink = deepLinkService.deepLink else { + return + } + + Task { + do { + try await eventHandler.handle(source: .main, event: deepLink, eventType: .deepLink) + deepLinkService.setDeepLinkShown() + } catch { + print("Can't handle Deep Link \(error)") + } + } + } +} diff --git a/UnstoppableWallet/UnstoppableWallet/ru.lproj/Localizable.strings b/UnstoppableWallet/UnstoppableWallet/ru.lproj/Localizable.strings index bc6dbf4c2f..4f18874e63 100644 --- a/UnstoppableWallet/UnstoppableWallet/ru.lproj/Localizable.strings +++ b/UnstoppableWallet/UnstoppableWallet/ru.lproj/Localizable.strings @@ -327,9 +327,9 @@ "balance.synced_through" = "до %@"; "balance.invalid_api_key" = "Недействительный ключ API"; "balance.empty.description" = "Вы еще не добавили токены в этот кошелек."; -"balance.sort.header" = "Сортировать"; -"balance.sort.valueHighToLow" = "Баланс"; -"balance.sort.az" = "Название"; +"balance.sort.header" = "Сортировка"; +"balance.sort.valueHighToLow" = "по балансу"; +"balance.sort.az" = "по названию"; "balance.sort.price_change" = "по изменению цены (%)"; "balance_error.change_source" = "Изменить источник"; @@ -715,14 +715,14 @@ "market.tab.pairs" = "Пары"; "market.tab.sectors" = "Секторы"; -"market.sort_by.title" = "Сортировать"; +"market.sort_by.title" = "Сортировка"; "market.sort_by.manual" = "Вручную"; -"market.sort_by.highest_cap" = "Наивысшей кап."; -"market.sort_by.lowest_cap" = "Наименьшей кап."; -"market.sort_by.gainers" = "Gainers"; -"market.sort_by.losers" = "Losers"; -"market.sort_by.highest_volume" = "Наивысшему обьему"; -"market.sort_by.lowest_volume" = "Наименьшему объему"; +"market.sort_by.highest_cap" = "Наивысшая кап."; +"market.sort_by.lowest_cap" = "Наименьшая кап."; +"market.sort_by.gainers" = "Показывают рост"; +"market.sort_by.losers" = "Теряют в цене"; +"market.sort_by.highest_volume" = "Наивысший обьем"; +"market.sort_by.lowest_volume" = "Наименьший объем"; "market.top_coins.title" = "Монеты"; "market.top_coins" = "Топ %@"; @@ -771,10 +771,10 @@ "market.top.title" = "Лучшие токены"; "market.top.description" = "Топ токенов по рыночной капитализации"; -"market.top.highest_cap" = "Наивысшей кап."; -"market.top.lowest_cap" = "Наименьшей кап."; -"market.top.highest_volume" = "Наивысшему обьему"; -"market.top.lowest_volume" = "Наименьшему объему"; +"market.top.highest_cap" = "Наивысшая кап."; +"market.top.lowest_cap" = "Наименьшая кап."; +"market.top.highest_volume" = "Наивысший обьем"; +"market.top.lowest_volume" = "Наименьший объем"; "market.top.top_gainers" = "Показывают рост"; "market.top.top_losers" = "Теряют в цене"; "market.top.top_collections" = "Топ NFT коллекции"; @@ -901,7 +901,7 @@ "market.etf.title" = "Общий чистый приток"; "market.etf.description" = "Чистый приток (net inflow) ETF равен разнице между поступлениями и оттоками наличных средств."; -"market.etf.total_net_assets" = "Всего чистых активов"; +"market.etf.total_net_assets" = "Общие активы"; "market.etf.sort_by.highest_assets" = "Наивысшие активы"; "market.etf.sort_by.lowest_assets" = "Наименьшие активы"; "market.etf.sort_by.inflow" = "Приток"; @@ -1552,7 +1552,7 @@ "appearance.markets_tab" = "Вкладка рынки"; "appearance.hide_markets" = "Скрыть рынки"; -"appearance.price_change" = "по изменению цены (%)"; +"appearance.price_change" = "Изменение цены"; "appearance.price_change.24h" = "24ч"; "appearance.price_change.1d" = "Полночь по UTC"; diff --git a/UnstoppableWallet/Widget/Localizable.xcstrings b/UnstoppableWallet/Widget/Localizable.xcstrings index 31309dd918..280711b9f5 100644 --- a/UnstoppableWallet/Widget/Localizable.xcstrings +++ b/UnstoppableWallet/Widget/Localizable.xcstrings @@ -3,51 +3,291 @@ "strings" : { "%@number.billion" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@B" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "%@B" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@B" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@G" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@십억" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@B" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@млрд" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@B" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@十亿" + } } } }, "%@number.million" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@M" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "%@M" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@M" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@M" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@백만" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@M" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@млн" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@M" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@百万" + } } } }, "%@number.quadrillion" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@Q" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "%@Q" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@Q" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@P" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@천조" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@Q" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@квадрл" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@Q" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@拍" + } } } }, "%@number.thousand" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@K" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "%@K" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@K" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@K" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@천" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@K" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@тыс" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@K" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@千" + } } } }, "%@number.trillion" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@T" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "%@T" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@T" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@MM" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@일조" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@T" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@трл" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@T" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@万亿" + } } } }, @@ -171,11 +411,59 @@ }, "sort_type.gainers" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gewinner" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Gainers" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ganadores" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gagnants" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이득자" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ganhadores" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывают рост" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kazanlar" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "涨幅最大者" + } } } }, @@ -239,11 +527,59 @@ }, "sort_type.losers" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verlierer" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Losers" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perdedores" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perdants" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "해자" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perdedores" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Теряют в цене" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kaybedenler" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "跌幅最大者" + } } } }, @@ -307,11 +643,59 @@ }, "sort_type.manual" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuell" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Manual" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manuel" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "수동" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manual" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вручную" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "El kitabı" + } + }, + "zh" : { + "stringUnit" : { + "state" : "translated", + "value" : "手册" + } } } }, diff --git a/UnstoppableWallet/Widget/Misc/ApiProvider.swift b/UnstoppableWallet/Widget/Misc/ApiProvider.swift index bd28ece00f..d1dae739be 100644 --- a/UnstoppableWallet/Widget/Misc/ApiProvider.swift +++ b/UnstoppableWallet/Widget/Misc/ApiProvider.swift @@ -24,7 +24,7 @@ class ApiProvider { func topCoins(limit: Int) async throws -> [Coin] { let parameters: Parameters = [ "limit": limit, - "fields": "uid,name,code", + "fields": "uid,name,code,image", "order_by_rank": "true", ] @@ -48,7 +48,7 @@ class ApiProvider { func coinWithPrice(uid: String, currencyCode: String) async throws -> Coin { let parameters: Parameters = [ "uids": uid, - "fields": "uid,name,code,price,price_change_24h,price_change_1d", + "fields": "uid,name,code,price,price_change_24h,price_change_1d,image", "currency": currencyCode.lowercased(), ] @@ -102,6 +102,7 @@ struct Coin: ImmutableMappable { let priceChange1w: Decimal? let priceChange1m: Decimal? let priceChange3m: Decimal? + let imageUrl: String? init(map: Map) throws { uid = try map.value("uid") @@ -115,6 +116,7 @@ struct Coin: ImmutableMappable { priceChange1w = try? map.value("price_change_1w", using: Transform.stringToDecimalTransform) priceChange1m = try? map.value("price_change_1m", using: Transform.stringToDecimalTransform) priceChange3m = try? map.value("price_change_3m", using: Transform.stringToDecimalTransform) + imageUrl = try? map.value("image") } } diff --git a/UnstoppableWallet/Widget/Misc/Extensions.swift b/UnstoppableWallet/Widget/Misc/Extensions.swift index 233cf694c3..bd9cd75967 100644 --- a/UnstoppableWallet/Widget/Misc/Extensions.swift +++ b/UnstoppableWallet/Widget/Misc/Extensions.swift @@ -2,13 +2,13 @@ import SwiftUI extension Coin { var image: Image? { - let iconUrl = "https://cdn.blocksdecoded.com/coin-icons/32px/\(uid)@3x.png" - - guard let url = URL(string: iconUrl) else { return nil } - guard let data = try? Data(contentsOf: url) else { return nil } - guard let uiImage = UIImage(data: data) else { return nil } - - return Image(uiImage: uiImage) + do { + let iconUrl = "https://cdn.blocksdecoded.com/coin-icons/32px/\(uid)@3x.png" + return try image(url: iconUrl) + } catch { + guard let alternativeUrl = imageUrl else { return nil } + return try? image(url: alternativeUrl) + } } func formattedPrice(currency: Currency) -> String { @@ -27,6 +27,14 @@ extension Coin { return priceChange >= 0 ? .up : .down } + private func image(url: String) throws -> Image? { + guard let url = URL(string: url) else { return nil } + let data = try Data(contentsOf: url) + + guard let uiImage = UIImage(data: data) else { return nil } + return Image(uiImage: uiImage) + } + private func priceChange(timePeriod: WatchlistTimePeriod) -> Decimal? { switch timePeriod { case .hour24: return priceChange24h diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c94fb2a9df..abf8c1a596 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -27,6 +27,7 @@ XCCONFIG_DEV_UNSTOPPABLE_DOMAINS_API_KEY = ENV["XCCONFIG_DEV_UNSTOPPABLE_DOMAINS XCCONFIG_DEV_ONE_INCH_API_KEY = ENV["XCCONFIG_DEV_ONE_INCH_API_KEY"] XCCONFIG_DEV_ONE_INCH_COMMISSION = ENV["XCCONFIG_DEV_ONE_INCH_COMMISSION"] XCCONFIG_DEV_ONE_INCH_COMMISSION_ADDRESS = ENV["XCCONFIG_DEV_ONE_INCH_COMMISSION_ADDRESS"] +XCCONFIG_DEV_REFERRAL_APP_SERVER_URL = ENV["XCCONFIG_DEV_REFERRAL_APP_SERVER_URL"] XCCONFIG_PROD_ETHERSCAN_API_KEY = ENV["XCCONFIG_PROD_ETHERSCAN_API_KEY"] XCCONFIG_PROD_ARBISCAN_API_KEY = ENV["XCCONFIG_PROD_ARBISCAN_API_KEY"] @@ -46,6 +47,7 @@ XCCONFIG_PROD_UNSTOPPABLE_DOMAINS_API_KEY = ENV["XCCONFIG_PROD_UNSTOPPABLE_DOMAI XCCONFIG_PROD_ONE_INCH_API_KEY = ENV["XCCONFIG_PROD_ONE_INCH_API_KEY"] XCCONFIG_PROD_ONE_INCH_COMMISSION = ENV["XCCONFIG_PROD_ONE_INCH_COMMISSION"] XCCONFIG_PROD_ONE_INCH_COMMISSION_ADDRESS = ENV["XCCONFIG_PROD_ONE_INCH_COMMISSION_ADDRESS"] +XCCONFIG_PROD_REFERRAL_APP_SERVER_URL = ENV["XCCONFIG_PROD_REFERRAL_APP_SERVER_URL"] def delete_temp_keychain(name) delete_keychain( @@ -127,6 +129,7 @@ def apply_dev_xcconfig update_dev_xcconfig('one_inch_api_key', XCCONFIG_DEV_ONE_INCH_API_KEY) update_dev_xcconfig('one_inch_commission', XCCONFIG_DEV_ONE_INCH_COMMISSION) update_dev_xcconfig('one_inch_commission_address', XCCONFIG_DEV_ONE_INCH_COMMISSION_ADDRESS) + update_dev_xcconfig('referral_app_server_url', XCCONFIG_DEV_REFERRAL_APP_SERVER_URL) end def apply_prod_xcconfig(swap_enabled, donate_enabled) @@ -150,6 +153,7 @@ def apply_prod_xcconfig(swap_enabled, donate_enabled) update_prod_xcconfig('one_inch_api_key', XCCONFIG_PROD_ONE_INCH_API_KEY) update_prod_xcconfig('one_inch_commission', XCCONFIG_PROD_ONE_INCH_COMMISSION) update_prod_xcconfig('one_inch_commission_address', XCCONFIG_PROD_ONE_INCH_COMMISSION_ADDRESS) + update_prod_xcconfig('referral_app_server_url', XCCONFIG_PROD_REFERRAL_APP_SERVER_URL) end def deploy_production