diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip index 07c4d6521..fe11a5463 100644 Binary files a/assets/default_themes/dark.zip and b/assets/default_themes/dark.zip differ diff --git a/assets/default_themes/light.zip b/assets/default_themes/light.zip index 649716126..1453d6ba0 100644 Binary files a/assets/default_themes/light.zip and b/assets/default_themes/light.zip differ diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index 32878366a..01e32bb2d 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -45,7 +45,7 @@ class StackTheme { case "dark": return Brightness.dark; default: - // just return light instead of a possible crash causing error + // just return light instead of a possible crash causing error return Brightness.light; } } @@ -131,8 +131,8 @@ class StackTheme { @ignore Color get accentColorBlue => _accentColorBlue ??= Color( - accentColorBlueInt, - ); + accentColorBlueInt, + ); @ignore Color? _accentColorBlue; late final int accentColorBlueInt; @@ -141,8 +141,8 @@ class StackTheme { @ignore Color get accentColorGreen => _accentColorGreen ??= Color( - accentColorGreenInt, - ); + accentColorGreenInt, + ); @ignore Color? _accentColorGreen; late final int accentColorGreenInt; @@ -151,8 +151,8 @@ class StackTheme { @ignore Color get accentColorYellow => _accentColorYellow ??= Color( - accentColorYellowInt, - ); + accentColorYellowInt, + ); @ignore Color? _accentColorYellow; late final int accentColorYellowInt; @@ -161,8 +161,8 @@ class StackTheme { @ignore Color get accentColorRed => _accentColorRed ??= Color( - accentColorRedInt, - ); + accentColorRedInt, + ); @ignore Color? _accentColorRed; late final int accentColorRedInt; @@ -171,8 +171,8 @@ class StackTheme { @ignore Color get accentColorOrange => _accentColorOrange ??= Color( - accentColorOrangeInt, - ); + accentColorOrangeInt, + ); @ignore Color? _accentColorOrange; late final int accentColorOrangeInt; @@ -181,8 +181,8 @@ class StackTheme { @ignore Color get accentColorDark => _accentColorDark ??= Color( - accentColorDarkInt, - ); + accentColorDarkInt, + ); @ignore Color? _accentColorDark; late final int accentColorDarkInt; @@ -191,8 +191,8 @@ class StackTheme { @ignore Color get shadow => _shadow ??= Color( - shadowInt, - ); + shadowInt, + ); @ignore Color? _shadow; late final int shadowInt; @@ -201,8 +201,8 @@ class StackTheme { @ignore Color get textDark => _textDark ??= Color( - textDarkInt, - ); + textDarkInt, + ); @ignore Color? _textDark; late final int textDarkInt; @@ -211,8 +211,8 @@ class StackTheme { @ignore Color get textDark2 => _textDark2 ??= Color( - textDark2Int, - ); + textDark2Int, + ); @ignore Color? _textDark2; late final int textDark2Int; @@ -221,8 +221,8 @@ class StackTheme { @ignore Color get textDark3 => _textDark3 ??= Color( - textDark3Int, - ); + textDark3Int, + ); @ignore Color? _textDark3; late final int textDark3Int; @@ -231,8 +231,8 @@ class StackTheme { @ignore Color get textSubtitle1 => _textSubtitle1 ??= Color( - textSubtitle1Int, - ); + textSubtitle1Int, + ); @ignore Color? _textSubtitle1; late final int textSubtitle1Int; @@ -241,8 +241,8 @@ class StackTheme { @ignore Color get textSubtitle2 => _textSubtitle2 ??= Color( - textSubtitle2Int, - ); + textSubtitle2Int, + ); @ignore Color? _textSubtitle2; late final int textSubtitle2Int; @@ -251,8 +251,8 @@ class StackTheme { @ignore Color get textSubtitle3 => _textSubtitle3 ??= Color( - textSubtitle3Int, - ); + textSubtitle3Int, + ); @ignore Color? _textSubtitle3; late final int textSubtitle3Int; @@ -261,8 +261,8 @@ class StackTheme { @ignore Color get textSubtitle4 => _textSubtitle4 ??= Color( - textSubtitle4Int, - ); + textSubtitle4Int, + ); @ignore Color? _textSubtitle4; late final int textSubtitle4Int; @@ -271,8 +271,8 @@ class StackTheme { @ignore Color get textSubtitle5 => _textSubtitle5 ??= Color( - textSubtitle5Int, - ); + textSubtitle5Int, + ); @ignore Color? _textSubtitle5; late final int textSubtitle5Int; @@ -281,8 +281,8 @@ class StackTheme { @ignore Color get textSubtitle6 => _textSubtitle6 ??= Color( - textSubtitle6Int, - ); + textSubtitle6Int, + ); @ignore Color? _textSubtitle6; late final int textSubtitle6Int; @@ -291,8 +291,8 @@ class StackTheme { @ignore Color get textWhite => _textWhite ??= Color( - textWhiteInt, - ); + textWhiteInt, + ); @ignore Color? _textWhite; late final int textWhiteInt; @@ -301,8 +301,8 @@ class StackTheme { @ignore Color get textFavoriteCard => _textFavoriteCard ??= Color( - textFavoriteCardInt, - ); + textFavoriteCardInt, + ); @ignore Color? _textFavoriteCard; late final int textFavoriteCardInt; @@ -311,8 +311,8 @@ class StackTheme { @ignore Color get textError => _textError ??= Color( - textErrorInt, - ); + textErrorInt, + ); @ignore Color? _textError; late final int textErrorInt; @@ -321,8 +321,8 @@ class StackTheme { @ignore Color get textRestore => _textRestore ??= Color( - textRestoreInt, - ); + textRestoreInt, + ); @ignore Color? _textRestore; late final int textRestoreInt; @@ -331,8 +331,8 @@ class StackTheme { @ignore Color get buttonBackPrimary => _buttonBackPrimary ??= Color( - buttonBackPrimaryInt, - ); + buttonBackPrimaryInt, + ); @ignore Color? _buttonBackPrimary; late final int buttonBackPrimaryInt; @@ -341,8 +341,8 @@ class StackTheme { @ignore Color get buttonBackSecondary => _buttonBackSecondary ??= Color( - buttonBackSecondaryInt, - ); + buttonBackSecondaryInt, + ); @ignore Color? _buttonBackSecondary; late final int buttonBackSecondaryInt; @@ -351,8 +351,8 @@ class StackTheme { @ignore Color get buttonBackPrimaryDisabled => _buttonBackPrimaryDisabled ??= Color( - buttonBackPrimaryDisabledInt, - ); + buttonBackPrimaryDisabledInt, + ); @ignore Color? _buttonBackPrimaryDisabled; late final int buttonBackPrimaryDisabledInt; @@ -372,8 +372,8 @@ class StackTheme { @ignore Color get buttonBackBorder => _buttonBackBorder ??= Color( - buttonBackBorderInt, - ); + buttonBackBorderInt, + ); @ignore Color? _buttonBackBorder; late final int buttonBackBorderInt; @@ -382,8 +382,8 @@ class StackTheme { @ignore Color get buttonBackBorderDisabled => _buttonBackBorderDisabled ??= Color( - buttonBackBorderDisabledInt, - ); + buttonBackBorderDisabledInt, + ); @ignore Color? _buttonBackBorderDisabled; late final int buttonBackBorderDisabledInt; @@ -392,8 +392,8 @@ class StackTheme { @ignore Color get buttonBackBorderSecondary => _buttonBackBorderSecondary ??= Color( - buttonBackBorderSecondaryInt, - ); + buttonBackBorderSecondaryInt, + ); @ignore Color? _buttonBackBorderSecondary; late final int buttonBackBorderSecondaryInt; @@ -413,8 +413,8 @@ class StackTheme { @ignore Color get numberBackDefault => _numberBackDefault ??= Color( - numberBackDefaultInt, - ); + numberBackDefaultInt, + ); @ignore Color? _numberBackDefault; late final int numberBackDefaultInt; @@ -423,8 +423,8 @@ class StackTheme { @ignore Color get numpadBackDefault => _numpadBackDefault ??= Color( - numpadBackDefaultInt, - ); + numpadBackDefaultInt, + ); @ignore Color? _numpadBackDefault; late final int numpadBackDefaultInt; @@ -433,8 +433,8 @@ class StackTheme { @ignore Color get bottomNavBack => _bottomNavBack ??= Color( - bottomNavBackInt, - ); + bottomNavBackInt, + ); @ignore Color? _bottomNavBack; late final int bottomNavBackInt; @@ -443,8 +443,8 @@ class StackTheme { @ignore Color get buttonTextPrimary => _buttonTextPrimary ??= Color( - buttonTextPrimaryInt, - ); + buttonTextPrimaryInt, + ); @ignore Color? _buttonTextPrimary; late final int buttonTextPrimaryInt; @@ -453,8 +453,8 @@ class StackTheme { @ignore Color get buttonTextSecondary => _buttonTextSecondary ??= Color( - buttonTextSecondaryInt, - ); + buttonTextSecondaryInt, + ); @ignore Color? _buttonTextSecondary; late final int buttonTextSecondaryInt; @@ -463,8 +463,8 @@ class StackTheme { @ignore Color get buttonTextPrimaryDisabled => _buttonTextPrimaryDisabled ??= Color( - buttonTextPrimaryDisabledInt, - ); + buttonTextPrimaryDisabledInt, + ); @ignore Color? _buttonTextPrimaryDisabled; late final int buttonTextPrimaryDisabledInt; @@ -1517,117 +1517,117 @@ class StackTheme { ..version = version ..assetsV1 = version == 1 ? ThemeAssets.fromJson( - json: Map.from(json["assets"] as Map), - themeId: json["id"] as String, - ) + json: Map.from(json["assets"] as Map), + themeId: json["id"] as String, + ) : null ..assetsV2 = version == 2 ? ThemeAssetsV2.fromJson( - json: Map.from(json["assets"] as Map), - themeId: json["id"] as String, - ) + json: Map.from(json["assets"] as Map), + themeId: json["id"] as String, + ) : null ..assetsV3 = version >= 3 ? ThemeAssetsV3.fromJson( - json: Map.from(json["assets"] as Map), - themeId: json["id"] as String, - ) + json: Map.from(json["assets"] as Map), + themeId: json["id"] as String, + ) : null ..themeId = json["id"] as String ..name = json["name"] as String ..brightnessString = json["brightness"] as String ..backgroundInt = parseColor(json["colors"]["background"] as String) ..backgroundAppBarInt = - parseColor(json["colors"]["background_app_bar"] as String) + parseColor(json["colors"]["background_app_bar"] as String) ..gradientBackgroundString = json["colors"]["gradients"] != null ? jsonEncode(json["colors"]["gradients"]) : null ..standardBoxShadowString = - jsonEncode(json["colors"]["box_shadows"]["standard"] as Map) + jsonEncode(json["colors"]["box_shadows"]["standard"] as Map) ..homeViewButtonBarBoxShadowString = - json["colors"]["box_shadows"]["home_view_button_bar"] == null - ? null - : jsonEncode( - json["colors"]["box_shadows"]["home_view_button_bar"] as Map) + json["colors"]["box_shadows"]["home_view_button_bar"] == null + ? null + : jsonEncode( + json["colors"]["box_shadows"]["home_view_button_bar"] as Map) ..coinColorsJsonString = jsonEncode(json["colors"]['coin'] as Map) ..overlayInt = parseColor(json["colors"]["overlay"] as String) ..accentColorBlueInt = - parseColor(json["colors"]["accent_color_blue"] as String) + parseColor(json["colors"]["accent_color_blue"] as String) ..accentColorGreenInt = - parseColor(json["colors"]["accent_color_green"] as String) + parseColor(json["colors"]["accent_color_green"] as String) ..accentColorYellowInt = - parseColor(json["colors"]["accent_color_yellow"] as String) + parseColor(json["colors"]["accent_color_yellow"] as String) ..accentColorRedInt = - parseColor(json["colors"]["accent_color_red"] as String) + parseColor(json["colors"]["accent_color_red"] as String) ..accentColorOrangeInt = - parseColor(json["colors"]["accent_color_orange"] as String) + parseColor(json["colors"]["accent_color_orange"] as String) ..accentColorDarkInt = - parseColor(json["colors"]["accent_color_dark"] as String) + parseColor(json["colors"]["accent_color_dark"] as String) ..shadowInt = parseColor(json["colors"]["shadow"] as String) ..textDarkInt = parseColor(json["colors"]["text_dark_one"] as String) ..textDark2Int = parseColor(json["colors"]["text_dark_two"] as String) ..textDark3Int = parseColor(json["colors"]["text_dark_three"] as String) ..textWhiteInt = parseColor(json["colors"]["text_white"] as String) ..textFavoriteCardInt = - parseColor(json["colors"]["text_favorite"] as String) + parseColor(json["colors"]["text_favorite"] as String) ..textErrorInt = parseColor(json["colors"]["text_error"] as String) ..textRestoreInt = parseColor(json["colors"]["text_restore"] as String) ..buttonBackPrimaryInt = - parseColor(json["colors"]["button_back_primary"] as String) + parseColor(json["colors"]["button_back_primary"] as String) ..buttonBackSecondaryInt = - parseColor(json["colors"]["button_back_secondary"] as String) + parseColor(json["colors"]["button_back_secondary"] as String) ..buttonBackPrimaryDisabledInt = - parseColor(json["colors"]["button_back_primary_disabled"] as String) + parseColor(json["colors"]["button_back_primary_disabled"] as String) ..buttonBackSecondaryDisabledInt = - parseColor(json["colors"]["button_back_secondary_disabled"] as String) + parseColor(json["colors"]["button_back_secondary_disabled"] as String) ..buttonBackBorderInt = - parseColor(json["colors"]["button_back_border"] as String) + parseColor(json["colors"]["button_back_border"] as String) ..buttonBackBorderDisabledInt = - parseColor(json["colors"]["button_back_border_disabled"] as String) + parseColor(json["colors"]["button_back_border_disabled"] as String) ..buttonBackBorderSecondaryInt = - parseColor(json["colors"]["button_back_border_secondary"] as String) + parseColor(json["colors"]["button_back_border_secondary"] as String) ..buttonBackBorderSecondaryDisabledInt = parseColor( json["colors"]["button_back_border_secondary_disabled"] as String) ..numberBackDefaultInt = - parseColor(json["colors"]["number_back_default"] as String) + parseColor(json["colors"]["number_back_default"] as String) ..numpadBackDefaultInt = - parseColor(json["colors"]["numpad_back_default"] as String) + parseColor(json["colors"]["numpad_back_default"] as String) ..bottomNavBackInt = - parseColor(json["colors"]["bottom_nav_back"] as String) + parseColor(json["colors"]["bottom_nav_back"] as String) ..textSubtitle1Int = - parseColor(json["colors"]["text_subtitle_one"] as String) + parseColor(json["colors"]["text_subtitle_one"] as String) ..textSubtitle2Int = - parseColor(json["colors"]["text_subtitle_two"] as String) + parseColor(json["colors"]["text_subtitle_two"] as String) ..textSubtitle3Int = - parseColor(json["colors"]["text_subtitle_three"] as String) + parseColor(json["colors"]["text_subtitle_three"] as String) ..textSubtitle4Int = - parseColor(json["colors"]["text_subtitle_four"] as String) + parseColor(json["colors"]["text_subtitle_four"] as String) ..textSubtitle5Int = - parseColor(json["colors"]["text_subtitle_five"] as String) + parseColor(json["colors"]["text_subtitle_five"] as String) ..textSubtitle6Int = - parseColor(json["colors"]["text_subtitle_six"] as String) + parseColor(json["colors"]["text_subtitle_six"] as String) ..buttonTextPrimaryInt = - parseColor(json["colors"]["button_text_primary"] as String) + parseColor(json["colors"]["button_text_primary"] as String) ..buttonTextSecondaryInt = - parseColor(json["colors"]["button_text_secondary"] as String) + parseColor(json["colors"]["button_text_secondary"] as String) ..buttonTextPrimaryDisabledInt = - parseColor(json["colors"]["button_text_primary_disabled"] as String) + parseColor(json["colors"]["button_text_primary_disabled"] as String) ..buttonTextSecondaryDisabledInt = - parseColor(json["colors"]["button_text_secondary_disabled"] as String) + parseColor(json["colors"]["button_text_secondary_disabled"] as String) ..buttonTextBorderInt = - parseColor(json["colors"]["button_text_border"] as String) + parseColor(json["colors"]["button_text_border"] as String) ..buttonTextDisabledInt = - parseColor(json["colors"]["button_text_disabled"] as String) + parseColor(json["colors"]["button_text_disabled"] as String) ..buttonTextBorderlessInt = - parseColor(json["colors"]["button_text_borderless"] as String) + parseColor(json["colors"]["button_text_borderless"] as String) ..buttonTextBorderlessDisabledInt = parseColor( json["colors"]["button_text_borderless_disabled"] as String) ..numberTextDefaultInt = - parseColor(json["colors"]["number_text_default"] as String) + parseColor(json["colors"]["number_text_default"] as String) ..numpadTextDefaultInt = - parseColor(json["colors"]["numpad_text_default"] as String) + parseColor(json["colors"]["numpad_text_default"] as String) ..bottomNavTextInt = - parseColor(json["colors"]["bottom_nav_text"] as String) + parseColor(json["colors"]["bottom_nav_text"] as String) ..customTextButtonEnabledTextInt = parseColor( json["colors"]["custom_text_button_enabled_text"] as String) ..customTextButtonDisabledTextInt = parseColor( @@ -1635,87 +1635,87 @@ class StackTheme { ..switchBGOnInt = parseColor(json["colors"]["switch_bg_on"] as String) ..switchBGOffInt = parseColor(json["colors"]["switch_bg_off"] as String) ..switchBGDisabledInt = - parseColor(json["colors"]["switch_bg_disabled"] as String) + parseColor(json["colors"]["switch_bg_disabled"] as String) ..switchCircleOnInt = - parseColor(json["colors"]["switch_circle_on"] as String) + parseColor(json["colors"]["switch_circle_on"] as String) ..switchCircleOffInt = - parseColor(json["colors"]["switch_circle_off"] as String) + parseColor(json["colors"]["switch_circle_off"] as String) ..switchCircleDisabledInt = - parseColor(json["colors"]["switch_circle_disabled"] as String) + parseColor(json["colors"]["switch_circle_disabled"] as String) ..stepIndicatorBGCheckInt = - parseColor(json["colors"]["step_indicator_bg_check"] as String) + parseColor(json["colors"]["step_indicator_bg_check"] as String) ..stepIndicatorBGNumberInt = - parseColor(json["colors"]["step_indicator_bg_number"] as String) + parseColor(json["colors"]["step_indicator_bg_number"] as String) ..stepIndicatorBGInactiveInt = - parseColor(json["colors"]["step_indicator_bg_inactive"] as String) + parseColor(json["colors"]["step_indicator_bg_inactive"] as String) ..stepIndicatorBGLinesInt = - parseColor(json["colors"]["step_indicator_bg_lines"] as String) + parseColor(json["colors"]["step_indicator_bg_lines"] as String) ..stepIndicatorBGLinesInactiveInt = parseColor( json["colors"]["step_indicator_bg_lines_inactive"] as String) ..stepIndicatorIconTextInt = - parseColor(json["colors"]["step_indicator_icon_text"] as String) + parseColor(json["colors"]["step_indicator_icon_text"] as String) ..stepIndicatorIconNumberInt = - parseColor(json["colors"]["step_indicator_icon_number"] as String) + parseColor(json["colors"]["step_indicator_icon_number"] as String) ..stepIndicatorIconInactiveInt = - parseColor(json["colors"]["step_indicator_icon_inactive"] as String) + parseColor(json["colors"]["step_indicator_icon_inactive"] as String) ..checkboxBGCheckedInt = - parseColor(json["colors"]["checkbox_bg_checked"] as String) + parseColor(json["colors"]["checkbox_bg_checked"] as String) ..checkboxBorderEmptyInt = - parseColor(json["colors"]["checkbox_border_empty"] as String) + parseColor(json["colors"]["checkbox_border_empty"] as String) ..checkboxBGDisabledInt = - parseColor(json["colors"]["checkbox_bg_disabled"] as String) + parseColor(json["colors"]["checkbox_bg_disabled"] as String) ..checkboxIconCheckedInt = - parseColor(json["colors"]["checkbox_icon_checked"] as String) + parseColor(json["colors"]["checkbox_icon_checked"] as String) ..checkboxIconDisabledInt = - parseColor(json["colors"]["checkbox_icon_disabled"] as String) + parseColor(json["colors"]["checkbox_icon_disabled"] as String) ..checkboxTextLabelInt = - parseColor(json["colors"]["checkbox_text_label"] as String) + parseColor(json["colors"]["checkbox_text_label"] as String) ..snackBarBackSuccessInt = - parseColor(json["colors"]["snack_bar_back_success"] as String) + parseColor(json["colors"]["snack_bar_back_success"] as String) ..snackBarBackErrorInt = - parseColor(json["colors"]["snack_bar_back_error"] as String) + parseColor(json["colors"]["snack_bar_back_error"] as String) ..snackBarBackInfoInt = - parseColor(json["colors"]["snack_bar_back_info"] as String) + parseColor(json["colors"]["snack_bar_back_info"] as String) ..snackBarTextSuccessInt = - parseColor(json["colors"]["snack_bar_text_success"] as String) + parseColor(json["colors"]["snack_bar_text_success"] as String) ..snackBarTextErrorInt = - parseColor(json["colors"]["snack_bar_text_error"] as String) + parseColor(json["colors"]["snack_bar_text_error"] as String) ..snackBarTextInfoInt = - parseColor(json["colors"]["snack_bar_text_info"] as String) + parseColor(json["colors"]["snack_bar_text_info"] as String) ..bottomNavIconBackInt = - parseColor(json["colors"]["bottom_nav_icon_back"] as String) + parseColor(json["colors"]["bottom_nav_icon_back"] as String) ..bottomNavIconIconInt = - parseColor(json["colors"]["bottom_nav_icon_icon"] as String) + parseColor(json["colors"]["bottom_nav_icon_icon"] as String) ..bottomNavIconIconHighlightedInt = parseColor( json["colors"]["bottom_nav_icon_icon_highlighted"] as String) ..topNavIconPrimaryInt = - parseColor(json["colors"]["top_nav_icon_primary"] as String) + parseColor(json["colors"]["top_nav_icon_primary"] as String) ..topNavIconGreenInt = - parseColor(json["colors"]["top_nav_icon_green"] as String) + parseColor(json["colors"]["top_nav_icon_green"] as String) ..topNavIconYellowInt = - parseColor(json["colors"]["top_nav_icon_yellow"] as String) + parseColor(json["colors"]["top_nav_icon_yellow"] as String) ..topNavIconRedInt = - parseColor(json["colors"]["top_nav_icon_red"] as String) + parseColor(json["colors"]["top_nav_icon_red"] as String) ..settingsIconBackInt = - parseColor(json["colors"]["settings_icon_back"] as String) + parseColor(json["colors"]["settings_icon_back"] as String) ..settingsIconIconInt = - parseColor(json["colors"]["settings_icon_icon"] as String) + parseColor(json["colors"]["settings_icon_icon"] as String) ..settingsIconBack2Int = - parseColor(json["colors"]["settings_icon_back_two"] as String) + parseColor(json["colors"]["settings_icon_back_two"] as String) ..settingsIconElementInt = - parseColor(json["colors"]["settings_icon_element"] as String) + parseColor(json["colors"]["settings_icon_element"] as String) ..textFieldActiveBGInt = - parseColor(json["colors"]["text_field_active_bg"] as String) + parseColor(json["colors"]["text_field_active_bg"] as String) ..textFieldDefaultBGInt = - parseColor(json["colors"]["text_field_default_bg"] as String) + parseColor(json["colors"]["text_field_default_bg"] as String) ..textFieldErrorBGInt = - parseColor(json["colors"]["text_field_error_bg"] as String) + parseColor(json["colors"]["text_field_error_bg"] as String) ..textFieldSuccessBGInt = - parseColor(json["colors"]["text_field_success_bg"] as String) + parseColor(json["colors"]["text_field_success_bg"] as String) ..textFieldErrorBorderInt = - parseColor(json["colors"]["text_field_error_border"] as String) + parseColor(json["colors"]["text_field_error_border"] as String) ..textFieldSuccessBorderInt = - parseColor(json["colors"]["text_field_success_border"] as String) + parseColor(json["colors"]["text_field_success_border"] as String) ..textFieldActiveSearchIconLeftInt = parseColor( json["colors"]["text_field_active_search_icon_left"] as String) ..textFieldDefaultSearchIconLeftInt = parseColor( @@ -1725,19 +1725,19 @@ class StackTheme { ..textFieldSuccessSearchIconLeftInt = parseColor( json["colors"]["text_field_success_search_icon_left"] as String) ..textFieldActiveTextInt = - parseColor(json["colors"]["text_field_active_text"] as String) + parseColor(json["colors"]["text_field_active_text"] as String) ..textFieldDefaultTextInt = - parseColor(json["colors"]["text_field_default_text"] as String) + parseColor(json["colors"]["text_field_default_text"] as String) ..textFieldErrorTextInt = - parseColor(json["colors"]["text_field_error_text"] as String) + parseColor(json["colors"]["text_field_error_text"] as String) ..textFieldSuccessTextInt = - parseColor(json["colors"]["text_field_success_text"] as String) + parseColor(json["colors"]["text_field_success_text"] as String) ..textFieldActiveLabelInt = - parseColor(json["colors"]["text_field_active_label"] as String) + parseColor(json["colors"]["text_field_active_label"] as String) ..textFieldErrorLabelInt = - parseColor(json["colors"]["text_field_error_label"] as String) + parseColor(json["colors"]["text_field_error_label"] as String) ..textFieldSuccessLabelInt = - parseColor(json["colors"]["text_field_success_label"] as String) + parseColor(json["colors"]["text_field_success_label"] as String) ..textFieldActiveSearchIconRightInt = parseColor( json["colors"]["text_field_active_search_icon_right"] as String) ..textFieldDefaultSearchIconRightInt = parseColor( @@ -1753,61 +1753,61 @@ class StackTheme { ..settingsItem2ActiveSubInt = parseColor( json["colors"]["settings_item_level_two_active_sub"] as String) ..radioButtonIconBorderInt = - parseColor(json["colors"]["radio_button_icon_border"] as String) + parseColor(json["colors"]["radio_button_icon_border"] as String) ..radioButtonIconBorderDisabledInt = parseColor( json["colors"]["radio_button_icon_border_disabled"] as String) ..radioButtonBorderEnabledInt = - parseColor(json["colors"]["radio_button_border_enabled"] as String) + parseColor(json["colors"]["radio_button_border_enabled"] as String) ..radioButtonBorderDisabledInt = - parseColor(json["colors"]["radio_button_border_disabled"] as String) + parseColor(json["colors"]["radio_button_border_disabled"] as String) ..radioButtonIconCircleInt = - parseColor(json["colors"]["radio_button_icon_circle"] as String) + parseColor(json["colors"]["radio_button_icon_circle"] as String) ..radioButtonIconEnabledInt = - parseColor(json["colors"]["radio_button_icon_enabled"] as String) + parseColor(json["colors"]["radio_button_icon_enabled"] as String) ..radioButtonTextEnabledInt = - parseColor(json["colors"]["radio_button_text_enabled"] as String) + parseColor(json["colors"]["radio_button_text_enabled"] as String) ..radioButtonTextDisabledInt = - parseColor(json["colors"]["radio_button_text_disabled"] as String) + parseColor(json["colors"]["radio_button_text_disabled"] as String) ..radioButtonLabelEnabledInt = - parseColor(json["colors"]["radio_button_label_enabled"] as String) + parseColor(json["colors"]["radio_button_label_enabled"] as String) ..radioButtonLabelDisabledInt = - parseColor(json["colors"]["radio_button_label_disabled"] as String) + parseColor(json["colors"]["radio_button_label_disabled"] as String) ..infoItemBGInt = parseColor(json["colors"]["info_item_bg"] as String) ..infoItemLabelInt = - parseColor(json["colors"]["info_item_label"] as String) + parseColor(json["colors"]["info_item_label"] as String) ..infoItemTextInt = parseColor(json["colors"]["info_item_text"] as String) ..infoItemIconsInt = - parseColor(json["colors"]["info_item_icons"] as String) + parseColor(json["colors"]["info_item_icons"] as String) ..popupBGInt = parseColor(json["colors"]["popup_bg"] as String) ..currencyListItemBGInt = - parseColor(json["colors"]["currency_list_item_bg"] as String) + parseColor(json["colors"]["currency_list_item_bg"] as String) ..stackWalletBGInt = parseColor(json["colors"]["sw_bg"] as String) ..stackWalletMidInt = parseColor(json["colors"]["sw_mid"] as String) ..stackWalletBottomInt = parseColor(json["colors"]["sw_bottom"] as String) ..bottomNavShadowInt = - parseColor(json["colors"]["bottom_nav_shadow"] as String) + parseColor(json["colors"]["bottom_nav_shadow"] as String) ..splashInt = parseColor(json["colors"]["splash"] as String) ..highlightInt = parseColor(json["colors"]["highlight"] as String) ..warningForegroundInt = - parseColor(json["colors"]["warning_foreground"] as String) + parseColor(json["colors"]["warning_foreground"] as String) ..warningBackgroundInt = - parseColor(json["colors"]["warning_background"] as String) + parseColor(json["colors"]["warning_background"] as String) ..loadingOverlayTextColorInt = - parseColor(json["colors"]["loading_overlay_text_color"] as String) + parseColor(json["colors"]["loading_overlay_text_color"] as String) ..myStackContactIconBGInt = - parseColor(json["colors"]["my_stack_contact_icon_bg"] as String) + parseColor(json["colors"]["my_stack_contact_icon_bg"] as String) ..textConfirmTotalAmountInt = - parseColor(json["colors"]["text_confirm_total_amount"] as String) + parseColor(json["colors"]["text_confirm_total_amount"] as String) ..textSelectedWordTableItemInt = - parseColor(json["colors"]["text_selected_word_table_iterm"] as String) + parseColor(json["colors"]["text_selected_word_table_iterm"] as String) ..favoriteStarActiveInt = - parseColor(json["colors"]["favorite_star_active"] as String) + parseColor(json["colors"]["favorite_star_active"] as String) ..favoriteStarInactiveInt = - parseColor(json["colors"]["favorite_star_inactive"] as String) + parseColor(json["colors"]["favorite_star_inactive"] as String) ..rateTypeToggleColorOnInt = - parseColor(json["colors"]["rate_type_toggle_color_on"] as String) + parseColor(json["colors"]["rate_type_toggle_color_on"] as String) ..rateTypeToggleColorOffInt = - parseColor(json["colors"]["rate_type_toggle_color_off"] as String) + parseColor(json["colors"]["rate_type_toggle_color_off"] as String) ..rateTypeToggleDesktopColorOnInt = parseColor( json["colors"]["rate_type_toggle_desktop_color_on"] as String) ..rateTypeToggleDesktopColorOffInt = parseColor( @@ -1815,19 +1815,19 @@ class StackTheme { ..ethTagTextInt = parseColor(json["colors"]["eth_tag_text"] as String) ..ethTagBGInt = parseColor(json["colors"]["eth_tag_bg"] as String) ..ethWalletTagTextInt = - parseColor(json["colors"]["eth_wallet_tag_text"] as String) + parseColor(json["colors"]["eth_wallet_tag_text"] as String) ..ethWalletTagBGInt = - parseColor(json["colors"]["eth_wallet_tag_bg"] as String) + parseColor(json["colors"]["eth_wallet_tag_bg"] as String) ..tokenSummaryTextPrimaryInt = - parseColor(json["colors"]["token_summary_text_primary"] as String) + parseColor(json["colors"]["token_summary_text_primary"] as String) ..tokenSummaryTextSecondaryInt = - parseColor(json["colors"]["token_summary_text_secondary"] as String) + parseColor(json["colors"]["token_summary_text_secondary"] as String) ..tokenSummaryBGInt = - parseColor(json["colors"]["token_summary_bg"] as String) + parseColor(json["colors"]["token_summary_bg"] as String) ..tokenSummaryButtonBGInt = - parseColor(json["colors"]["token_summary_button_bg"] as String) + parseColor(json["colors"]["token_summary_button_bg"] as String) ..tokenSummaryIconInt = - parseColor(json["colors"]["token_summary_icon"] as String); + parseColor(json["colors"]["token_summary_icon"] as String); } /// Grab the int value of the hex color string. @@ -1840,7 +1840,7 @@ class StackTheme { } else { throw ArgumentError( '"$colorHex" and corresponding int ' - 'value "$colorValue" is not a valid color.', + 'value "$colorValue" is not a valid color.', ); } } catch (_) { @@ -2078,18 +2078,18 @@ class ThemeAssetsV2 implements IThemeAssets { @ignore Map get coinIcons => _coinIcons ??= parseCoinAssetsString( - coinIconsString, - placeHolder: coinPlaceholder, - ); + coinIconsString, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinIcons; late final String coinIconsString; @ignore Map get coinImages => _coinImages ??= parseCoinAssetsString( - coinImagesString, - placeHolder: coinPlaceholder, - ); + coinImagesString, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinImages; late final String coinImagesString; @@ -2164,9 +2164,9 @@ class ThemeAssetsV2 implements IThemeAssets { } static Map parseCoinAssetsString( - String jsonString, { - required String placeHolder, - }) { + String jsonString, { + required String placeHolder, + }) { final json = jsonDecode(jsonString) as Map; final map = Map.from(json); @@ -2348,18 +2348,18 @@ class ThemeAssetsV3 implements IThemeAssets { @ignore Map get coinIcons => _coinIcons ??= parseCoinAssetsString( - coinIconsString, - placeHolder: coinPlaceholder, - ); + coinIconsString, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinIcons; late final String coinIconsString; @ignore Map get coinImages => _coinImages ??= parseCoinAssetsString( - coinImagesString, - placeHolder: coinPlaceholder, - ); + coinImagesString, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinImages; late final String coinImagesString; @@ -2379,9 +2379,9 @@ class ThemeAssetsV3 implements IThemeAssets { _coinCardImages ??= coinCardImagesString == null ? null : parseCoinAssetsString( - coinCardImagesString!, - placeHolder: coinPlaceholder, - ); + coinCardImagesString!, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinCardImages; late final String? coinCardImagesString; @@ -2391,9 +2391,9 @@ class ThemeAssetsV3 implements IThemeAssets { _coinCardFavoritesImages ??= coinCardFavoritesImagesString == null ? null : parseCoinAssetsString( - coinCardFavoritesImagesString!, - placeHolder: coinPlaceholder, - ); + coinCardFavoritesImagesString!, + placeHolder: coinPlaceholder, + ); @ignore Map? _coinCardFavoritesImages; @Name("otherStringParam1") @@ -2450,15 +2450,15 @@ class ThemeAssetsV3 implements IThemeAssets { ) ..coinCardImagesString = json["coins"]["cards"] is Map ? createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["cards"] as Map), - ) + "$themeId/assets", + Map.from(json["coins"]["cards"] as Map), + ) : null ..coinCardFavoritesImagesString = json["coins"]["favoriteCards"] is Map ? createCoinAssetsString( - "$themeId/assets", - Map.from(json["coins"]["favoriteCards"] as Map), - ) + "$themeId/assets", + Map.from(json["coins"]["favoriteCards"] as Map), + ) : null ..loadingGifRelative = json["loading_gif"] is String ? "$themeId/assets/${json["loading_gif"] as String}" @@ -2499,9 +2499,9 @@ class ThemeAssetsV3 implements IThemeAssets { } static Map parseCoinAssetsString( - String jsonString, { - required String placeHolder, - }) { + String jsonString, { + required String placeHolder, + }) { final json = jsonDecode(jsonString) as Map; final map = Map.from(json); @@ -2571,4 +2571,4 @@ abstract class IThemeAssets { String? get loadingGif; String? get background; -} +} \ No newline at end of file diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 3e5d3e3e4..56b008093 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -196,7 +196,10 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + case Coin.stellarTestnet: + throw UnimplementedError(); + //TODO: check network/node } if (showFlushBar && mounted) { @@ -736,6 +739,8 @@ class _NodeFormState extends ConsumerState { case Coin.nano: case Coin.banano: case Coin.eCash: + case Coin.stellar: + case Coin.stellarTestnet: return false; case Coin.ethereum: diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index fa69871cb..59ff1efad 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -172,7 +172,10 @@ class _NodeDetailsViewState extends ConsumerState { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + case Coin.stellarTestnet: + throw UnimplementedError(); + //TODO: check network/node } if (testPassed) { diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 00a52eba5..5aa6e382d 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/nano/nano_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; +import 'package:stackwallet/services/coins/stellar/stellar_wallet.dart'; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -218,6 +219,15 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker); + case Coin.stellar: + return StellarWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + tracker: tracker, + ); + case Coin.wownero: return WowneroWallet( walletId: walletId, @@ -275,6 +285,15 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker, ); + + case Coin.stellarTestnet: + return StellarWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + tracker: tracker, + ); } } diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart new file mode 100644 index 000000000..c4c92373f --- /dev/null +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -0,0 +1,809 @@ +import 'dart:async'; + +import 'package:http/http.dart' as http; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart' as SWBalance; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart' + as SWAddress; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart' + as SWTransaction; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; +import 'package:tuple/tuple.dart'; + +const int MINIMUM_CONFIRMATIONS = 1; + +class StellarWallet extends CoinServiceAPI with WalletCache, WalletDB { + late StellarSDK stellarSdk; + late Network stellarNetwork; + + StellarWallet({ + required String walletId, + required String walletName, + required Coin coin, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + + if (coin.name == "stellarTestnet") { + stellarSdk = StellarSDK.TESTNET; + stellarNetwork = Network.TESTNET; + } else { + stellarSdk = StellarSDK.PUBLIC; + stellarNetwork = Network.PUBLIC; + } + } + + late final TransactionNotificationTracker txTracker; + late SecureStorageInterface _secureStore; + + // final StellarSDK stellarSdk = StellarSDK.PUBLIC; + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + bool? _isFavorite; + + @override + set isFavorite(bool isFavorite) { + _isFavorite = isFavorite; + updateCachedIsFavorite(isFavorite); + } + + @override + bool get shouldAutoSync => _shouldAutoSync; + bool _shouldAutoSync = true; + + Timer? timer; + + final _prefs = Prefs.instance; + + @override + set shouldAutoSync(bool shouldAutoSync) { + if (_shouldAutoSync != shouldAutoSync) { + _shouldAutoSync = shouldAutoSync; + if (!shouldAutoSync) { + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } else { + startNetworkAlivePinging(); + refresh(); + } + } + } + + Timer? _networkAliveTimer; + + void startNetworkAlivePinging() { + // call once on start right away + _periodicPingCheck(); + + // then periodically check + _networkAliveTimer = Timer.periodic( + Constants.networkAliveTimerDuration, + (_) async { + _periodicPingCheck(); + }, + ); + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + void _periodicPingCheck() async { + bool hasNetwork = await testNetworkConnection(); + + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + status, + walletId, + coin, + ), + ); + + _isConnected = hasNetwork; + if (hasNetwork) { + unawaited(refresh()); + } + } + } + + @override + String get walletName => _walletName; + late String _walletName; + + @override + set walletName(String name) => _walletName = name; + + @override + SWBalance.Balance get balance => _balance ??= getCachedBalance(); + SWBalance.Balance? _balance; + + @override + Coin get coin => _coin; + late Coin _coin; + + Future _accountExists(String accountId) async { + bool exists = false; + + try { + AccountResponse receiverAccount = + await stellarSdk.accounts.account(accountId); + if (receiverAccount.accountId != "") { + exists = true; + } + } catch (e, s) { + Logging.instance.log( + "Error getting account ${e.toString()} - ${s.toString()}", + level: LogLevel.Error); + } + return exists; + } + + @override + Future confirmSend({required Map txData}) async { + final secretSeed = await _secureStore.read(key: '${_walletId}_secretSeed'); + KeyPair senderKeyPair = KeyPair.fromSecretSeed(secretSeed!); + AccountResponse sender = + await stellarSdk.accounts.account(senderKeyPair.accountId); + final amountToSend = txData['recipientAmt'] as Amount; + + //First check if account exists, can be skipped, but if the account does not exist, + // the transaction fee will be charged when the transaction fails. + bool validAccount = await _accountExists(txData['address'] as String); + Transaction transaction; + + if (!validAccount) { + //Fund the account, user must ensure account is correct + CreateAccountOperationBuilder createAccBuilder = + CreateAccountOperationBuilder( + txData['address'] as String, amountToSend.decimal.toString()); + transaction = TransactionBuilder(sender) + .addOperation(createAccBuilder.build()) + .build(); + } else { + transaction = TransactionBuilder(sender) + .addOperation(PaymentOperationBuilder(txData['address'] as String, + Asset.NATIVE, amountToSend.decimal.toString()) + .build()) + .build(); + } + transaction.sign(senderKeyPair, stellarNetwork); + try { + SubmitTransactionResponse response = + await stellarSdk.submitTransaction(transaction); + + if (!response.success) { + throw ("Unable to send transaction"); + } + return response.hash!; + } catch (e, s) { + Logging.instance.log("Error sending TX $e - $s", level: LogLevel.Error); + rethrow; + } + } + + Future get _currentReceivingAddress => db + .getAddresses(walletId) + .filter() + .typeEqualTo(SWAddress.AddressType.unknown) + .and() + .subTypeEqualTo(SWAddress.AddressSubType.unknown) + .sortByDerivationIndexDesc() + .findFirst(); + + @override + Future get currentReceivingAddress async => + (await _currentReceivingAddress)?.value ?? await getAddressSW(); + + Future getBaseFee() async { + // final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + final nodeURI = Uri.parse(getCurrentNode().host); + final httpClient = http.Client(); + FeeStatsResponse fsp = + await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + return int.parse(fsp.lastLedgerBaseFee); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + var baseFee = await getBaseFee(); + int fee = 100; + switch (feeRate) { + case 0: + fee = baseFee * 10; + case 1: + case 2: + fee = baseFee * 50; + case 3: + fee = baseFee * 100; + case 4: + fee = baseFee * 200; + default: + fee = baseFee * 50; + } + return Amount(rawValue: BigInt.from(fee), fractionDigits: coin.decimals); + } + + @override + Future exit() async { + _hasCalledExit = true; + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } + + NodeModel? _xlmNode; + + NodeModel getCurrentNode() { + if (_xlmNode != null) { + return _xlmNode!; + } else if (NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) != + null) { + return NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin)!; + } else { + return DefaultNodes.getNodeFor(coin); + } + } + + @override + Future get fees async { + // final nodeURI = Uri.parse("${getCurrentNode().host}:${getCurrentNode().port}"); + final nodeURI = Uri.parse(getCurrentNode().host); + + final httpClient = http.Client(); + FeeStatsResponse fsp = + await FeeStatsRequestBuilder(httpClient, nodeURI).execute(); + + return FeeObject( + numberOfBlocksFast: 0, + numberOfBlocksAverage: 0, + numberOfBlocksSlow: 0, + fast: int.parse(fsp.lastLedgerBaseFee) * 100, + medium: int.parse(fsp.lastLedgerBaseFee) * 50, + slow: int.parse(fsp.lastLedgerBaseFee) * 10); + } + + @override + Future fullRescan( + int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async { + await _prefs.init(); + await updateTransactions(); + await updateChainHeight(); + await updateBalance(); + } + + @override + Future generateNewAddress() { + // TODO: implement generateNewAddress + throw UnimplementedError(); + } + + @override + bool get hasCalledExit => _hasCalledExit; + bool _hasCalledExit = false; + + @override + Future initializeExisting() async { + await _prefs.init(); + } + + @override + Future initializeNew() async { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + + await _prefs.init(); + + String mnemonic = await Wallet.generate24WordsMnemonic(); + await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); + await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + + Wallet wallet = await Wallet.from(mnemonic); + KeyPair keyPair = await wallet.getKeyPair(index: 0); + String address = keyPair.accountId; + String secretSeed = + keyPair.secretSeed; //This will be required for sending a tx + + await _secureStore.write(key: '${_walletId}_secretSeed', value: secretSeed); + + final swAddress = SWAddress.Address( + walletId: walletId, + value: address, + publicKey: keyPair.publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown); + + await db.putAddress(swAddress); + + await Future.wait( + [updateCachedId(walletId), updateCachedIsFavorite(false)]); + } + + Future getAddressSW() async { + var mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + + Wallet wallet = await Wallet.from(mnemonic!); + KeyPair keyPair = await wallet.getKeyPair(index: 0); + + return Future.value(keyPair.accountId); + } + + @override + bool get isConnected => _isConnected; + bool _isConnected = false; + + @override + bool get isRefreshing => refreshMutex; + bool refreshMutex = false; + + @override + // TODO: implement maxFee + Future get maxFee => throw UnimplementedError(); + + @override + Future> get mnemonic => + mnemonicString.then((value) => value!.split(" ")); + + @override + Future get mnemonicPassphrase => + _secureStore.read(key: '${_walletId}_mnemonicPassphrase'); + + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future> prepareSend( + {required String address, + required Amount amount, + Map? args}) async { + try { + final feeRate = args?["feeRate"]; + var fee = 1000; + if (feeRate is FeeRateType) { + final theFees = await fees; + switch (feeRate) { + case FeeRateType.fast: + fee = theFees.fast; + case FeeRateType.slow: + fee = theFees.slow; + case FeeRateType.average: + default: + fee = theFees.medium; + } + } + Map txData = { + "fee": fee, + "address": address, + "recipientAmt": amount, + }; + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + return txData; + } catch (e, s) { + Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future recoverFromMnemonic( + {required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height}) async { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + + var wallet = await Wallet.from(mnemonic); + var keyPair = await wallet.getKeyPair(index: 0); + var address = keyPair.accountId; + var secretSeed = keyPair.secretSeed; + + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + await _secureStore.write(key: '${_walletId}_secretSeed', value: secretSeed); + + final swAddress = SWAddress.Address( + walletId: walletId, + value: address, + publicKey: keyPair.publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown); + + await db.putAddress(swAddress); + + await Future.wait( + [updateCachedId(walletId), updateCachedIsFavorite(false)]); + } + + Future updateChainHeight() async { + final height = await stellarSdk.ledgers + .order(RequestBuilderOrder.DESC) + .limit(1) + .execute() + .then((value) => value.records!.first.sequence) + .onError((error, stackTrace) => throw ("Error getting chain height")); + await updateCachedChainHeight(height); + } + + Future updateTransactions() async { + try { + List> + transactionList = []; + + Page payments = await stellarSdk.payments + .forAccount(await getAddressSW()) + .order(RequestBuilderOrder.DESC) + .execute() + .onError( + (error, stackTrace) => throw ("Could not fetch transactions")); + + for (OperationResponse response in payments.records!) { + // PaymentOperationResponse por; + if (response is PaymentOperationResponse) { + PaymentOperationResponse por = response; + + Logging.instance.log( + "ALL TRANSACTIONS IS ${por.transactionSuccessful}", + level: LogLevel.Info); + + Logging.instance.log("THIS TX HASH IS ${por.transactionHash}", + level: LogLevel.Info); + + SWTransaction.TransactionType type; + if (por.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( + rawValue: BigInt.parse(float + .parse(por.amount!) + .toStringAsFixed(coin.decimals) + .replaceAll(".", "")), + fractionDigits: coin.decimals, + ); + int fee = 0; + int height = 0; + //Query the transaction linked to the payment, + // por.transaction returns a null sometimes + TransactionResponse tx = + await stellarSdk.transactions.transaction(por.transactionHash!); + + if (tx.hash.isNotEmpty) { + fee = tx.feeCharged!; + height = tx.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: por.transactionHash!, + timestamp: + DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = + type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: por.sourceAccount!, + publicKey: + KeyPair.fromAccountId(por.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown); + Tuple2 tuple = + Tuple2(theTransaction, address); + transactionList.add(tuple); + } else if (response is CreateAccountOperationResponse) { + CreateAccountOperationResponse caor = response; + SWTransaction.TransactionType type; + if (caor.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( + rawValue: BigInt.parse(float + .parse(caor.startingBalance!) + .toStringAsFixed(coin.decimals) + .replaceAll(".", "")), + fractionDigits: coin.decimals, + ); + int fee = 0; + int height = 0; + TransactionResponse tx = + await stellarSdk.transactions.transaction(caor.transactionHash!); + if (tx.hash.isNotEmpty) { + fee = tx.feeCharged!; + height = tx.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: caor.transactionHash!, + timestamp: + DateTime.parse(caor.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = + type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: caor.sourceAccount!, + publicKey: + KeyPair.fromAccountId(caor.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown); + Tuple2 tuple = + Tuple2(theTransaction, address); + transactionList.add(tuple); + } + } + await db.addNewTransactionData(transactionList, walletId); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from updateTransactions(): $e\n$s", + level: LogLevel.Error); + } + } + + Future updateBalance() async { + try { + AccountResponse accountResponse = + await stellarSdk.accounts.account(await getAddressSW()); + for (Balance balance in accountResponse.balances) { + switch (balance.assetType) { + case Asset.TYPE_NATIVE: + _balance = SWBalance.Balance( + total: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000), + fractionDigits: coin.decimals, + ), + spendable: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000), + fractionDigits: coin.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ), + ); + Logging.instance.log(_balance, level: LogLevel.Info); + await updateCachedBalance(_balance!); + } + } + } catch (e, s) { + Logging.instance.log( + "ERROR GETTING BALANCE $e\n$s", + level: LogLevel.Info, + ); + } + } + + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log( + "$walletId $walletName refreshMutex denied", + level: LogLevel.Info, + ); + return; + } else { + refreshMutex = true; + } + + try { + await _prefs.init(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + await updateChainHeight(); + await updateTransactions(); + await updateBalance(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { + Logging.instance.log( + "Periodic refresh check for $walletId $walletName in object instance: $hashCode", + level: LogLevel.Info); + + await refresh(); + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId, + ), + ); + }); + } + } catch (e, s) { + Logging.instance.log( + "Failed to refresh stellar wallet $walletId: '$walletName': $e\n$s", + level: LogLevel.Warning, + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + } + + refreshMutex = false; + } + + @override + int get storedChainHeight => getCachedChainHeight(); + + @override + Future testNetworkConnection() { + // TODO: implement testNetworkConnection + throw UnimplementedError(); + } + + @override + Future> get transactions => + db.getTransactions(walletId).findAll(); + + @override + Future updateNode(bool shouldRefresh) async { + _xlmNode = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + + if (shouldRefresh) { + unawaited(refresh()); + } + } + + @override + Future updateSentCachedTxData(Map txData) async { + final transaction = SWTransaction.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: SWTransaction.TransactionType.outgoing, + subType: SWTransaction.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, + inputs: [], + outputs: [], + numberOfMessages: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); + } + + @override + // TODO: implement utxos + Future> get utxos => throw UnimplementedError(); + + @override + bool validateAddress(String address) { + return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); + } + + @override + String get walletId => _walletId; + late String _walletId; +} diff --git a/lib/services/price.dart b/lib/services/price.dart index 24d929062..66842c9d8 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -100,7 +100,7 @@ class PriceAPI { Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency" "=${baseCurrency.toLowerCase()}" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"); final coinGeckoResponse = await client.get( diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart index bcfa45ac9..c78d35c11 100644 --- a/lib/themes/color_theme.dart +++ b/lib/themes/color_theme.dart @@ -28,6 +28,7 @@ class CoinThemeColorDefault { Color get namecoin => const Color(0xFF91B1E1); Color get wownero => const Color(0xFFED80C1); Color get particl => const Color(0xFF8175BD); + Color get stellar => const Color(0xFF6600FF); Color get nano => const Color(0xFF209CE9); Color get banano => const Color(0xFFFBDD11); @@ -62,6 +63,9 @@ class CoinThemeColorDefault { return wownero; case Coin.particl: return particl; + case Coin.stellar: + case Coin.stellarTestnet: + return stellar; case Coin.nano: return nano; case Coin.banano: diff --git a/lib/themes/stack_colors.dart b/lib/themes/stack_colors.dart index b9e58a5ca..5dbd55fe1 100644 --- a/lib/themes/stack_colors.dart +++ b/lib/themes/stack_colors.dart @@ -1707,6 +1707,9 @@ class StackColors extends ThemeExtension { return _coin.wownero; case Coin.particl: return _coin.particl; + case Coin.stellar: + case Coin.stellarTestnet: + return _coin.stellar; case Coin.nano: return _coin.nano; case Coin.banano: diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 0093e1d00..8766d706a 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -105,6 +105,8 @@ class AddressUtils { return Address.validateAddress(address, namecoin, namecoin.bech32!); case Coin.particl: return Address.validateAddress(address, particl); + case Coin.stellar: + return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); case Coin.nano: return NanoAccounts.isValid(NanoAccountType.NANO, address); case Coin.banano: @@ -139,6 +141,8 @@ class AddressUtils { return Address.validateAddress(address, firoTestNetwork); case Coin.dogecoinTestNet: return Address.validateAddress(address, dogecointestnet); + case Coin.stellarTestnet: + return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); } } diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index 91fef8bc7..ccdd4e095 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -50,6 +50,8 @@ enum AmountUnit { case Coin.dogecoin: case Coin.eCash: case Coin.epicCash: + case Coin.stellar: // TODO: check if this is correct + case Coin.stellarTestnet: return AmountUnit.values.sublist(0, 4); case Coin.monero: diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 7ff0cf349..831332320 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -54,10 +54,14 @@ Uri getDefaultBlockExplorerUrlFor({ return Uri.parse("https://chainz.cryptoid.info/nmc/tx.dws?$txid.htm"); case Coin.particl: return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm"); + case Coin.stellar: + return Uri.parse("https://stellarchain.io/tx/$txid"); case Coin.nano: return Uri.parse("https://www.nanolooker.com/block/$txid"); case Coin.banano: return Uri.parse("https://www.bananolooker.com/block/$txid"); + case Coin.stellarTestnet: + return Uri.parse("https://testnet.stellarchain.io/transactions/$txid"); } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index ad9d68997..ae357c5b3 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -43,6 +43,7 @@ abstract class Constants { BigInt.parse("1000000000000000000000000000000"); // 1*10^30 static final BigInt _satsPerCoinBanano = BigInt.parse("100000000000000000000000000000"); // 1*10^29 + static final BigInt _satsPerCoinStellar = BigInt.from(10000000); // https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/assets#amount-precision static final BigInt _satsPerCoin = BigInt.from(100000000); static const int _decimalPlaces = 8; static const int _decimalPlacesNano = 30; @@ -51,6 +52,7 @@ abstract class Constants { static const int _decimalPlacesMonero = 12; static const int _decimalPlacesEthereum = 18; static const int _decimalPlacesECash = 2; + static const int _decimalPlacesStellar = 7; static const int notificationsMax = 0xFFFFFFFF; static const Duration networkAliveTimerDuration = Duration(seconds: 10); @@ -96,6 +98,10 @@ abstract class Constants { case Coin.eCash: return _satsPerCoinECash; + + case Coin.stellar: + case Coin.stellarTestnet: + return _satsPerCoinStellar; } } @@ -133,6 +139,10 @@ abstract class Constants { case Coin.eCash: return _decimalPlacesECash; + + case Coin.stellar: + case Coin.stellarTestnet: + return _decimalPlacesStellar; } } @@ -155,6 +165,8 @@ abstract class Constants { case Coin.namecoin: case Coin.particl: case Coin.nano: + case Coin.stellar: + case Coin.stellarTestnet: values.addAll([24, 12]); break; case Coin.banano: @@ -214,6 +226,10 @@ abstract class Constants { case Coin.nano: // TODO: Verify this case Coin.banano: // TODO: Verify this return 1; + + case Coin.stellar: + case Coin.stellarTestnet: + return 5; } } @@ -241,6 +257,8 @@ abstract class Constants { case Coin.nano: case Coin.banano: + case Coin.stellar: + case Coin.stellarTestnet: return 24; case Coin.monero: diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index c8ff94120..9a6a0d914 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -34,6 +34,7 @@ abstract class DefaultNodes { bitcoincashTestnet, dogecoinTestnet, firoTestnet, + stellarTestnet, ]; static NodeModel get bitcoin => NodeModel( @@ -181,6 +182,18 @@ abstract class DefaultNodes { isFailover: true, isDown: false); + static NodeModel get stellar => NodeModel( + host: "https://horizon.stellar.org", + port: 443, + name: defaultName, + id: _nodeId(Coin.stellar), + useSSL: false, + enabled: true, + coinName: Coin.stellar.name, + isFailover: true, + isDown: false + ); + static NodeModel get nano => NodeModel( host: "https://rainstorm.city/api", port: 443, @@ -263,6 +276,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get stellarTestnet => NodeModel( + host: "https://horizon-testnet.stellar.org/", + port: 50022, + name: defaultName, + id: _nodeId(Coin.stellarTestnet), + useSSL: true, + enabled: true, + coinName: Coin.stellarTestnet.name, + isFailover: true, + isDown: false, + ); + static NodeModel getNodeFor(Coin coin) { switch (coin) { case Coin.bitcoin: @@ -301,6 +326,9 @@ abstract class DefaultNodes { case Coin.particl: return particl; + case Coin.stellar: + return stellar; + case Coin.nano: return nano; @@ -321,6 +349,9 @@ abstract class DefaultNodes { case Coin.dogecoinTestNet: return dogecoinTestnet; + + case Coin.stellarTestnet: + return stellarTestnet; } } } diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index c656b2c62..677d474e7 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' import 'package:stackwallet/services/coins/nano/nano_wallet.dart' as nano; import 'package:stackwallet/services/coins/particl/particl_wallet.dart' as particl; +import 'package:stackwallet/services/coins/stellar/stellar_wallet.dart' as xlm; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; import 'package:stackwallet/utilities/constants.dart'; @@ -44,6 +45,7 @@ enum Coin { namecoin, nano, particl, + stellar, wownero, /// @@ -56,6 +58,7 @@ enum Coin { dogecoinTestNet, firoTestNet, litecoinTestNet, + stellarTestnet, } final int kTestNetCoinCount = 4; // Util.isDesktop ? 5 : 4; @@ -84,6 +87,8 @@ extension CoinExt on Coin { return "Monero"; case Coin.particl: return "Particl"; + case Coin.stellar: + return "Stellar"; case Coin.wownero: return "Wownero"; case Coin.namecoin: @@ -102,6 +107,8 @@ extension CoinExt on Coin { return "tFiro"; case Coin.dogecoinTestNet: return "tDogecoin"; + case Coin.stellarTestnet: + return "tStellar"; } } @@ -127,6 +134,8 @@ extension CoinExt on Coin { return "XMR"; case Coin.particl: return "PART"; + case Coin.stellar: + return "XLM"; case Coin.wownero: return "WOW"; case Coin.namecoin: @@ -145,6 +154,8 @@ extension CoinExt on Coin { return "tFIRO"; case Coin.dogecoinTestNet: return "tDOGE"; + case Coin.stellarTestnet: + return "tXLM"; } } @@ -171,6 +182,8 @@ extension CoinExt on Coin { return "monero"; case Coin.particl: return "particl"; + case Coin.stellar: + return "stellar"; case Coin.wownero: return "wownero"; case Coin.namecoin: @@ -189,6 +202,8 @@ extension CoinExt on Coin { return "firo"; case Coin.dogecoinTestNet: return "dogecoin"; + case Coin.stellarTestnet: + return "stellar"; } } @@ -215,6 +230,8 @@ extension CoinExt on Coin { case Coin.wownero: case Coin.nano: case Coin.banano: + case Coin.stellar: + case Coin.stellarTestnet: return false; } } @@ -242,6 +259,8 @@ extension CoinExt on Coin { case Coin.firoTestNet: case Coin.nano: case Coin.banano: + case Coin.stellar: + case Coin.stellarTestnet: return false; } } @@ -262,6 +281,7 @@ extension CoinExt on Coin { case Coin.nano: case Coin.banano: case Coin.eCash: + case Coin.stellar: return false; case Coin.dogecoinTestNet: @@ -269,6 +289,7 @@ extension CoinExt on Coin { case Coin.litecoinTestNet: case Coin.bitcoincashTestnet: case Coin.firoTestNet: + case Coin.stellarTestnet: return true; } } @@ -289,6 +310,7 @@ extension CoinExt on Coin { case Coin.nano: case Coin.banano: case Coin.eCash: + case Coin.stellar: return this; case Coin.dogecoinTestNet: @@ -305,6 +327,9 @@ extension CoinExt on Coin { case Coin.firoTestNet: return Coin.firo; + + case Coin.stellarTestnet: + return Coin.stellar; } } @@ -345,6 +370,10 @@ extension CoinExt on Coin { case Coin.particl: return particl.MINIMUM_CONFIRMATIONS; + case Coin.stellar: + case Coin.stellarTestnet: + return xlm.MINIMUM_CONFIRMATIONS; + case Coin.wownero: return wow.MINIMUM_CONFIRMATIONS; @@ -404,6 +433,10 @@ Coin coinFromPrettyName(String name) { case "particl": return Coin.particl; + case "Stellar": + case "stellar": + return Coin.stellar; + case "Namecoin": case "namecoin": return Coin.namecoin; @@ -448,6 +481,11 @@ Coin coinFromPrettyName(String name) { case "banano": return Coin.banano; + case "Stellar Testnet": + case "stellarTestnet": + case "tStellar": + return Coin.stellarTestnet; + default: throw ArgumentError.value( name, @@ -481,6 +519,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.namecoin; case "part": return Coin.particl; + case "xlm": + return Coin.stellar; case "tltc": return Coin.litecoinTestNet; case "tbtc": @@ -497,6 +537,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.nano; case "ban": return Coin.banano; + case "txlm": + return Coin.stellarTestnet; default: throw ArgumentError.value( ticker, "name", "No Coin enum value with that ticker"); diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart index f3e7f86df..07785a5ee 100644 --- a/lib/utilities/enums/derive_path_type_enum.dart +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -49,6 +49,8 @@ extension DerivePathTypeExt on DerivePathType { case Coin.wownero: case Coin.nano: case Coin.banano: + case Coin.stellar: + case Coin.stellarTestnet: throw UnsupportedError( "$coin does not use bitcoin style derivation paths"); } diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index 9214e3e0a..57482119c 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -194,7 +194,10 @@ class _NodeCardState extends ConsumerState { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + case Coin.stellarTestnet: + throw UnimplementedError(); + //TODO: check network/node } if (testPassed) { diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index 16e87581c..b55f818a2 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -177,7 +177,10 @@ class NodeOptionsSheet extends ConsumerWidget { case Coin.nano: case Coin.banano: - //TODO: check network/node + case Coin.stellar: + case Coin.stellarTestnet: + throw UnimplementedError(); + //TODO: check network/node } if (testPassed) { diff --git a/pubspec.lock b/pubspec.lock index e260cb994..cfdf7a710 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" asn1lib: dependency: transitive description: name: asn1lib - sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" async: dependency: "direct main" description: @@ -146,10 +146,10 @@ packages: dependency: transitive description: name: build - sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" build_config: dependency: transitive description: @@ -170,26 +170,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced" + sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.6" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "88a57f2ac99849362e73878334caa9f06ee25f31d2adced882b8337838c84e1e" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.9" + version: "7.2.10" built_collection: dependency: transitive description: @@ -202,10 +202,10 @@ packages: dependency: transitive description: name: built_value - sha256: "7dd62d9faf105c434f3d829bbe9c4be02ec67f5ed94832222116122df67c5452" + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" url: "https://pub.dev" source: hosted - version: "8.6.0" + version: "8.6.1" characters: dependency: transitive description: @@ -242,10 +242,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.0" collection: dependency: transitive description: @@ -298,10 +298,10 @@ packages: dependency: "direct main" description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cryptography: dependency: transitive description: @@ -314,10 +314,10 @@ packages: dependency: transitive description: name: csslib - sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" url: "https://pub.dev" source: hosted - version: "0.17.3" + version: "1.0.0" cw_core: dependency: "direct main" description: @@ -382,18 +382,18 @@ packages: dependency: transitive description: name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" dartx: dependency: transitive description: name: dartx - sha256: "45d7176701f16c5a5e00a4798791c1964bc231491b879369c818dd9a9c764871" + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" dbus: dependency: transitive description: @@ -406,10 +406,10 @@ packages: dependency: "direct main" description: name: decimal - sha256: eece91944f523657c75a3a008a90ec7f7eb3986191153a78570c7d0ac8ef3d01 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" dependency_validator: dependency: "direct dev" description: @@ -450,14 +450,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + dio: + dependency: transitive + description: + name: dio + sha256: "3866d67f93523161b643187af65f5ac08bc991a5bcdaf41a2d587fe4ccb49993" + url: "https://pub.dev" + source: hosted + version: "5.3.0" dropdown_button2: dependency: "direct main" description: name: dropdown_button2 - sha256: "374f2390161bf782b4896f0b1b24cbb2b5daaa1cfb11047c3307461dcdf44e07" + sha256: "83c54a5022f898d63e3abe21240b64b937e676103207287e6705d3f9bb04d654" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.6" eip1559: dependency: transitive description: @@ -542,10 +550,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" + sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432 url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "5.3.2" fixnum: dependency: transitive description: @@ -606,10 +614,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_local_notifications: dependency: "direct main" description: @@ -670,10 +678,10 @@ packages: dependency: "direct main" description: name: flutter_rounded_date_picker - sha256: e7143cc5cbf3aec1536286653e38b0809abc99fb76c91bd910dbd98ae003d890 + sha256: e6aa2dc5d3b44e8bbe85ef901be69eac59ba4136427f11f4c8b2a303e1e774e7 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" flutter_secure_storage: dependency: "direct main" description: @@ -821,10 +829,10 @@ packages: dependency: transitive description: name: html - sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.15.3" + version: "0.15.4" http: dependency: "direct main" description: @@ -953,10 +961,10 @@ packages: dependency: transitive description: name: lints - sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" local_auth: dependency: "direct main" description: @@ -977,10 +985,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: "23522951540d20a57a60202ed7022e6376bed206a4eee1c347a91f58bd57eb9f" + sha256: "0793a5866062e5cc8a8b24892fa94c3095953ea914a7fdf790f550dd7537fe60" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.0" matcher: dependency: transitive description: @@ -1033,10 +1041,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: "8b46d7eb40abdda92d62edd01546051f0c27365e65608c284de336dccfef88cc" + sha256: "7d5b53bcd556c1bc7ffbe4e4d5a19c3e112b7e925e9e172dd7c6ad0630812616" url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "5.4.2" mocktail: dependency: transitive description: @@ -1145,10 +1153,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" path_provider_linux: dependency: transitive description: @@ -1169,50 +1177,50 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 + sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted - version: "10.2.1" + version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.0.8" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: @@ -1221,6 +1229,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + pinenacl: + dependency: transitive + description: + name: pinenacl + sha256: "3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327" + url: "https://pub.dev" + source: hosted + version: "0.5.1" platform: dependency: transitive description: @@ -1233,10 +1249,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pointycastle: dependency: "direct main" description: @@ -1390,18 +1406,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" source_helper: dependency: transitive description: name: source_helper - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.4" source_map_stack_trace: dependency: transitive description: @@ -1451,6 +1467,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2+1" + stellar_flutter_sdk: + dependency: "direct main" + description: + name: stellar_flutter_sdk + sha256: "7a9b7dc76018bbd0b9c828045cf0e26e07ec44208fb1a1733273de2390205475" + url: "https://pub.dev" + source: hosted + version: "1.6.0" stream_channel: dependency: transitive description: @@ -1555,14 +1579,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + toml: + dependency: transitive + description: + name: toml + sha256: "69756bc12eccf279b72217a87310d217efc4b3752f722e890f672801f19ac485" + url: "https://pub.dev" + source: hosted + version: "0.13.1" tuple: dependency: "direct main" description: name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" typed_data: dependency: transitive description: @@ -1579,22 +1611,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b" + url: "https://pub.dev" + source: hosted + version: "0.2.0" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.1.12" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51 + sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af" url: "https://pub.dev" source: hosted - version: "6.0.35" + version: "6.0.37" url_launcher_ios: dependency: transitive description: @@ -1615,34 +1655,34 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.18" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" uuid: dependency: "direct main" description: @@ -1768,18 +1808,18 @@ packages: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.0.5" win32_registry: dependency: transitive description: name: win32_registry - sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" window_size: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bff7cae6e..d3c93e77a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -138,6 +138,7 @@ dependencies: desktop_drop: ^0.4.1 nanodart: ^2.0.0 basic_utils: ^5.5.4 + stellar_flutter_sdk: ^1.6.0 dev_dependencies: flutter_test: diff --git a/test/price_test.dart b/test/price_test.dart index f611c5450..6741d5445 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -28,7 +28,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash" - ",namecoin,wownero,ethereum,particl,nano,banano&order=market_cap_desc&per_page=50" + ",namecoin,wownero,ethereum,particl,nano,banano,stellar&order=market_cap_desc&per_page=50" "&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -113,19 +113,21 @@ void main() { 'Coin.namecoin: [0, 0.0], ' 'Coin.nano: [0, 0.0], ' 'Coin.particl: [0, 0.0], ' + 'Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' 'Coin.bitcoincashTestnet: [0, 0.0], ' 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); verify(client.get( Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false", ), headers: {'Content-Type': 'application/json'})).called(1); @@ -140,7 +142,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&" "ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -219,23 +221,20 @@ void main() { '{' 'Coin.bitcoin: [1, 0.0], ' 'Coin.monero: [0.00717236, -0.77656], ' - 'Coin.banano: [0, 0.0], ' - 'Coin.bitcoincash: [0, 0.0], ' + 'Coin.banano: [0, 0.0], Coin.bitcoincash: [0, 0.0], ' 'Coin.dogecoin: [0.00000315, -2.68533], ' 'Coin.eCash: [0, 0.0], ' - 'Coin.epicCash: [0.00002803, 7.27524], ' - 'Coin.ethereum: [0, 0.0], ' + 'Coin.epicCash: [0.00002803, 7.27524], Coin.ethereum: [0, 0.0], ' 'Coin.firo: [0.0001096, -0.89304], ' 'Coin.litecoin: [0, 0.0], ' 'Coin.namecoin: [0, 0.0], ' - 'Coin.nano: [0, 0.0], ' - 'Coin.particl: [0, 0.0], ' + 'Coin.nano: [0, 0.0], Coin.particl: [0, 0.0], Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' - 'Coin.bitcoincashTestnet: [0, 0.0], ' - 'Coin.dogecoinTestNet: [0, 0.0], ' + 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); @@ -244,7 +243,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: {'Content-Type': 'application/json'})).called(1); @@ -258,7 +257,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -331,8 +330,7 @@ void main() { expect( price.toString(), '{' - 'Coin.bitcoin: [0, 0.0], ' - 'Coin.monero: [0, 0.0], ' + 'Coin.bitcoin: [0, 0.0], Coin.monero: [0, 0.0], ' 'Coin.banano: [0, 0.0], ' 'Coin.bitcoincash: [0, 0.0], ' 'Coin.dogecoin: [0, 0.0], ' @@ -344,12 +342,14 @@ void main() { 'Coin.namecoin: [0, 0.0], ' 'Coin.nano: [0, 0.0], ' 'Coin.particl: [0, 0.0], ' + 'Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' 'Coin.bitcoincashTestnet: [0, 0.0], ' 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); }); @@ -361,7 +361,7 @@ void main() { Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano" + "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' @@ -389,12 +389,14 @@ void main() { 'Coin.namecoin: [0, 0.0], ' 'Coin.nano: [0, 0.0], ' 'Coin.particl: [0, 0.0], ' + 'Coin.stellar: [0, 0.0], ' 'Coin.wownero: [0, 0.0], ' 'Coin.bitcoinTestNet: [0, 0.0], ' 'Coin.bitcoincashTestnet: [0, 0.0], ' 'Coin.dogecoinTestNet: [0, 0.0], ' 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0]' + 'Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.stellarTestnet: [0, 0.0]' '}', ); });