mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-22 06:28:39 +02:00
Cw 497 wallet connect for desktop (#1134)
* feat: Implement WalletConnect for Desktop * feat: WalletConnect for Desktop * fix: Properly handle and dispose textEditingController for URI * chore: Move BottomSheetListener to Sidebar for desktop app * Remove unused variable and imports * Update desktop_settings_page.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
8ca9fa0a82
commit
374110db54
@@ -1,10 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
@@ -21,14 +19,12 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
class DesktopDashboardPage extends StatelessWidget {
|
||||
DesktopDashboardPage({
|
||||
required this.balancePage,
|
||||
required this.bottomSheetService,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
required this.desktopKey,
|
||||
});
|
||||
|
||||
final BalancePage balancePage;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final GlobalKey<NavigatorState> desktopKey;
|
||||
@@ -40,34 +36,31 @@ class DesktopDashboardPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
return BottomSheetListener(
|
||||
bottomSheetService: bottomSheetService,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
child: balancePage,
|
||||
),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: Navigator(
|
||||
key: desktopKey,
|
||||
initialRoute: Routes.desktop_actions,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
),
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
child: balancePage,
|
||||
),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: Navigator(
|
||||
key: desktopKey,
|
||||
initialRoute: Routes.desktop_actions,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sideba
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -16,6 +18,7 @@ import 'package:cake_wallet/router.dart' as Router;
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class DesktopSidebarWrapper extends BasePage {
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Widget child;
|
||||
final DesktopSidebarViewModel desktopSidebarViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
@@ -23,6 +26,7 @@ class DesktopSidebarWrapper extends BasePage {
|
||||
|
||||
DesktopSidebarWrapper({
|
||||
required this.child,
|
||||
required this.bottomSheetService,
|
||||
required this.desktopSidebarViewModel,
|
||||
required this.dashboardViewModel,
|
||||
required this.desktopNavigatorKey,
|
||||
@@ -67,63 +71,75 @@ class DesktopSidebarWrapper extends BasePage {
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects();
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Observer(builder: (_) {
|
||||
return SideMenu(
|
||||
width: sideMenuWidth,
|
||||
topItems: [
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/wallet_outline.png',
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.dashboard,
|
||||
onTap: () {
|
||||
desktopSidebarViewModel.onPageChange(SidebarItem.dashboard);
|
||||
desktopNavigatorKey.currentState
|
||||
?.pushNamedAndRemoveUntil(Routes.desktop_actions, (route) => false);
|
||||
},
|
||||
),
|
||||
SideMenuItem(
|
||||
onTap: () {
|
||||
if (desktopSidebarViewModel.currentPage == SidebarItem.transactions) {
|
||||
return BottomSheetListener(
|
||||
bottomSheetService: bottomSheetService,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Observer(builder: (_) {
|
||||
return SideMenu(
|
||||
width: sideMenuWidth,
|
||||
topItems: [
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/wallet_outline.png',
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.dashboard,
|
||||
onTap: () {
|
||||
desktopSidebarViewModel.onPageChange(SidebarItem.dashboard);
|
||||
desktopNavigatorKey.currentState
|
||||
?.pushNamedAndRemoveUntil(Routes.desktop_actions, (route) => false);
|
||||
desktopSidebarViewModel.resetSidebar();
|
||||
} else {
|
||||
desktopSidebarViewModel.onPageChange(SidebarItem.transactions);
|
||||
desktopNavigatorKey.currentState?.pushNamed(Routes.transactionsPage);
|
||||
}
|
||||
},
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.transactions,
|
||||
imagePath: desktopSidebarViewModel.currentPage == SidebarItem.transactions
|
||||
? selectedIconPath
|
||||
: unselectedIconPath,
|
||||
),
|
||||
],
|
||||
bottomItems: [
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/support_icon.png',
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.support,
|
||||
onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.support)),
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/settings_outline.png',
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.settings,
|
||||
onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.settings),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Expanded(
|
||||
child: PageView(
|
||||
controller: pageController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
child,
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Navigator(
|
||||
initialRoute: Routes.support,
|
||||
},
|
||||
),
|
||||
SideMenuItem(
|
||||
onTap: () {
|
||||
if (desktopSidebarViewModel.currentPage == SidebarItem.transactions) {
|
||||
desktopNavigatorKey.currentState
|
||||
?.pushNamedAndRemoveUntil(Routes.desktop_actions, (route) => false);
|
||||
desktopSidebarViewModel.resetSidebar();
|
||||
} else {
|
||||
desktopSidebarViewModel.onPageChange(SidebarItem.transactions);
|
||||
desktopNavigatorKey.currentState?.pushNamed(Routes.transactionsPage);
|
||||
}
|
||||
},
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.transactions,
|
||||
imagePath: desktopSidebarViewModel.currentPage == SidebarItem.transactions
|
||||
? selectedIconPath
|
||||
: unselectedIconPath,
|
||||
),
|
||||
],
|
||||
bottomItems: [
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/support_icon.png',
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.support,
|
||||
onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.support)),
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/settings_outline.png',
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.settings,
|
||||
onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.settings),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Expanded(
|
||||
child: PageView(
|
||||
controller: pageController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
child,
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Navigator(
|
||||
initialRoute: Routes.support,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
Navigator(
|
||||
initialRoute: Routes.desktop_settings_page,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
@@ -131,20 +147,11 @@ class DesktopSidebarWrapper extends BasePage {
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
Navigator(
|
||||
initialRoute: Routes.desktop_settings_page,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class ConnectionSyncPage extends BasePage {
|
||||
);
|
||||
},
|
||||
),
|
||||
if (dashboardViewModel.wallet.type == WalletType.ethereum && DeviceInfo.instance.isMobile) ...[
|
||||
if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
|
||||
WalletConnectTile(
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(
|
||||
|
||||
@@ -12,6 +12,7 @@ final _settingsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
class DesktopSettingsPage extends StatefulWidget {
|
||||
const DesktopSettingsPage({super.key});
|
||||
|
||||
|
||||
@override
|
||||
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ import 'dart:developer';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
@@ -36,7 +38,13 @@ class WCPairingsWidget extends BasePage {
|
||||
String get title => S.current.walletConnect;
|
||||
|
||||
Future<void> _onScanQrCode(BuildContext context, Web3Wallet web3Wallet) async {
|
||||
final String? uri = await presentQRScanner();
|
||||
final String? uri;
|
||||
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
uri = await presentQRScanner();
|
||||
} else {
|
||||
uri = await _showEnterWalletConnectURIPopUp(context);
|
||||
}
|
||||
|
||||
if (uri == null) return _invalidUriToast(context, S.current.nullURIError);
|
||||
|
||||
@@ -51,6 +59,16 @@ class WCPairingsWidget extends BasePage {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _showEnterWalletConnectURIPopUp(BuildContext context) async {
|
||||
final walletConnectURI = await showPopUp<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return EnterWalletConnectURIWrapperWidget();
|
||||
},
|
||||
);
|
||||
return walletConnectURI;
|
||||
}
|
||||
|
||||
Future<void> _invalidUriToast(BuildContext context, String message) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class EnterWalletConnectURIWrapperWidget extends StatefulWidget {
|
||||
const EnterWalletConnectURIWrapperWidget({super.key});
|
||||
|
||||
@override
|
||||
State<EnterWalletConnectURIWrapperWidget> createState() =>
|
||||
_EnterWallectConnectURIWrapperWidgetState();
|
||||
}
|
||||
|
||||
class _EnterWallectConnectURIWrapperWidgetState extends State<EnterWalletConnectURIWrapperWidget> {
|
||||
late final TextEditingController controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _EnterWalletConnectURIWidget(
|
||||
controller: controller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EnterWalletConnectURIWidget extends BaseAlertDialog {
|
||||
_EnterWalletConnectURIWidget({
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
String get titleText => S.current.enterWalletConnectURI;
|
||||
|
||||
Future<void> _pasteWalletConnectURI() async {
|
||||
final clipboard = await Clipboard.getData('text/plain');
|
||||
final totpURI = clipboard?.text ?? '';
|
||||
|
||||
if (totpURI.isNotEmpty) {
|
||||
controller.text = totpURI;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget content(BuildContext context) {
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
S.current.copyWalletConnectLink,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: controller,
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
padding: EdgeInsets.only(top: 0),
|
||||
child: Semantics(
|
||||
label: S.of(context).paste,
|
||||
child: InkWell(
|
||||
onTap: () => _pasteWalletConnectURI(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/images/paste_ios.png',
|
||||
color:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
hintText: S.current.enterWalletConnectURI,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
),
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget actionButtons(BuildContext context) {
|
||||
return Container(
|
||||
width: 300,
|
||||
height: 52,
|
||||
padding: EdgeInsets.only(left: 12, right: 12),
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.infinity,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, controller.text);
|
||||
},
|
||||
child: Text(
|
||||
S.current.confirm,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).primaryColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user