diff --git a/assets/svg/circle-plus-filled.svg b/assets/svg/circle-plus-filled.svg new file mode 100644 index 000000000..3e3244adb --- /dev/null +++ b/assets/svg/circle-plus-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/models/paynym/paynym_account.dart b/lib/models/paynym/paynym_account.dart new file mode 100644 index 000000000..08438a6b7 --- /dev/null +++ b/lib/models/paynym/paynym_account.dart @@ -0,0 +1,48 @@ +import 'package:stackwallet/models/paynym/paynym_code.dart'; + +class PaynymAccount { + final String nymID; + final String nymName; + + final List codes; + + /// list of nymId + final List followers; + + /// list of nymId + final List following; + + PaynymAccount( + this.nymID, + this.nymName, + this.codes, + this.followers, + this.following, + ); + + PaynymAccount.fromMap(Map map) + : nymID = map["nymID"] as String, + nymName = map["nymName"] as String, + codes = (map["codes"] as List) + .map((e) => PaynymCode.fromMap(Map.from(e as Map))) + .toList(), + followers = (map["followers"] as List) + .map((e) => e["nymId"] as String) + .toList(), + following = (map["following"] as List) + .map((e) => e["nymId"] as String) + .toList(); + + Map toMap() => { + "nymID": nymID, + "nymName": nymName, + "codes": codes.map((e) => e.toMap()), + "followers": followers.map((e) => {"nymId": e}), + "following": followers.map((e) => {"nymId": e}), + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/paynym/paynym_code.dart b/lib/models/paynym/paynym_code.dart new file mode 100644 index 000000000..d01366d20 --- /dev/null +++ b/lib/models/paynym/paynym_code.dart @@ -0,0 +1,27 @@ +class PaynymCode { + final bool claimed; + final bool segwit; + final String code; + + PaynymCode( + this.claimed, + this.segwit, + this.code, + ); + + PaynymCode.fromMap(Map map) + : claimed = map["claimed"] as bool, + segwit = map["segwit"] as bool, + code = map["code"] as String; + + Map toMap() => { + "claimed": claimed, + "segwit": segwit, + "code": code, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/pages/paynym/paynym_claim_view.dart b/lib/pages/paynym/paynym_claim_view.dart index adb70f1d8..b9b5ebd95 100644 --- a/lib/pages/paynym/paynym_claim_view.dart +++ b/lib/pages/paynym/paynym_claim_view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -8,6 +9,7 @@ import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/services/coins/coin_paynym_extension.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; @@ -88,53 +90,52 @@ class _PaynymClaimViewState extends ConsumerState { builder: (context) => const ClaimingPaynymDialog(), ).then((value) => shouldCancel = value == true), ); - // generate and submit paynym to api + // ghet wallet to access paynym calls final wallet = ref .read(walletsChangeNotifierProvider) .getManager(widget.walletId) .wallet as DogecoinWallet; + + // get payment code final pCode = await wallet.getPaymentCode(); - final result = await ref + // attempt to create new entry in paynym.is db + final created = await ref .read(paynymAPIProvider) .create(pCode.toString()); - // final result = - // await ref.read(paynymAPIProvider).token(pCode.toString()); + if (created.claimed) { + // payment code already claimed + debugPrint("pcode already claimed!!"); + return; + } - // final token = - // "IlBNOFRKWWt1U2RZWEpud0RCcThDaGZpbmZYdjNzcnhoUXJ4M2VvRXdiU3c1MXdNamRvOUpKMkRzeWN3VDNndDN6SFE3Y1YxZ3J2YWJNbW1mMUJ0ajZmWTd0Z2tnU3o5QjhNWnVSM2tqWWZnTUxNVVJKQ1hOIg.FoPF3g.KUMZDC4U_ek-B6cqPLYilXniQv8"; - // - // print("======================"); - // print(token); - // print(token.codeUnits); - // print(utf8.encode(token)); - // print(utf8.decode(token.codeUnits)); - // - // print("======================"); - // - // final signed = await wallet.signWithNotificationKey( - // Uint8List.fromList(token.codeUnits)); - // - // final signedString = Format.uint8listToString(signed); - // - // print("======================"); - // print(signed); - // print(signedString); - // - // print("======================"); + String token; - // final result2 = await ref - // .read(paynymAPIProvider) - // .claim(token, signedString); + if (created.token == null) { + // payment code already in db + // so we need to fetch a token - // print("======================"); - // print( - // result2); // {claimed: PM8TJYkuSdYXJnwDBq8ChfinfXv3srxhQrx3eoEwbSw51wMjdo9JJ2DsycwT3gt3zHQ7cV1grvabMmmf1Btj6fY7tgkgSz9B8MZuR3kjYfgMLMURJCXN, token: IlBNOFRKWWt1U2RZWEpud0RCcThDaGZpbmZYdjNzcnhoUXJ4M2VvRXdiU3c1MXdNamRvOUpKMkRzeWN3VDNndDN6SFE3Y1YxZ3J2YWJNbW1mMUJ0ajZmWTd0Z2tnU3o5QjhNWnVSM2tqWWZnTUxNVVJKQ1hOIg.FoPF3g.KUMZDC4U_ek-B6cqPLYilXniQv8} - // print("======================"); + token = await ref + .read(paynymAPIProvider) + .token(pCode.toString()); + } else { + token = created.token!; + } - await Future.delayed(const Duration(seconds: 3)); + // sign token with notification private key + final signatureBytes = await wallet.signWithNotificationKey( + Uint8List.fromList(token.codeUnits)); + final signature = Format.uint8listToString(signatureBytes); + + // claim paynym account + final claim = + await ref.read(paynymAPIProvider).claim(token, signature); + + if (claim["claimed"] == pCode.toString()) { + // mark claim successful + } if (mounted && !shouldCancel) { Navigator.of(context).pop(); diff --git a/lib/pages/paynym/paynym_home_view.dart b/lib/pages/paynym/paynym_home_view.dart index 93873b003..4782e2491 100644 --- a/lib/pages/paynym/paynym_home_view.dart +++ b/lib/pages/paynym/paynym_home_view.dart @@ -1,18 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/share_icon.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/toggle.dart'; class PaynymHomeView extends StatefulWidget { const PaynymHomeView({ Key? key, required this.walletId, - required this.paymentCodeString, + required this.nymAccount, }) : super(key: key); final String walletId; - final String paymentCodeString; + final PaynymAccount nymAccount; static const String routeName = "/paynymHome"; @@ -21,6 +34,8 @@ class PaynymHomeView extends StatefulWidget { } class _PaynymHomeViewState extends State { + bool showFollowing = false; + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); @@ -40,14 +55,172 @@ class _PaynymHomeViewState extends State { style: STextStyles.navBarTitle(context), overflow: TextOverflow.ellipsis, ), + actions: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + width: 20, + height: 20, + color: Theme.of(context).extension()!.textDark, + ), + onPressed: () { + // todo add ? + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 20, + height: 20, + color: Theme.of(context).extension()!.textDark, + ), + onPressed: () { + // todo add ? + }, + ), + ), + ), + const SizedBox( + width: 4, + ), + ], ), body: SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ PayNymBot( - paymentCodeString: widget.paymentCodeString, + paymentCodeString: widget.nymAccount.codes.first.code, + ), + const SizedBox( + height: 10, + ), + Text( + widget.nymAccount.nymName, + style: STextStyles.desktopMenuItemSelected(context), + ), + const SizedBox( + height: 4, + ), + Text( + Format.shorten(widget.nymAccount.codes.first.code, 12, 5), + style: STextStyles.label(context), + ), + const SizedBox( + height: 11, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Copy", + buttonHeight: ButtonHeight.l, + iconSpacing: 4, + icon: CopyIcon( + width: 10, + height: 10, + color: Theme.of(context) + .extension()! + .textDark, + ), + onPressed: () { + // copy to clipboard + }, + ), + ), + const SizedBox( + width: 13, + ), + Expanded( + child: SecondaryButton( + label: "Share", + buttonHeight: ButtonHeight.l, + iconSpacing: 4, + icon: ShareIcon( + width: 10, + height: 10, + color: Theme.of(context) + .extension()! + .textDark, + ), + onPressed: () { + // copy to clipboard + }, + ), + ), + const SizedBox( + width: 13, + ), + Expanded( + child: SecondaryButton( + label: "Address", + buttonHeight: ButtonHeight.l, + iconSpacing: 4, + icon: QrCodeIcon( + width: 10, + height: 10, + color: Theme.of(context) + .extension()! + .textDark, + ), + onPressed: () { + // copy to clipboard + }, + ), + ), + ], + ), + const SizedBox( + height: 24, + ), + SizedBox( + height: 40, + child: Toggle( + onColor: Theme.of(context).extension()!.popupBG, + onText: "Following", + offColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + offText: "Followers", + isOn: showFollowing, + onValueChanged: (value) { + setState(() { + showFollowing = value; + }); + }, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Your PayNym contacts will appear here", + style: STextStyles.label(context), + ), + ], + ), ), ], ), @@ -61,12 +234,30 @@ class PayNymBot extends StatelessWidget { const PayNymBot({ Key? key, required this.paymentCodeString, + this.size = 60.0, }) : super(key: key); final String paymentCodeString; + final double size; @override Widget build(BuildContext context) { - return Image.network("https://paynym.is/$paymentCodeString/avatar"); + return ClipRRect( + borderRadius: BorderRadius.circular(size / 2), + child: SizedBox( + width: size, + height: size, + child: Image.network( + "https://paynym.is/$paymentCodeString/avatar", + loadingBuilder: (context, child, event) { + if (event == null) { + return child; + } else { + return const LoadingIndicator(); + } + }, + ), + ), + ); } } diff --git a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart index 41b429311..af5b19f14 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart @@ -1,10 +1,21 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; +import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/coins/coin_paynym_extension.dart'; +import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:tuple/tuple.dart'; + +import '../../../widgets/loading_indicator.dart'; class WalletNavigationBar extends StatefulWidget { const WalletNavigationBar({ @@ -88,42 +99,84 @@ class _WalletNavigationBarState extends State { AnimatedOpacity( opacity: scale, duration: duration, - child: GestureDetector( - onTap: () { - setState(() { - scale = 0; - }); - Navigator.of(context).pushNamed( - PaynymClaimView.routeName, - arguments: widget.walletId, - ); - }, - child: Container( - padding: const EdgeInsets.all(16), - width: 146, - decoration: BoxDecoration( - color: - Theme.of(context).extension()!.popupBG, - boxShadow: [ - Theme.of(context) - .extension()! - .standardBoxShadow - ], - borderRadius: BorderRadius.circular( - widget.height / 2.0, + child: Consumer(builder: (context, ref, __) { + return GestureDetector( + onTap: () async { + setState(() { + scale = 0; + }); + unawaited( + showDialog( + context: context, + builder: (context) => const LoadingIndicator( + width: 100, + ), + ), + ); + + // todo make generic and not doge specific + final wallet = (ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as DogecoinWallet); + + final code = await wallet.getPaymentCode(); + + final account = await ref + .read(paynymAPIProvider) + .nym(code.toString()); + + if (mounted) { + Navigator.of(context).pop(); + + // check if account exists and for matching code to see if claimed + if (account != null && + account.codes + .where((e) => + e.code == code.toString() && e.claimed) + .isNotEmpty) { + await Navigator.of(context).pushNamed( + PaynymHomeView.routeName, + arguments: Tuple2( + widget.walletId, + account, + ), + ); + } else { + await Navigator.of(context).pushNamed( + PaynymClaimView.routeName, + arguments: widget.walletId, + ); + } + } + }, + child: Container( + padding: const EdgeInsets.all(16), + width: 146, + decoration: BoxDecoration( + color: + Theme.of(context).extension()!.popupBG, + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow + ], + borderRadius: BorderRadius.circular( + widget.height / 2.0, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Paynym", + style: STextStyles.w600_12(context), + ), + ], ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Paynym", - style: STextStyles.w600_12(context), - ), - ], - ), - ), - ), + ); + }), ), const SizedBox( height: 8, diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 3da3a29d7..3b4c4e103 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; @@ -204,12 +205,12 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case PaynymHomeView.routeName: - if (args is Tuple2) { + if (args is Tuple2) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => PaynymHomeView( walletId: args.item1, - paymentCodeString: args.item2, + nymAccount: args.item2, ), settings: RouteSettings( name: settings.name, diff --git a/lib/services/coins/coin_paynym_extension.dart b/lib/services/coins/coin_paynym_extension.dart index 99ee6ec32..366b2cf78 100644 --- a/lib/services/coins/coin_paynym_extension.dart +++ b/lib/services/coins/coin_paynym_extension.dart @@ -34,4 +34,7 @@ extension PayNym on DogecoinWallet { final signed = pair.sign(SHA256Digest().process(data)); return signed; } + + // Future> prepareNotificationTransaction( + // String targetPaymentCode) async {} } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index b02c6584e..c85a2bf3c 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -76,6 +76,7 @@ class _SVG { String get circleSliders => "assets/svg/configuration.svg"; String get circlePlus => "assets/svg/plus-circle.svg"; + String get circlePlusFilled => "assets/svg/circle-plus-filled.svg"; String get framedGear => "assets/svg/framed-gear.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg"; String get circleNode => "assets/svg/node-circle.svg"; diff --git a/lib/utilities/format.dart b/lib/utilities/format.dart index 136ec5b95..a0a6613a7 100644 --- a/lib/utilities/format.dart +++ b/lib/utilities/format.dart @@ -8,6 +8,10 @@ import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; abstract class Format { + static String shorten(String value, int beginCount, int endCount) { + return "${value.substring(0, beginCount)}...${value.substring(value.length - endCount)}"; + } + static Decimal satoshisToAmount(int sats, {required Coin coin}) { return (Decimal.fromInt(sats) / Decimal.fromInt(Constants.satsPerCoin(coin))) diff --git a/lib/utilities/paynym_api.dart b/lib/utilities/paynym_api.dart index e2ef33a96..65de910a6 100644 --- a/lib/utilities/paynym_api.dart +++ b/lib/utilities/paynym_api.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:stackwallet/models/paynym/created_paynym.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; class PaynymAPI { static const String baseURL = "https://paynym.is/api"; @@ -117,8 +118,9 @@ class PaynymAPI { // // // ------ - Future> token(String code) async { - return _post("/token", {"code": code}); + Future token(String code) async { + final map = await _post("/token", {"code": code}); + return map["token"] as String; } // ### `/api/v1/nym` @@ -172,8 +174,13 @@ class PaynymAPI { // | 404 | Nym not found | // | 400 | Bad request | - Future> nym(String code) async { - return _post("/nym", {"code": code}); + Future nym(String code) async { + final map = await _post("/nym", {"nym": code}); + try { + return PaynymAccount.fromMap(map); + } catch (_) { + return null; + } } // ## Authenticated Requests diff --git a/lib/widgets/desktop/secondary_button.dart b/lib/widgets/desktop/secondary_button.dart index 62bd900dd..5603815b4 100644 --- a/lib/widgets/desktop/secondary_button.dart +++ b/lib/widgets/desktop/secondary_button.dart @@ -16,6 +16,7 @@ class SecondaryButton extends StatelessWidget { this.onPressed, this.enabled = true, this.buttonHeight, + this.iconSpacing = 10, }) : super(key: key); final double? width; @@ -25,6 +26,7 @@ class SecondaryButton extends StatelessWidget { final bool enabled; final Widget? icon; final ButtonHeight? buttonHeight; + final double iconSpacing; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { @@ -66,6 +68,16 @@ class SecondaryButton extends StatelessWidget { : STextStyles.desktopButtonSecondaryDisabled(context); } } else { + if (buttonHeight == ButtonHeight.l) { + return STextStyles.button(context).copyWith( + fontSize: 10, + color: enabled + ? Theme.of(context).extension()!.buttonTextSecondary + : Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + ); + } return STextStyles.button(context).copyWith( color: enabled ? Theme.of(context).extension()!.buttonTextSecondary @@ -136,8 +148,8 @@ class SecondaryButton extends StatelessWidget { children: [ if (icon != null) icon!, if (icon != null && label != null) - const SizedBox( - width: 10, + SizedBox( + width: iconSpacing, ), if (label != null) Text( diff --git a/lib/widgets/icon_widgets/share_icon.dart b/lib/widgets/icon_widgets/share_icon.dart new file mode 100644 index 000000000..7a89dd829 --- /dev/null +++ b/lib/widgets/icon_widgets/share_icon.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; + +class ShareIcon extends StatelessWidget { + const ShareIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.share, + width: width, + height: height, + color: color ?? Theme.of(context).extension()!.textDark3, + ); + } +} diff --git a/lib/widgets/loading_indicator.dart b/lib/widgets/loading_indicator.dart index de4ed464e..8725af439 100644 --- a/lib/widgets/loading_indicator.dart +++ b/lib/widgets/loading_indicator.dart @@ -1,6 +1,5 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; - import 'package:stackwallet/utilities/assets.dart'; class LoadingIndicator extends StatelessWidget { @@ -15,13 +14,18 @@ class LoadingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: width, - height: height, - child: Lottie.asset( - Assets.lottie.test2, - animate: true, - repeat: true, + return Container( + color: Colors.transparent, + child: Center( + child: SizedBox( + width: width, + height: height, + child: Lottie.asset( + Assets.lottie.test2, + animate: true, + repeat: true, + ), + ), ), ); } diff --git a/lib/widgets/toggle.dart b/lib/widgets/toggle.dart index 651f2b5e1..7d3135770 100644 --- a/lib/widgets/toggle.dart +++ b/lib/widgets/toggle.dart @@ -186,29 +186,31 @@ class ToggleState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - widget.onIcon ?? "", - width: 12, - height: 14, - color: isDesktop - ? !_isOn - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary - : !_isOn - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - const SizedBox( - width: 5, - ), + if (widget.onIcon != null) + SvgPicture.asset( + widget.onIcon ?? "", + width: 12, + height: 14, + color: isDesktop + ? !_isOn + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : !_isOn + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + if (widget.onIcon != null) + const SizedBox( + width: 5, + ), Text( widget.onText ?? "", style: isDesktop @@ -243,29 +245,31 @@ class ToggleState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - widget.offIcon ?? "", - width: 12, - height: 14, - color: isDesktop - ? _isOn - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary - : _isOn - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - const SizedBox( - width: 5, - ), + if (widget.offIcon != null) + SvgPicture.asset( + widget.offIcon ?? "", + width: 12, + height: 14, + color: isDesktop + ? _isOn + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : _isOn + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + if (widget.offIcon != null) + const SizedBox( + width: 5, + ), Text( widget.offText ?? "", style: isDesktop diff --git a/pubspec.yaml b/pubspec.yaml index af8e082e6..b5e85fb26 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -305,6 +305,7 @@ flutter: - assets/svg/keys.svg - assets/svg/arrow-down.svg - assets/svg/plus-circle.svg + - assets/svg/circle-plus-filled.svg - assets/svg/configuration.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg