| 1 | import 'package:bloc/bloc.dart'; |
| 2 | import 'package:bloc_concurrency/bloc_concurrency.dart'; |
| 3 | import 'package:cake_wallet/bitcoin/bitcoin.dart'; |
| 4 | import 'package:cake_wallet/core/address_resolver/lnurl_pay/lnurlpay_record.dart'; |
| 5 | import 'package:cake_wallet/generated/i18n.dart'; |
| 6 | import 'package:cw_core/crypto_currency.dart'; |
| 7 | import 'package:cw_core/wallet_base.dart'; |
| 8 | import 'package:meta/meta.dart'; |
| 9 | |
| 10 | part 'lightning_username_event.dart'; |
| 11 | |
| 12 | part 'lightning_username_state.dart'; |
| 13 | |
| 14 | class UsernameError { |
| 15 | String message; |
| 16 | bool isInfo; |
| 17 | |
| 18 | UsernameError(this.message, {this.isInfo = false}); |
| 19 | } |
| 20 | |
| 21 | // TODO find other errors that api returns |
| 22 | final Map<String, String> errorPatterns = {"name already taken": S.current.username_not_available}; |
| 23 | |
| 24 | const usernameRegex = r"^[a-z0-9_.-]+$"; |
| 25 | const usernameSuffix = "@cake.cash"; |
| 26 | |
| 27 | class LightningUsernameBloc extends Bloc<LightningUsernameEvent, LightningUsernameState> { |
| 28 | final WalletBase _wallet; |
| 29 | |
| 30 | LightningUsernameBloc(this._wallet) : super(LightningUsernameInitial("")) { |
| 31 | on<ChangeUsername>(onUsernameChanged, transformer: restartable()); |
| 32 | |
| 33 | on<RequestUsernameSave>(onUsernameSaveRequested); |
| 34 | |
| 35 | on<_Init>((event, emit) async { |
| 36 | final username = await bitcoin!.getLightningUsername(_wallet); |
| 37 | emit(LightningUsernameInitial(username ?? "")); |
| 38 | }); |
| 39 | |
| 40 | add(_Init()); |
| 41 | } |
| 42 | |
| 43 | Future<void> onUsernameChanged(ChangeUsername event, Emitter<LightningUsernameState> emit) async { |
| 44 | emit(LightningUsernameChecking(event.newUsername)); |
| 45 | |
| 46 | final error = await getUsernameError(event.newUsername); |
| 47 | |
| 48 | if (error != null) { |
| 49 | emit(LightningUsernameError(event.newUsername, error)); |
| 50 | } else { |
| 51 | emit(LightningUsernameReady(event.newUsername)); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | Future<void> onUsernameSaveRequested( |
| 56 | RequestUsernameSave event, Emitter<LightningUsernameState> emit) async { |
| 57 | emit(LightningUsernameSaving(state.username)); |
| 58 | try { |
| 59 | await bitcoin!.setLightningUsername(_wallet, state.username); |
| 60 | emit(LightningUsernameSaved(state.username)); |
| 61 | } catch (e) { |
| 62 | for (final pattern in errorPatterns.keys) { |
| 63 | if (e.toString().contains(pattern)) { |
| 64 | emit(LightningUsernameError(state.username, UsernameError(errorPatterns[pattern]!))); |
| 65 | return; |
| 66 | } |
| 67 | } |
| 68 | emit(LightningUsernameError( |
| 69 | state.username, UsernameError(bitcoin!.getBreezSdkError(e) ?? e.toString()))); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | Future<UsernameError?> getUsernameError(String username) async { |
| 74 | if (!(RegExp(usernameRegex).hasMatch(username))) { |
| 75 | return UsernameError("${S.current.username_character_error} a-z, 0-9, - _ ."); |
| 76 | } |
| 77 | |
| 78 | if (username.length < 1 || username.length > 64) { |
| 79 | return UsernameError(S.current.username_length_error_1_64); |
| 80 | } |
| 81 | |
| 82 | if (username == (await bitcoin!.getLightningUsername(_wallet))) { |
| 83 | return UsernameError(S.current.already_your_username, isInfo: true); |
| 84 | } |
| 85 | |
| 86 | try { |
| 87 | if ((await LNUrlPayRecord.checkWellKnownUsername( |
| 88 | "${username}${usernameSuffix}", CryptoCurrency.btc, |
| 89 | allowRethrow: true)) != |
| 90 | null) { |
| 91 | return UsernameError(S.current.username_not_available); |
| 92 | } |
| 93 | } catch (e) { |
| 94 | return UsernameError(S.current.username_check_your_network); |
| 95 | } |
| 96 | |
| 97 | return null; |
| 98 | } |
| 99 | } |