chore(source structure/naming): organize and name accordingly
What changed, and why it matters
This commit simply moves three Dart files from one folder to another and updates the corresponding import paths. It is a routine code organization change with no visible security implications.
No security action needed; treat as a normal refactoring commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch relocates desktop service-related UI files from lib/pages_desktop_specific/more_view/sub_widgets/ to lib/pages_desktop_specific/services/ and adjusts imports in desktop_home_view.dart and route_generator.dart. File contents are otherwise identical, and no functional, cryptographic, or network code was modified.
Changed components
lib/pages_desktop_specific/desktop_home_view.dartlib/pages_desktop_specific/services/desktop_services_view.dartlib/pages_desktop_specific/services/sub_widgets/desktop_gift_cards_view.dartlib/pages_desktop_specific/services/sub_widgets/desktop_shopinbit_view.dartlib/route_generator.dartInspect captured patch +827 / −834
diff --git a/lib/pages_desktop_specific/desktop_home_view.dart b/lib/pages_desktop_specific/desktop_home_view.dart
index d1cd371..f61cc94 100644
--- a/lib/pages_desktop_specific/desktop_home_view.dart
+++ b/lib/pages_desktop_specific/desktop_home_view.dart
@@ -31,10 +31,10 @@ import 'address_book_view/desktop_address_book.dart';
import 'desktop_buy/desktop_buy_view.dart';
import 'desktop_exchange/desktop_exchange_view.dart';
import 'desktop_menu.dart';
-import 'more_view/sub_widgets/desktop_services_view.dart';
import 'my_stack_view/my_stack_view.dart';
import 'notifications/desktop_notifications_view.dart';
import 'password/desktop_unlock_app_dialog.dart';
+import 'services/desktop_services_view.dart';
import 'settings/desktop_settings_view.dart';
import 'settings/settings_menu/desktop_about_view.dart';
import 'settings/settings_menu/desktop_support_view.dart';
diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart
deleted file mode 100644
index 8c328bd..0000000
--- a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart
+++ /dev/null
@@ -1,142 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/svg.dart';
-
-import '../../../app_config.dart';
-import '../../../pages/cakepay/cakepay_orders_view.dart';
-import '../../../pages/cakepay/cakepay_vendors_view.dart';
-import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
-import '../../../services/tor_service.dart';
-import '../../../themes/stack_colors.dart';
-import '../../../utilities/assets.dart';
-import '../../../utilities/text_styles.dart';
-import '../../../widgets/desktop/primary_button.dart';
-import '../../../widgets/desktop/secondary_button.dart';
-import '../../../widgets/rounded_white_container.dart';
-import '../../../widgets/tor_subscription.dart';
-
-class DesktopGiftCardsView extends ConsumerStatefulWidget {
- const DesktopGiftCardsView({super.key});
-
- static const String routeName = "/desktopGiftCardsView";
-
- @override
- ConsumerState<DesktopGiftCardsView> createState() =>
- _DesktopGiftCardsViewState();
-}
-
-class _DesktopGiftCardsViewState extends ConsumerState<DesktopGiftCardsView> {
- late bool _torEnabled;
-
- @override
- void initState() {
- _torEnabled = AppConfig.hasFeature(AppFeature.tor)
- ? ref.read(pTorService).status != TorConnectionStatus.disconnected
- : false;
- super.initState();
- }
-
- @override
- Widget build(BuildContext context) {
- return TorSubscription(
- onTorStatusChanged: (status) {
- setState(() {
- _torEnabled = status != TorConnectionStatus.disconnected;
- });
- },
- child: Column(
- children: [
- Padding(
- padding: const EdgeInsets.only(right: 30),
- child: RoundedWhiteContainer(
- radiusMultiplier: 2,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: SvgPicture.asset(
- Assets.svg.creditCard,
- width: 48,
- height: 48,
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(10),
- child: RichText(
- textAlign: TextAlign.start,
- text: TextSpan(
- children: [
- TextSpan(
- text: "CakePay",
- style: STextStyles.desktopTextSmall(context),
- ),
- TextSpan(
- text:
- "\n\nPurchase gift cards with cryptocurrency.",
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ),
- ),
- ],
- ),
- ),
- ),
- if (_torEnabled)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10),
- child: Text(
- "CakePay is not available while Tor is enabled",
- style: STextStyles.desktopTextExtraExtraSmall(context)
- .copyWith(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.textSubtitle1,
- ),
- ),
- ),
- Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 10,
- vertical: 6,
- ),
- child: Row(
- children: [
- Expanded(
- child: PrimaryButton(
- buttonHeight: ButtonHeight.m,
- label: "Browse Gift Cards",
- enabled: !_torEnabled,
- onPressed: () {
- showDialog<void>(
- context: context,
- builder: (_) => const CakePayVendorsView(),
- );
- },
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: SecondaryButton(
- buttonHeight: ButtonHeight.m,
- label: "My Orders",
- onPressed: () {
- showDialog<void>(
- context: context,
- builder: (_) => const CakePayOrdersView(),
- );
- },
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart
deleted file mode 100644
index dca35f5..0000000
--- a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/svg.dart';
-
-import '../../../route_generator.dart';
-import '../../../themes/stack_colors.dart';
-import '../../../utilities/assets.dart';
-import '../../../utilities/text_styles.dart';
-import '../../../widgets/desktop/desktop_app_bar.dart';
-import '../../../widgets/desktop/desktop_scaffold.dart';
-import '../../settings/settings_menu_item.dart';
-import 'desktop_gift_cards_view.dart';
-import 'desktop_shopinbit_view.dart';
-
-final selectedServicesMenuItemStateProvider = StateProvider<int>((_) => 0);
-
-class DesktopServicesView extends ConsumerStatefulWidget {
- const DesktopServicesView({super.key});
-
- static const String routeName = "/desktopServicesView";
-
- @override
- ConsumerState<DesktopServicesView> createState() =>
- _DesktopServicesViewState();
-}
-
-class _DesktopServicesViewState extends ConsumerState<DesktopServicesView> {
- final List<String> _labels = const ["Services", "Gift Cards"];
-
- @override
- Widget build(BuildContext context) {
- final List<Widget> contentViews = [
- const Navigator(
- key: Key("servicesShopInBitDesktopKey"),
- onGenerateRoute: RouteGenerator.generateRoute,
- initialRoute: DesktopShopInBitView.routeName,
- ),
- const Navigator(
- key: Key("servicesGiftCardsDesktopKey"),
- onGenerateRoute: RouteGenerator.generateRoute,
- initialRoute: DesktopGiftCardsView.routeName,
- ),
- ];
-
- return DesktopScaffold(
- background: Theme.of(context).extension<StackColors>()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: true,
- leading: Row(
- children: [
- const SizedBox(width: 24, height: 24),
- Text("Services", style: STextStyles.desktopH3(context)),
- ],
- ),
- ),
- body: Row(
- children: [
- Padding(
- padding: const EdgeInsets.all(15.0),
- child: Align(
- alignment: Alignment.topLeft,
- child: SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- width: 250,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- for (int i = 0; i < _labels.length; i++)
- Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (i > 0) const SizedBox(height: 2),
- SettingsMenuItem<int>(
- icon: SvgPicture.asset(
- Assets.svg.polygon,
- width: 11,
- height: 11,
- color:
- ref
- .watch(
- selectedServicesMenuItemStateProvider
- .state,
- )
- .state ==
- i
- ? Theme.of(context)
- .extension<StackColors>()!
- .accentColorBlue
- : Colors.transparent,
- ),
- label: _labels[i],
- value: i,
- group: ref
- .watch(
- selectedServicesMenuItemStateProvider
- .state,
- )
- .state,
- onChanged: (newValue) =>
- ref
- .read(
- selectedServicesMenuItemStateProvider
- .state,
- )
- .state =
- newValue,
- ),
- ],
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- Expanded(
- child:
- contentViews[ref
- .watch(selectedServicesMenuItemStateProvider.state)
- .state],
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart
deleted file mode 100644
index e5c9e59..0000000
--- a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart
+++ /dev/null
@@ -1,550 +0,0 @@
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:url_launcher/url_launcher.dart';
-
-import '../../../app_config.dart';
-import '../../../db/isar/main_db.dart';
-import '../../../models/shopinbit/shopinbit_order_model.dart';
-import '../../../notifications/show_flush_bar.dart';
-import '../../../pages/shopinbit/shopinbit_step_1.dart';
-import '../../../pages/shopinbit/shopinbit_step_2.dart';
-import '../../../pages/shopinbit/shopinbit_tickets_view.dart';
-import '../../../providers/desktop/current_desktop_menu_item.dart';
-import '../../../services/shopinbit/shopinbit_service.dart';
-import '../../../themes/stack_colors.dart';
-import '../../../utilities/assets.dart';
-import '../../../utilities/text_styles.dart';
-import '../../../widgets/desktop/desktop_dialog.dart';
-import '../../../widgets/desktop/desktop_dialog_close_button.dart';
-import '../../../widgets/desktop/primary_button.dart';
-import '../../../widgets/desktop/secondary_button.dart';
-import '../../../widgets/rounded_container.dart';
-import '../../../widgets/rounded_white_container.dart';
-import '../../../widgets/textfields/adaptive_text_field.dart';
-import '../../desktop_menu.dart';
-import '../../settings/settings_menu.dart';
-
-class DesktopShopInBitView extends ConsumerStatefulWidget {
- const DesktopShopInBitView({super.key});
-
- static const String routeName = "/desktopShopInBitView";
-
- @override
- ConsumerState<DesktopShopInBitView> createState() =>
- _DesktopServicesViewState();
-}
-
-class _DesktopServicesViewState extends ConsumerState<DesktopShopInBitView> {
- Future<bool> _showOpenBrowserWarning(BuildContext context, String url) async {
- final uri = Uri.parse(url);
- final shouldContinue = await showDialog<bool>(
- context: context,
- barrierDismissible: false,
- builder: (_) => DesktopDialog(
- maxWidth: 550,
- maxHeight: 250,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
- child: Column(
- children: [
- Text("Attention", style: STextStyles.desktopH2(context)),
- const SizedBox(height: 16),
- Text(
- "You are about to open "
- "${uri.scheme}://${uri.host} "
- "in your browser.",
- style: STextStyles.desktopTextSmall(context),
- ),
- const SizedBox(height: 35),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SecondaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Cancel",
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop(false);
- },
- ),
- const SizedBox(width: 20),
- PrimaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Continue",
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop(true);
- },
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- );
- return shouldContinue ?? false;
- }
-
- void _showShopDialog(BuildContext context) async {
- final service = ShopInBitService.instance;
- final model = ShopInBitOrderModel();
- bool isFirstRun = false;
-
- if (!service.loadSetupComplete()) {
- // First-time user: show setup.
- final completed = await showDialog<bool>(
- context: context,
- barrierDismissible: false,
- builder: (_) => _ShopInBitDesktopSetupDialog(model: model),
- );
- if (completed != true) return; // user cancelled
- isFirstRun = true;
- } else {
- // Returning user: restore display name.
- final savedName = service.loadDisplayName();
- if (savedName != null && savedName.isNotEmpty) {
- model.displayName = savedName;
- }
- }
-
- if (!mounted) return;
-
- if (isFirstRun) {
- // First run: show service overview then go directly to Step2
- // (name was just entered in setup dialog, no need to show Step1 again).
- showDialog<void>(
- context: context,
- barrierDismissible: false,
- builder: (dialogContext) => DesktopDialog(
- maxWidth: 550,
- maxHeight: 300,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text("ShopinBit", style: STextStyles.desktopH2(dialogContext)),
- const SizedBox(height: 16),
- RichText(
- text: TextSpan(
- style: STextStyles.desktopTextSmall(dialogContext),
- children: const [
- TextSpan(
- text:
- "Please note the following before proceeding:"
- "\n\n\u2022 Minimum order amount: 1,000 EUR"
- "\n\u2022 Service fee: 10% of the order total",
- ),
- ],
- ),
- ),
- const Spacer(),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SecondaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Cancel",
- onPressed: () {
- Navigator.of(dialogContext, rootNavigator: true).pop();
- },
- ),
- const SizedBox(width: 20),
- PrimaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Continue",
- onPressed: () async {
- Navigator.of(dialogContext, rootNavigator: true).pop();
- await showDialog<void>(
- context: context,
- barrierDismissible: false,
- builder: (_) => ShopInBitStep2(model: model),
- );
- if (mounted) setState(() {});
- },
- ),
- ],
- ),
- const Spacer(),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SecondaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Cancel",
- onPressed: () {
- Navigator.of(dialogContext, rootNavigator: true).pop();
- },
- ),
- const SizedBox(width: 20),
- PrimaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Continue",
- onPressed: () async {
- Navigator.of(dialogContext, rootNavigator: true).pop();
- await showDialog<void>(
- context: context,
- barrierDismissible: false,
- builder: (_) => ShopInBitStep1(model: model),
- );
- if (mounted) setState(() {});
- },
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- );
- } else {
- // Returning user: go directly to Step1 (skip service overview dialog).
- await showDialog<void>(
- context: context,
- barrierDismissible: false,
- builder: (_) => ShopInBitStep1(model: model),
- );
- if (mounted) setState(() {});
- }
- }
-
- @override
- Widget build(BuildContext context) {
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.only(right: 30),
- child: RoundedWhiteContainer(
- radiusMultiplier: 2,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: SvgPicture.asset(
- Assets.svg.circleSliders,
- width: 48,
- height: 48,
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(10),
- child: RichText(
- textAlign: TextAlign.start,
- text: TextSpan(
- style: STextStyles.desktopTextExtraExtraSmall(context),
- children: [
- TextSpan(
- text: "ShopinBit",
- style: STextStyles.desktopTextSmall(context),
- ),
- const TextSpan(
- text:
- "\n\nTurn your crypto into Electronics, Flights, Hotel, "
- "Cars or any other legal product or service... "
- "ShopinBit is a concierge shopping service that helps "
- "you 'live the good life with crypto'..."
- "\n\n"
- "Minimum order value of 1,000 EUR. "
- "A 10% service fee applies to all orders.\n\n"
- "By using ShopinBit, you agree to their ",
- ),
- TextSpan(
- text: "Terms & Conditions",
- style: STextStyles.richLink(
- context,
- ).copyWith(fontSize: 14),
- recognizer: TapGestureRecognizer()
- ..onTap = () async {
- const url =
- "https://api.shopinbit.com/static/policy/terms.html";
- final shouldOpen = await _showOpenBrowserWarning(
- context,
- url,
- );
- if (shouldOpen) {
- await launchUrl(
- Uri.parse(url),
- mode: LaunchMode.externalApplication,
- );
- }
- },
- ),
- const TextSpan(text: " and "),
- TextSpan(
- text: "Privacy Policy",
- style: STextStyles.richLink(
- context,
- ).copyWith(fontSize: 14),
- recognizer: TapGestureRecognizer()
- ..onTap = () async {
- const url =
- "https://api.shopinbit.com/static/policy/privacy.html";
- final shouldOpen = await _showOpenBrowserWarning(
- context,
- url,
- );
- if (shouldOpen) {
- await launchUrl(
- Uri.parse(url),
- mode: LaunchMode.externalApplication,
- );
- }
- },
- ),
- const TextSpan(text: "."),
- ],
- ),
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(10),
- child: Row(
- children: [
- PrimaryButton(
- width: 250,
- buttonHeight: ButtonHeight.m,
- enabled: true,
- label: "Shop with ShopinBit",
- onPressed: () => _showShopDialog(context),
- ),
- const SizedBox(width: 16),
- Builder(
- builder: (context) {
- final count = MainDB.instance
- .getShopInBitTickets()
- .length;
- return SecondaryButton(
- width: 200,
- buttonHeight: ButtonHeight.m,
- label: count > 0
- ? "My requests ($count)"
- : "My requests",
- onPressed: () async {
- await showDialog<void>(
- context: context,
- builder: (_) => const ShopInBitTicketsView(),
- );
- if (mounted) setState(() {});
- },
- );
- },
- ),
- const SizedBox(width: 16),
- SecondaryButton(
- width: 140,
- buttonHeight: ButtonHeight.m,
- label: "Settings",
- onPressed: () {
- // ShopInBit is the last settings menu item.
- var idx = 8;
- if (AppConfig.hasFeature(AppFeature.themeSelection)) {
- idx++;
- }
- ref
- .read(
- selectedSettingsMenuItemStateProvider.state,
- )
- .state =
- idx;
- ref.read(currentDesktopMenuItemProvider.state).state =
- DesktopMenuItemId.settings;
- },
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- );
- }
-}
-
-class _ShopInBitDesktopSetupDialog extends StatefulWidget {
- const _ShopInBitDesktopSetupDialog({required this.model});
-
- final ShopInBitOrderModel model;
-
- @override
- State<_ShopInBitDesktopSetupDialog> createState() =>
- _ShopInBitDesktopSetupDialogState();
-}
-
-class _ShopInBitDesktopSetupDialogState
- extends State<_ShopInBitDesktopSetupDialog> {
- late final Future<String> _keyFuture;
- late final TextEditingController _nameController;
-
- bool get _canContinue => _nameController.text.trim().isNotEmpty;
-
- @override
- void initState() {
- super.initState();
- _keyFuture = ShopInBitService.instance.ensureCustomerKey();
- final existingName = ShopInBitService.instance.loadDisplayName();
- _nameController = TextEditingController(text: existingName ?? '');
- }
-
- @override
- void dispose() {
- _nameController.dispose();
- super.dispose();
- }
-
- Future<void> _completeSetup() async {
- final name = _nameController.text.trim();
- widget.model.displayName = name;
- await ShopInBitService.instance.setDisplayName(name);
- await ShopInBitService.instance.setSetupComplete(true);
- if (mounted) {
- Navigator.of(context, rootNavigator: true).pop(true);
- }
- }
-
- @override
- Widget build(BuildContext context) {
- final maxDialogHeight = MediaQuery.sizeOf(context).height - 64;
- return DesktopDialog(
- maxWidth: 580,
- maxHeight: maxDialogHeight,
- child: Column(
- mainAxisSize: .min,
- crossAxisAlignment: .stretch,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Padding(
- padding: const EdgeInsets.only(left: 32),
- child: Text(
- "ShopinBit Setup",
- style: STextStyles.desktopH3(context),
- ),
- ),
- const DesktopDialogCloseButton(),
- ],
- ),
- Flexible(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 32),
- child: Column(
- mainAxisSize: .min,
- crossAxisAlignment: .stretch,
- children: [
- const SizedBox(height: 16),
- Text(
- "Your Customer Key",
- style: STextStyles.w600_20(context),
- ),
- const SizedBox(height: 8),
- Text(
- "This is your ShopinBit customer key: save it "
- "somewhere safe, you'll need it to recover "
- "your ShopinBit account on a new device.",
- style: STextStyles.desktopTextExtraExtraSmall(context),
- ),
- const SizedBox(height: 12),
- FutureBuilder<String>(
- future: _keyFuture,
- builder: (context, snapshot) {
- if (snapshot.connectionState != ConnectionState.done) {
- return const Center(child: CircularProgressIndicator());
- }
- if (snapshot.hasError) {
- return RoundedContainer(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.warningBackground,
- child: Text(
- "Failed to generate key. Please try again.",
- style: STextStyles.label700(context).copyWith(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.warningForeground,
- fontSize: 14,
- ),
- ),
- );
- }
- final key = snapshot.data!;
- return RoundedWhiteContainer(
- borderColor: Theme.of(
- context,
- ).extension<StackColors>()!.textSubtitle6,
- child: Row(
- children: [
- const SizedBox(width: 10),
- Expanded(
- child: SelectableText(
- key,
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ),
- ),
- ),
- IconButton(
- icon: const Icon(Icons.copy, size: 20),
- onPressed: () {
- Clipboard.setData(ClipboardData(text: key));
- showFloatingFlushBar(
- type: FlushBarType.info,
- message: "Copied to clipboard!",
- context: context,
- );
- },
- ),
- ],
- ),
- );
- },
- ),
- const SizedBox(height: 24),
- Text(
- "Display Name",
- style: STextStyles.desktopTextExtraExtraSmall(context)
- .copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textFieldActiveSearchIconRight,
- ),
- ),
- const SizedBox(height: 8),
- AdaptiveTextField(
- controller: _nameController,
- showPasteClearButton: true,
- maxLines: 1,
- onChangedComprehensive: (_) => setState(() {}),
- ),
- const SizedBox(height: 40),
- Row(
- mainAxisAlignment: .end,
- children: [
- PrimaryButton(
- label: "Complete Setup",
- enabled: _canContinue,
- onPressed: _canContinue ? _completeSetup : null,
- horizontalContentPadding: 20,
- ),
- ],
- ),
- const SizedBox(height: 32),
- ],
- ),
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/pages_desktop_specific/services/desktop_services_view.dart b/lib/pages_desktop_specific/services/desktop_services_view.dart
new file mode 100644
index 0000000..26b6e9e
--- /dev/null
+++ b/lib/pages_desktop_specific/services/desktop_services_view.dart
@@ -0,0 +1,131 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+
+import '../../route_generator.dart';
+import '../../themes/stack_colors.dart';
+import '../../utilities/assets.dart';
+import '../../utilities/text_styles.dart';
+import '../../widgets/desktop/desktop_app_bar.dart';
+import '../../widgets/desktop/desktop_scaffold.dart';
+import '../settings/settings_menu_item.dart';
+import 'sub_widgets/desktop_gift_cards_view.dart';
+import 'sub_widgets/desktop_shopinbit_view.dart';
+
+final selectedServicesMenuItemStateProvider = StateProvider<int>((_) => 0);
+
+class DesktopServicesView extends ConsumerStatefulWidget {
+ const DesktopServicesView({super.key});
+
+ static const String routeName = "/desktopServicesView";
+
+ @override
+ ConsumerState<DesktopServicesView> createState() =>
+ _DesktopServicesViewState();
+}
+
+class _DesktopServicesViewState extends ConsumerState<DesktopServicesView> {
+ final List<String> _labels = const ["Services", "Gift Cards"];
+
+ @override
+ Widget build(BuildContext context) {
+ final List<Widget> contentViews = [
+ const Navigator(
+ key: Key("servicesShopInBitDesktopKey"),
+ onGenerateRoute: RouteGenerator.generateRoute,
+ initialRoute: DesktopShopInBitView.routeName,
+ ),
+ const Navigator(
+ key: Key("servicesGiftCardsDesktopKey"),
+ onGenerateRoute: RouteGenerator.generateRoute,
+ initialRoute: DesktopGiftCardsView.routeName,
+ ),
+ ];
+
+ return DesktopScaffold(
+ background: Theme.of(context).extension<StackColors>()!.background,
+ appBar: DesktopAppBar(
+ isCompactHeight: true,
+ leading: Row(
+ children: [
+ const SizedBox(width: 24, height: 24),
+ Text("Services", style: STextStyles.desktopH3(context)),
+ ],
+ ),
+ ),
+ body: Row(
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Align(
+ alignment: Alignment.topLeft,
+ child: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: 250,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ for (int i = 0; i < _labels.length; i++)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (i > 0) const SizedBox(height: 2),
+ SettingsMenuItem<int>(
+ icon: SvgPicture.asset(
+ Assets.svg.polygon,
+ width: 11,
+ height: 11,
+ color:
+ ref
+ .watch(
+ selectedServicesMenuItemStateProvider
+ .state,
+ )
+ .state ==
+ i
+ ? Theme.of(context)
+ .extension<StackColors>()!
+ .accentColorBlue
+ : Colors.transparent,
+ ),
+ label: _labels[i],
+ value: i,
+ group: ref
+ .watch(
+ selectedServicesMenuItemStateProvider
+ .state,
+ )
+ .state,
+ onChanged: (newValue) =>
+ ref
+ .read(
+ selectedServicesMenuItemStateProvider
+ .state,
+ )
+ .state =
+ newValue,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ Expanded(
+ child:
+ contentViews[ref
+ .watch(selectedServicesMenuItemStateProvider.state)
+ .state],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/services/sub_widgets/desktop_gift_cards_view.dart b/lib/pages_desktop_specific/services/sub_widgets/desktop_gift_cards_view.dart
new file mode 100644
index 0000000..8c328bd
--- /dev/null
+++ b/lib/pages_desktop_specific/services/sub_widgets/desktop_gift_cards_view.dart
@@ -0,0 +1,142 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+
+import '../../../app_config.dart';
+import '../../../pages/cakepay/cakepay_orders_view.dart';
+import '../../../pages/cakepay/cakepay_vendors_view.dart';
+import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
+import '../../../services/tor_service.dart';
+import '../../../themes/stack_colors.dart';
+import '../../../utilities/assets.dart';
+import '../../../utilities/text_styles.dart';
+import '../../../widgets/desktop/primary_button.dart';
+import '../../../widgets/desktop/secondary_button.dart';
+import '../../../widgets/rounded_white_container.dart';
+import '../../../widgets/tor_subscription.dart';
+
+class DesktopGiftCardsView extends ConsumerStatefulWidget {
+ const DesktopGiftCardsView({super.key});
+
+ static const String routeName = "/desktopGiftCardsView";
+
+ @override
+ ConsumerState<DesktopGiftCardsView> createState() =>
+ _DesktopGiftCardsViewState();
+}
+
+class _DesktopGiftCardsViewState extends ConsumerState<DesktopGiftCardsView> {
+ late bool _torEnabled;
+
+ @override
+ void initState() {
+ _torEnabled = AppConfig.hasFeature(AppFeature.tor)
+ ? ref.read(pTorService).status != TorConnectionStatus.disconnected
+ : false;
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return TorSubscription(
+ onTorStatusChanged: (status) {
+ setState(() {
+ _torEnabled = status != TorConnectionStatus.disconnected;
+ });
+ },
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(right: 30),
+ child: RoundedWhiteContainer(
+ radiusMultiplier: 2,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SvgPicture.asset(
+ Assets.svg.creditCard,
+ width: 48,
+ height: 48,
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(10),
+ child: RichText(
+ textAlign: TextAlign.start,
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: "CakePay",
+ style: STextStyles.desktopTextSmall(context),
+ ),
+ TextSpan(
+ text:
+ "\n\nPurchase gift cards with cryptocurrency.",
+ style: STextStyles.desktopTextExtraExtraSmall(
+ context,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ if (_torEnabled)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10),
+ child: Text(
+ "CakePay is not available while Tor is enabled",
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle1,
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 10,
+ vertical: 6,
+ ),
+ child: Row(
+ children: [
+ Expanded(
+ child: PrimaryButton(
+ buttonHeight: ButtonHeight.m,
+ label: "Browse Gift Cards",
+ enabled: !_torEnabled,
+ onPressed: () {
+ showDialog<void>(
+ context: context,
+ builder: (_) => const CakePayVendorsView(),
+ );
+ },
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: SecondaryButton(
+ buttonHeight: ButtonHeight.m,
+ label: "My Orders",
+ onPressed: () {
+ showDialog<void>(
+ context: context,
+ builder: (_) => const CakePayOrdersView(),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/services/sub_widgets/desktop_shopinbit_view.dart b/lib/pages_desktop_specific/services/sub_widgets/desktop_shopinbit_view.dart
new file mode 100644
index 0000000..e5c9e59
--- /dev/null
+++ b/lib/pages_desktop_specific/services/sub_widgets/desktop_shopinbit_view.dart
@@ -0,0 +1,550 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+import '../../../app_config.dart';
+import '../../../db/isar/main_db.dart';
+import '../../../models/shopinbit/shopinbit_order_model.dart';
+import '../../../notifications/show_flush_bar.dart';
+import '../../../pages/shopinbit/shopinbit_step_1.dart';
+import '../../../pages/shopinbit/shopinbit_step_2.dart';
+import '../../../pages/shopinbit/shopinbit_tickets_view.dart';
+import '../../../providers/desktop/current_desktop_menu_item.dart';
+import '../../../services/shopinbit/shopinbit_service.dart';
+import '../../../themes/stack_colors.dart';
+import '../../../utilities/assets.dart';
+import '../../../utilities/text_styles.dart';
+import '../../../widgets/desktop/desktop_dialog.dart';
+import '../../../widgets/desktop/desktop_dialog_close_button.dart';
+import '../../../widgets/desktop/primary_button.dart';
+import '../../../widgets/desktop/secondary_button.dart';
+import '../../../widgets/rounded_container.dart';
+import '../../../widgets/rounded_white_container.dart';
+import '../../../widgets/textfields/adaptive_text_field.dart';
+import '../../desktop_menu.dart';
+import '../../settings/settings_menu.dart';
+
+class DesktopShopInBitView extends ConsumerStatefulWidget {
+ const DesktopShopInBitView({super.key});
+
+ static const String routeName = "/desktopShopInBitView";
+
+ @override
+ ConsumerState<DesktopShopInBitView> createState() =>
+ _DesktopServicesViewState();
+}
+
+class _DesktopServicesViewState extends ConsumerState<DesktopShopInBitView> {
+ Future<bool> _showOpenBrowserWarning(BuildContext context, String url) async {
+ final uri = Uri.parse(url);
+ final shouldContinue = await showDialog<bool>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => DesktopDialog(
+ maxWidth: 550,
+ maxHeight: 250,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
+ child: Column(
+ children: [
+ Text("Attention", style: STextStyles.desktopH2(context)),
+ const SizedBox(height: 16),
+ Text(
+ "You are about to open "
+ "${uri.scheme}://${uri.host} "
+ "in your browser.",
+ style: STextStyles.desktopTextSmall(context),
+ ),
+ const SizedBox(height: 35),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SecondaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Cancel",
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop(false);
+ },
+ ),
+ const SizedBox(width: 20),
+ PrimaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Continue",
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop(true);
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ return shouldContinue ?? false;
+ }
+
+ void _showShopDialog(BuildContext context) async {
+ final service = ShopInBitService.instance;
+ final model = ShopInBitOrderModel();
+ bool isFirstRun = false;
+
+ if (!service.loadSetupComplete()) {
+ // First-time user: show setup.
+ final completed = await showDialog<bool>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => _ShopInBitDesktopSetupDialog(model: model),
+ );
+ if (completed != true) return; // user cancelled
+ isFirstRun = true;
+ } else {
+ // Returning user: restore display name.
+ final savedName = service.loadDisplayName();
+ if (savedName != null && savedName.isNotEmpty) {
+ model.displayName = savedName;
+ }
+ }
+
+ if (!mounted) return;
+
+ if (isFirstRun) {
+ // First run: show service overview then go directly to Step2
+ // (name was just entered in setup dialog, no need to show Step1 again).
+ showDialog<void>(
+ context: context,
+ barrierDismissible: false,
+ builder: (dialogContext) => DesktopDialog(
+ maxWidth: 550,
+ maxHeight: 300,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("ShopinBit", style: STextStyles.desktopH2(dialogContext)),
+ const SizedBox(height: 16),
+ RichText(
+ text: TextSpan(
+ style: STextStyles.desktopTextSmall(dialogContext),
+ children: const [
+ TextSpan(
+ text:
+ "Please note the following before proceeding:"
+ "\n\n\u2022 Minimum order amount: 1,000 EUR"
+ "\n\u2022 Service fee: 10% of the order total",
+ ),
+ ],
+ ),
+ ),
+ const Spacer(),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SecondaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Cancel",
+ onPressed: () {
+ Navigator.of(dialogContext, rootNavigator: true).pop();
+ },
+ ),
+ const SizedBox(width: 20),
+ PrimaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Continue",
+ onPressed: () async {
+ Navigator.of(dialogContext, rootNavigator: true).pop();
+ await showDialog<void>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => ShopInBitStep2(model: model),
+ );
+ if (mounted) setState(() {});
+ },
+ ),
+ ],
+ ),
+ const Spacer(),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SecondaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Cancel",
+ onPressed: () {
+ Navigator.of(dialogContext, rootNavigator: true).pop();
+ },
+ ),
+ const SizedBox(width: 20),
+ PrimaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Continue",
+ onPressed: () async {
+ Navigator.of(dialogContext, rootNavigator: true).pop();
+ await showDialog<void>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => ShopInBitStep1(model: model),
+ );
+ if (mounted) setState(() {});
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ } else {
+ // Returning user: go directly to Step1 (skip service overview dialog).
+ await showDialog<void>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => ShopInBitStep1(model: model),
+ );
+ if (mounted) setState(() {});
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(right: 30),
+ child: RoundedWhiteContainer(
+ radiusMultiplier: 2,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SvgPicture.asset(
+ Assets.svg.circleSliders,
+ width: 48,
+ height: 48,
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(10),
+ child: RichText(
+ textAlign: TextAlign.start,
+ text: TextSpan(
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ children: [
+ TextSpan(
+ text: "ShopinBit",
+ style: STextStyles.desktopTextSmall(context),
+ ),
+ const TextSpan(
+ text:
+ "\n\nTurn your crypto into Electronics, Flights, Hotel, "
+ "Cars or any other legal product or service... "
+ "ShopinBit is a concierge shopping service that helps "
+ "you 'live the good life with crypto'..."
+ "\n\n"
+ "Minimum order value of 1,000 EUR. "
+ "A 10% service fee applies to all orders.\n\n"
+ "By using ShopinBit, you agree to their ",
+ ),
+ TextSpan(
+ text: "Terms & Conditions",
+ style: STextStyles.richLink(
+ context,
+ ).copyWith(fontSize: 14),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () async {
+ const url =
+ "https://api.shopinbit.com/static/policy/terms.html";
+ final shouldOpen = await _showOpenBrowserWarning(
+ context,
+ url,
+ );
+ if (shouldOpen) {
+ await launchUrl(
+ Uri.parse(url),
+ mode: LaunchMode.externalApplication,
+ );
+ }
+ },
+ ),
+ const TextSpan(text: " and "),
+ TextSpan(
+ text: "Privacy Policy",
+ style: STextStyles.richLink(
+ context,
+ ).copyWith(fontSize: 14),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () async {
+ const url =
+ "https://api.shopinbit.com/static/policy/privacy.html";
+ final shouldOpen = await _showOpenBrowserWarning(
+ context,
+ url,
+ );
+ if (shouldOpen) {
+ await launchUrl(
+ Uri.parse(url),
+ mode: LaunchMode.externalApplication,
+ );
+ }
+ },
+ ),
+ const TextSpan(text: "."),
+ ],
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(10),
+ child: Row(
+ children: [
+ PrimaryButton(
+ width: 250,
+ buttonHeight: ButtonHeight.m,
+ enabled: true,
+ label: "Shop with ShopinBit",
+ onPressed: () => _showShopDialog(context),
+ ),
+ const SizedBox(width: 16),
+ Builder(
+ builder: (context) {
+ final count = MainDB.instance
+ .getShopInBitTickets()
+ .length;
+ return SecondaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.m,
+ label: count > 0
+ ? "My requests ($count)"
+ : "My requests",
+ onPressed: () async {
+ await showDialog<void>(
+ context: context,
+ builder: (_) => const ShopInBitTicketsView(),
+ );
+ if (mounted) setState(() {});
+ },
+ );
+ },
+ ),
+ const SizedBox(width: 16),
+ SecondaryButton(
+ width: 140,
+ buttonHeight: ButtonHeight.m,
+ label: "Settings",
+ onPressed: () {
+ // ShopInBit is the last settings menu item.
+ var idx = 8;
+ if (AppConfig.hasFeature(AppFeature.themeSelection)) {
+ idx++;
+ }
+ ref
+ .read(
+ selectedSettingsMenuItemStateProvider.state,
+ )
+ .state =
+ idx;
+ ref.read(currentDesktopMenuItemProvider.state).state =
+ DesktopMenuItemId.settings;
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class _ShopInBitDesktopSetupDialog extends StatefulWidget {
+ const _ShopInBitDesktopSetupDialog({required this.model});
+
+ final ShopInBitOrderModel model;
+
+ @override
+ State<_ShopInBitDesktopSetupDialog> createState() =>
+ _ShopInBitDesktopSetupDialogState();
+}
+
+class _ShopInBitDesktopSetupDialogState
+ extends State<_ShopInBitDesktopSetupDialog> {
+ late final Future<String> _keyFuture;
+ late final TextEditingController _nameController;
+
+ bool get _canContinue => _nameController.text.trim().isNotEmpty;
+
+ @override
+ void initState() {
+ super.initState();
+ _keyFuture = ShopInBitService.instance.ensureCustomerKey();
+ final existingName = ShopInBitService.instance.loadDisplayName();
+ _nameController = TextEditingController(text: existingName ?? '');
+ }
+
+ @override
+ void dispose() {
+ _nameController.dispose();
+ super.dispose();
+ }
+
+ Future<void> _completeSetup() async {
+ final name = _nameController.text.trim();
+ widget.model.displayName = name;
+ await ShopInBitService.instance.setDisplayName(name);
+ await ShopInBitService.instance.setSetupComplete(true);
+ if (mounted) {
+ Navigator.of(context, rootNavigator: true).pop(true);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final maxDialogHeight = MediaQuery.sizeOf(context).height - 64;
+ return DesktopDialog(
+ maxWidth: 580,
+ maxHeight: maxDialogHeight,
+ child: Column(
+ mainAxisSize: .min,
+ crossAxisAlignment: .stretch,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 32),
+ child: Text(
+ "ShopinBit Setup",
+ style: STextStyles.desktopH3(context),
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: Column(
+ mainAxisSize: .min,
+ crossAxisAlignment: .stretch,
+ children: [
+ const SizedBox(height: 16),
+ Text(
+ "Your Customer Key",
+ style: STextStyles.w600_20(context),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ "This is your ShopinBit customer key: save it "
+ "somewhere safe, you'll need it to recover "
+ "your ShopinBit account on a new device.",
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ ),
+ const SizedBox(height: 12),
+ FutureBuilder<String>(
+ future: _keyFuture,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState != ConnectionState.done) {
+ return const Center(child: CircularProgressIndicator());
+ }
+ if (snapshot.hasError) {
+ return RoundedContainer(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.warningBackground,
+ child: Text(
+ "Failed to generate key. Please try again.",
+ style: STextStyles.label700(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.warningForeground,
+ fontSize: 14,
+ ),
+ ),
+ );
+ }
+ final key = snapshot.data!;
+ return RoundedWhiteContainer(
+ borderColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle6,
+ child: Row(
+ children: [
+ const SizedBox(width: 10),
+ Expanded(
+ child: SelectableText(
+ key,
+ style: STextStyles.desktopTextExtraExtraSmall(
+ context,
+ ),
+ ),
+ ),
+ IconButton(
+ icon: const Icon(Icons.copy, size: 20),
+ onPressed: () {
+ Clipboard.setData(ClipboardData(text: key));
+ showFloatingFlushBar(
+ type: FlushBarType.info,
+ message: "Copied to clipboard!",
+ context: context,
+ );
+ },
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ const SizedBox(height: 24),
+ Text(
+ "Display Name",
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textFieldActiveSearchIconRight,
+ ),
+ ),
+ const SizedBox(height: 8),
+ AdaptiveTextField(
+ controller: _nameController,
+ showPasteClearButton: true,
+ maxLines: 1,
+ onChangedComprehensive: (_) => setState(() {}),
+ ),
+ const SizedBox(height: 40),
+ Row(
+ mainAxisAlignment: .end,
+ children: [
+ PrimaryButton(
+ label: "Complete Setup",
+ enabled: _canContinue,
+ onPressed: _canContinue ? _completeSetup : null,
+ horizontalContentPadding: 20,
+ ),
+ ],
+ ),
+ const SizedBox(height: 32),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 501d878..0f0fbbf 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -89,13 +89,6 @@ import 'pages/masternodes/create_masternode_view.dart';
import 'pages/masternodes/masternode_details_view.dart';
import 'pages/masternodes/masternodes_home_view.dart';
import 'pages/monkey/monkey_view.dart';
-import 'pages/cakepay/cakepay_card_detail_view.dart';
-import 'services/cakepay/src/models/card.dart';
-import 'pages/cakepay/cakepay_confirm_send_view.dart';
-import 'pages/cakepay/cakepay_order_view.dart';
-import 'pages/cakepay/cakepay_orders_view.dart';
-import 'pages/cakepay/cakepay_send_from_view.dart';
-import 'pages/cakepay/cakepay_vendors_view.dart';
import 'pages/more_view/gift_cards_view.dart';
import 'pages/more_view/services_view.dart';
import 'pages/namecoin_names/buy_domain_view.dart';
@@ -230,9 +223,6 @@ import 'pages_desktop_specific/desktop_buy/desktop_buy_view.dart';
import 'pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
import 'pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
import 'pages_desktop_specific/desktop_home_view.dart';
-import 'pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart';
-import 'pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart';
-import 'pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart';
import 'pages_desktop_specific/mweb_utxos_view.dart';
import 'pages_desktop_specific/my_stack_view/my_stack_view.dart';
import 'pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart';
@@ -251,6 +241,9 @@ import 'pages_desktop_specific/password/create_password_view.dart';
import 'pages_desktop_specific/password/delete_password_warning_view.dart';
import 'pages_desktop_specific/password/forgot_password_desktop_view.dart';
import 'pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart';
+import 'pages_desktop_specific/services/desktop_services_view.dart';
+import 'pages_desktop_specific/services/sub_widgets/desktop_gift_cards_view.dart';
+import 'pages_desktop_specific/services/sub_widgets/desktop_shopinbit_view.dart';
import 'pages_desktop_specific/settings/desktop_settings_view.dart';
import 'pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart';
import 'pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart';
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.