package org.multibit.hd.ui.views.wizards; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Uninterruptibles; import org.multibit.hd.core.config.Configuration; import org.multibit.hd.core.config.Configurations; import org.multibit.hd.core.dto.*; import org.multibit.hd.core.managers.WalletManager; import org.multibit.hd.core.services.CoreServices; import org.multibit.hd.ui.views.wizards.about.AboutState; import org.multibit.hd.ui.views.wizards.about.AboutWizard; import org.multibit.hd.ui.views.wizards.about.AboutWizardModel; import org.multibit.hd.ui.views.wizards.appearance_settings.AppearanceSettingsState; import org.multibit.hd.ui.views.wizards.appearance_settings.AppearanceSettingsWizard; import org.multibit.hd.ui.views.wizards.appearance_settings.AppearanceSettingsWizardModel; import org.multibit.hd.ui.views.wizards.buy_sell.BuySellState; import org.multibit.hd.ui.views.wizards.buy_sell.BuySellWizard; import org.multibit.hd.ui.views.wizards.buy_sell.BuySellWizardModel; import org.multibit.hd.ui.views.wizards.change_password.ChangePasswordState; import org.multibit.hd.ui.views.wizards.change_password.ChangePasswordWizard; import org.multibit.hd.ui.views.wizards.change_password.ChangePasswordWizardModel; import org.multibit.hd.ui.views.wizards.change_pin.ChangePinState; import org.multibit.hd.ui.views.wizards.change_pin.ChangePinWizard; import org.multibit.hd.ui.views.wizards.change_pin.ChangePinWizardModel; import org.multibit.hd.ui.views.wizards.credentials.CredentialsRequestType; import org.multibit.hd.ui.views.wizards.credentials.CredentialsState; import org.multibit.hd.ui.views.wizards.credentials.CredentialsWizard; import org.multibit.hd.ui.views.wizards.credentials.CredentialsWizardModel; import org.multibit.hd.ui.views.wizards.edit_contact.EditContactState; import org.multibit.hd.ui.views.wizards.edit_contact.EditContactWizard; import org.multibit.hd.ui.views.wizards.edit_contact.EditContactWizardModel; import org.multibit.hd.ui.views.wizards.edit_contact.EnterContactDetailsMode; import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletState; import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletWizard; import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletWizardModel; import org.multibit.hd.ui.views.wizards.empty_wallet.EmptyWalletState; import org.multibit.hd.ui.views.wizards.empty_wallet.EmptyWalletWizard; import org.multibit.hd.ui.views.wizards.empty_wallet.EmptyWalletWizardModel; import org.multibit.hd.ui.views.wizards.exchange_settings.ExchangeSettingsState; import org.multibit.hd.ui.views.wizards.exchange_settings.ExchangeSettingsWizard; import org.multibit.hd.ui.views.wizards.exchange_settings.ExchangeSettingsWizardModel; import org.multibit.hd.ui.views.wizards.exit.ExitState; import org.multibit.hd.ui.views.wizards.exit.ExitWizard; import org.multibit.hd.ui.views.wizards.exit.ExitWizardModel; import org.multibit.hd.ui.views.wizards.export_payments.ExportPaymentsWizard; import org.multibit.hd.ui.views.wizards.export_payments.ExportPaymentsWizardModel; import org.multibit.hd.ui.views.wizards.export_payments.ExportPaymentsWizardState; import org.multibit.hd.ui.views.wizards.fee_settings.FeeSettingsState; import org.multibit.hd.ui.views.wizards.fee_settings.FeeSettingsWizard; import org.multibit.hd.ui.views.wizards.fee_settings.FeeSettingsWizardModel; import org.multibit.hd.ui.views.wizards.lab_settings.LabSettingsState; import org.multibit.hd.ui.views.wizards.lab_settings.LabSettingsWizard; import org.multibit.hd.ui.views.wizards.lab_settings.LabSettingsWizardModel; import org.multibit.hd.ui.views.wizards.language_settings.LanguageSettingsState; import org.multibit.hd.ui.views.wizards.language_settings.LanguageSettingsWizard; import org.multibit.hd.ui.views.wizards.language_settings.LanguageSettingsWizardModel; import org.multibit.hd.ui.views.wizards.payment_settings.PaymentSettingsState; import org.multibit.hd.ui.views.wizards.payment_settings.PaymentSettingsWizard; import org.multibit.hd.ui.views.wizards.payment_settings.PaymentSettingsWizardModel; import org.multibit.hd.ui.views.wizards.payments.PaymentsState; import org.multibit.hd.ui.views.wizards.payments.PaymentsWizard; import org.multibit.hd.ui.views.wizards.payments.PaymentsWizardModel; import org.multibit.hd.ui.views.wizards.repair_wallet.RepairWalletState; import org.multibit.hd.ui.views.wizards.repair_wallet.RepairWalletWizard; import org.multibit.hd.ui.views.wizards.repair_wallet.RepairWalletWizardModel; import org.multibit.hd.ui.views.wizards.request_bitcoin.RequestBitcoinState; import org.multibit.hd.ui.views.wizards.request_bitcoin.RequestBitcoinWizard; import org.multibit.hd.ui.views.wizards.request_bitcoin.RequestBitcoinWizardModel; import org.multibit.hd.ui.views.wizards.send_bitcoin.SendBitcoinParameter; import org.multibit.hd.ui.views.wizards.send_bitcoin.SendBitcoinState; import org.multibit.hd.ui.views.wizards.send_bitcoin.SendBitcoinWizard; import org.multibit.hd.ui.views.wizards.send_bitcoin.SendBitcoinWizardModel; import org.multibit.hd.ui.views.wizards.sign_message.SignMessageState; import org.multibit.hd.ui.views.wizards.sign_message.SignMessageWizard; import org.multibit.hd.ui.views.wizards.sign_message.SignMessageWizardModel; import org.multibit.hd.ui.views.wizards.sound_settings.SoundSettingsState; import org.multibit.hd.ui.views.wizards.sound_settings.SoundSettingsWizard; import org.multibit.hd.ui.views.wizards.sound_settings.SoundSettingsWizardModel; import org.multibit.hd.ui.views.wizards.units_settings.UnitsSettingsState; import org.multibit.hd.ui.views.wizards.units_settings.UnitsSettingsWizard; import org.multibit.hd.ui.views.wizards.units_settings.UnitsWizardModel; import org.multibit.hd.ui.views.wizards.use_hardware_wallet.UseHardwareWalletState; import org.multibit.hd.ui.views.wizards.use_hardware_wallet.UseHardwareWalletWizard; import org.multibit.hd.ui.views.wizards.use_hardware_wallet.UseHardwareWalletWizardModel; import org.multibit.hd.ui.views.wizards.verify_message.VerifyMessageState; import org.multibit.hd.ui.views.wizards.verify_message.VerifyMessageWizard; import org.multibit.hd.ui.views.wizards.verify_message.VerifyMessageWizardModel; import org.multibit.hd.ui.views.wizards.verify_network.VerifyNetworkState; import org.multibit.hd.ui.views.wizards.verify_network.VerifyNetworkWizard; import org.multibit.hd.ui.views.wizards.verify_network.VerifyNetworkWizardModel; import org.multibit.hd.ui.views.wizards.wallet_details.WalletDetailsState; import org.multibit.hd.ui.views.wizards.wallet_details.WalletDetailsWizard; import org.multibit.hd.ui.views.wizards.wallet_details.WalletDetailsWizardModel; import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizard; import org.multibit.hd.core.dto.WalletMode; import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizardModel; import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizardState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.TimeUnit; /** * <p>Factory to provide the following to UI:</p> * <ul> * <li>Provision of different wizards targeting various use cases</li> * </ul> * * <h3>Overview of the Wizard architecture</h3> * * <p>A wizard presents a series of panels enclosed in a light box. This is in contrast to the * standard modal dialog approach offered by Swing which is more limited and offers less customisation * opportunities.</p> * * <p>From a data perspective each wizard consists of one "wizard model" which has many "panel models" * each of which have many "component models". Components are reused across panels and so do not maintain * a back reference to a parent panels but instead use a <code>WizardComponentModelChangedEvent</code> to * inform all interested panels that their data has changed. Events are filtered by the panel name to prevent * collisions.</p> * * <p>A "wizard view" has a consistent layout: a title and description (top), some components (middle) and a row of * buttons (bottom). The top and bottom rows are handled mainly by boilerplate code leaving just the presentation * and management of the middle section to the developer.</p> * * <h3>Quickly assembling a wizard</h3> * * <p>The quickest way to get a wizard up and running is to take an existing one and modify it accordingly. If * your requirement is straightforward (no MaV components or reliance on previous panels) then the boilerplate * will handle all the work for you.</p> * * @since 0.0.1 */ public class Wizards { private static final Logger log = LoggerFactory.getLogger(Wizards.class); private static CredentialsWizard credentialsWizard = null; /** * @return A new "exit" wizard */ public static ExitWizard newExitWizard() { log.debug("New 'Exit wizard'"); return new ExitWizard(new ExitWizardModel(ExitState.SELECT_RESET_OPTION), true); } /** * @return A new "buy/sell" wizard */ public static BuySellWizard newBuySellWizard() { log.debug("New 'Buy/sell wizard'"); return new BuySellWizard(new BuySellWizardModel(BuySellState.SHOW_PARTNER_NOTES)); } /** * @return A new "about" wizard */ public static AboutWizard newAboutWizard() { log.debug("New 'About wizard'"); return new AboutWizard(new AboutWizardModel(AboutState.ABOUT_DETAILS)); } /** * @param parameter Providing information about how the send should be performed * * @return A new "send bitcoin" wizard */ public static SendBitcoinWizard newSendBitcoinWizard(SendBitcoinParameter parameter) { log.debug("New 'Send bitcoin wizard'"); return new SendBitcoinWizard(new SendBitcoinWizardModel(SendBitcoinState.SEND_ENTER_AMOUNT, parameter)); } /** * @return A new "request bitcoin" wizard */ public static RequestBitcoinWizard newRequestBitcoinWizard() { log.debug("New 'Request bitcoin wizard'"); return new RequestBitcoinWizard(new RequestBitcoinWizardModel(RequestBitcoinState.REQUEST_ENTER_DETAILS)); } /** * @param contacts The list of contacts to edit * @param mode The editing mode * * @return A new "edit contact" wizard for contacts */ public static EditContactWizard newEditContactWizard(List<Contact> contacts, EnterContactDetailsMode mode) { log.debug("New 'Edit contact wizard'"); Preconditions.checkState(!contacts.isEmpty(), "'contacts' cannot be empty"); Preconditions.checkNotNull(mode, "'mode' must be present"); return new EditContactWizard( new EditContactWizardModel(EditContactState.EDIT_CONTACT_ENTER_DETAILS, contacts), mode ); } /** * @param initialState The initial state * @param walletMode The wallet mode * @return A new "welcome" wizard for the initial set up */ public static WelcomeWizard newExitingWelcomeWizard(WelcomeWizardState initialState, WalletMode walletMode) { log.debug("New 'Exiting welcome wizard'. Initial state: {}, mode: {}", initialState, walletMode); Preconditions.checkNotNull(initialState, "'initialState' must be present"); // Allow time for any background UI tasks to complete before starting up // This reduces the chance of a language change with keyboard causing a // crash on startup (someone has to be deliberately hammering to trigger a failure) Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); return new WelcomeWizard(new WelcomeWizardModel(initialState), true); } /** * @return A new "sign message" wizard */ public static SignMessageWizard newSignMessageWizard() { WalletMode walletMode = WalletMode.of(CoreServices.getCurrentHardwareWalletService()); switch (walletMode) { case STANDARD: log.debug("New 'Sign message wizard' with password"); return new SignMessageWizard( new SignMessageWizardModel(SignMessageState.SIGN_MESSAGE_PASSWORD), false ); case TREZOR: // Fall through case KEEP_KEY: log.debug("New 'Sign message wizard' with hardware wallet"); return new SignMessageWizard( new SignMessageWizardModel(SignMessageState.SIGN_MESSAGE_HARDWARE), false ); default: throw new IllegalStateException("Unknown hardware wallet: " + walletMode.name()); } } /** * @return A new "verify message" wizard for a warm start */ public static VerifyMessageWizard newVerifyMessageWizard() { log.debug("New 'Verify message wizard'"); return new VerifyMessageWizard(new VerifyMessageWizardModel(VerifyMessageState.EDIT_MESSAGE)); } /** * @param credentialsRequestType whether the user enters a password or PIN * * @return A new "credentials" wizard for a warm start */ public static CredentialsWizard newExitingCredentialsWizard(CredentialsRequestType credentialsRequestType) { // Use a synchronized block here to ensure that hardware wallets are // handled correctly during startup synchronized (Wizards.class) { log.debug("Creating 'Credentials wizard' with credentialsRequestType = " + credentialsRequestType); CredentialsWizardModel model; switch (credentialsRequestType) { case HARDWARE: model = new CredentialsWizardModel(CredentialsState.CREDENTIALS_REQUEST_MASTER_PUBLIC_KEY); break; case PASSWORD: model = new CredentialsWizardModel(CredentialsState.CREDENTIALS_ENTER_PASSWORD); break; default: throw new UnsupportedOperationException("The '" + credentialsRequestType.name() + "' is not supported"); } if (credentialsWizard != null) { // Clear down all existing subscriptions credentialsWizard.unsubscribeAll(); } credentialsWizard = new CredentialsWizard(model, true); log.debug("CredentialsWizard: {}", credentialsWizard); } return credentialsWizard; } /** * @return A new "use trezor" wizard */ public static UseHardwareWalletWizard newUseTrezorWizard() { return new UseHardwareWalletWizard(new UseHardwareWalletWizardModel(UseHardwareWalletState.SELECT_HARDWARE_ACTION)); } /** * @return A new "change credentials" wizard */ public static ChangePasswordWizard newChangePasswordWizard() { log.debug("New 'Change credentials wizard'"); return new ChangePasswordWizard(new ChangePasswordWizardModel(ChangePasswordState.CHANGE_PASSWORD_ENTER_PASSWORD)); } /** * @return A new "change PIN" wizard */ public static ChangePinWizard newChangePinWizard() { log.debug("New 'Change PIN wizard'"); return new ChangePinWizard(new ChangePinWizardModel(ChangePinState.SELECT_OPTION)); } /** * @return A new "verify network" wizard */ public static VerifyNetworkWizard newVerifyNetworkWizard() { log.debug("New 'Verify network wizard'"); return new VerifyNetworkWizard(new VerifyNetworkWizardModel(VerifyNetworkState.VERIFY_NETWORK_SHOW_REPORT)); } /** * @return A new "repair wallet" wizard */ public static RepairWalletWizard newRepairWalletWizard() { log.debug("New 'Repair wallet wizard'"); return new RepairWalletWizard(new RepairWalletWizardModel(RepairWalletState.REPAIR_WALLET)); } /** * @return A new "empty wallet" wizard */ public static EmptyWalletWizard newEmptyWalletWizard() { log.debug("New 'Empty wallet wizard'"); return new EmptyWalletWizard(new EmptyWalletWizardModel(EmptyWalletState.EMPTY_WALLET_ENTER_DETAILS)); } /** * @return A new "payment settings" wizard */ public static PaymentSettingsWizard newPaymentSettingsWizard() { log.debug("New 'Payment wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new PaymentSettingsWizard(new PaymentSettingsWizardModel(PaymentSettingsState.PAYMENT_SETTINGS, configuration)); } /** * @return A new "language settings" wizard for language selection */ public static LanguageSettingsWizard newLanguageSettingsWizard() { log.debug("New 'Language settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new LanguageSettingsWizard(new LanguageSettingsWizardModel(LanguageSettingsState.LANGUAGE_ENTER_DETAILS, configuration)); } /** * @return A new "appearance settings" wizard for theme selection */ public static AppearanceSettingsWizard newAppearanceSettingsWizard() { log.debug("New 'Appearance settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new AppearanceSettingsWizard(new AppearanceSettingsWizardModel(AppearanceSettingsState.APPEARANCE_ENTER_DETAILS, configuration)); } /** * @return A new "fee settings" wizard for fee customisation */ public static FeeSettingsWizard newFeeSettingsWizard() { log.debug("New 'Fee settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new FeeSettingsWizard(new FeeSettingsWizardModel(FeeSettingsState.FEE_ENTER_DETAILS, configuration)); } /** * @return A new "sound settings" wizard for sound selection */ public static SoundSettingsWizard newSoundSettingsWizard() { log.debug("New 'Sound settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new SoundSettingsWizard(new SoundSettingsWizardModel(SoundSettingsState.SOUND_ENTER_DETAILS, configuration)); } /** * @return A new "lab settings" wizard for experimental features selection */ public static LabSettingsWizard newLabSettingsWizard() { log.debug("New 'Lab settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new LabSettingsWizard(new LabSettingsWizardModel(LabSettingsState.LAB_ENTER_DETAILS, configuration)); } /** * @return A new "Units settings" wizard for currency unit selection */ public static UnitsSettingsWizard newUnitsSettingsWizard() { log.debug("New 'Units settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new UnitsSettingsWizard(new UnitsWizardModel(UnitsSettingsState.UNITS_ENTER_DETAILS, configuration)); } /** * @return A new "Exchange settings" wizard for exchange rate provider selection */ public static ExchangeSettingsWizard newExchangeSettingsWizard() { log.debug("New 'Exchange settings wizard'"); // Ensure we work with a copy of the current configuration in case of cancellation Configuration configuration = Configurations.currentConfiguration.deepCopy(); return new ExchangeSettingsWizard(new ExchangeSettingsWizardModel(ExchangeSettingsState.EXCHANGE_ENTER_DETAILS, configuration)); } /** * @return A new "payments" wizard */ public static PaymentsWizard newPaymentsWizard(PaymentData paymentData) { log.debug("New 'Payments wizard'"); Preconditions.checkNotNull(paymentData, "'paymentData' must be present"); PaymentsWizardModel paymentsWizardModel; if (paymentData instanceof MBHDPaymentRequestData) { paymentsWizardModel = new PaymentsWizardModel(PaymentsState.PAYMENT_REQUEST_DETAILS, paymentData); paymentsWizardModel.setMBHDPaymentRequestData((MBHDPaymentRequestData) paymentData); paymentsWizardModel.setShowPrevOnPaymentRequestDetailScreen(false); } else if (paymentData instanceof PaymentRequestData) { paymentsWizardModel = new PaymentsWizardModel(PaymentsState.BIP70_PAYMENT_REQUEST_DETAILS, paymentData); paymentsWizardModel.setPaymentRequestData((PaymentRequestData) paymentData); paymentsWizardModel.setShowPrevOnPaymentRequestDetailScreen(false); } else { paymentsWizardModel = new PaymentsWizardModel(PaymentsState.TRANSACTION_OVERVIEW, paymentData); paymentsWizardModel.setShowPrevOnPaymentRequestDetailScreen(true); } return new PaymentsWizard(paymentsWizardModel); } /** * @return A new "export payments" wizard */ public static ExportPaymentsWizard newExportPaymentsWizard(ExportPaymentsWizardState initialState) { log.debug("New 'Export payments wizard'"); return new ExportPaymentsWizard(new ExportPaymentsWizardModel(initialState)); } /** * @return A new "edit wallet" wizard for adjusted wallet details */ public static EditWalletWizard newEditWalletWizard() { log.debug("New 'Edit wallet wizard'"); Optional<WalletSummary> currentWalletSummary = WalletManager.INSTANCE.getCurrentWalletSummary(); Preconditions.checkState(currentWalletSummary.isPresent(), "'currentWalletSummary' must be present"); return new EditWalletWizard(new EditWalletWizardModel(EditWalletState.EDIT_WALLET, currentWalletSummary.get())); } /** * @return A new "wallet details" wizard for reviewing wallet capabilities */ public static WalletDetailsWizard newWalletDetailsWizard() { log.debug("New 'Wallet details wizard'"); Optional<WalletSummary> currentWalletSummary = WalletManager.INSTANCE.getCurrentWalletSummary(); Preconditions.checkState(currentWalletSummary.isPresent(), "'currentWalletSummary' must be present"); return new WalletDetailsWizard(new WalletDetailsWizardModel(WalletDetailsState.WALLET_DETAILS, currentWalletSummary.get())); } }