WIP desktop restore ui and wallets overview layout

This commit is contained in:
julian 2022-09-18 19:27:25 -06:00
parent 6c436b5103
commit 340cb3ccc3
7 changed files with 705 additions and 382 deletions

View file

@ -29,6 +29,7 @@ import 'package:stackwallet/pages/loading_view.dart';
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart';
import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart';
import 'package:stackwallet/providers/exchange/change_now_provider.dart';
@ -603,6 +604,14 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
ref.read(prefsChangeNotifierProvider).startupWalletId;
}
// TODO proper desktop auth view
if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
Future<void>.delayed(Duration.zero).then((value) =>
Navigator.of(context).pushNamedAndRemoveUntil(
DesktopHomeView.routeName, (route) => false));
return Container();
}
return LockscreenView(
isInitialAppLogin: true,
routeOnSuccess: HomeView.routeName,

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart';
@ -13,6 +15,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
@ -35,9 +39,9 @@ class RestoreOptionsView extends ConsumerStatefulWidget {
class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
late final String walletName;
late final Coin coin;
late final bool isDesktop;
late TextEditingController _dateController;
late TextEditingController _lengthController;
late FocusNode textFieldFocusNode;
final bool _nextEnabled = true;
@ -47,9 +51,9 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
void initState() {
walletName = widget.walletName;
coin = widget.coin;
isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
_dateController = TextEditingController();
_lengthController = TextEditingController();
textFieldFocusNode = FocusNode();
super.initState();
@ -58,7 +62,6 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
@override
void dispose() {
_dateController.dispose();
_lengthController.dispose();
textFieldFocusNode.dispose();
super.dispose();
}
@ -122,26 +125,225 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
);
}
Future<void> nextPressed() async {
if (!isDesktop) {
// hide keyboard if has focus
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
}
if (mounted) {
await Navigator.of(context).pushNamed(
RestoreWalletView.routeName,
arguments: Tuple4(
walletName,
coin,
ref.read(mnemonicWordCountStateProvider.state).state,
_restoreFromDate,
),
);
}
}
Future<void> chooseDate() async {
final height = MediaQuery.of(context).size.height;
// check and hide keyboard
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 125));
}
final date = await showRoundedDatePicker(
context: context,
initialDate: DateTime.now(),
height: height * 0.5,
theme: ThemeData(
primarySwatch: CFColors.createMaterialColor(CFColors.stackAccent),
),
//TODO pick a better initial date
// 2007 chosen as that is just before bitcoin launched
firstDate: DateTime(2007),
lastDate: DateTime.now(),
borderRadius: Constants.size.circularBorderRadius * 2,
textPositiveButton: "SELECT",
styleDatePicker: _buildDatePickerStyle(),
styleYearPicker: _buildYearPickerStyle(),
);
if (date != null) {
_restoreFromDate = date;
_dateController.text = Format.formatDate(date);
}
}
Future<void> chooseMnemonicLength() async {
await showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) {
return MnemonicWordCountSelectSheet(
lengthOptions: Constants.possibleLengthsForCoin(coin),
);
},
);
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType with ${coin.name} $walletName");
return Scaffold(
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
if (textFieldFocusNode.hasFocus) {
textFieldFocusNode.unfocus();
Future<void>.delayed(const Duration(milliseconds: 100))
.then((value) => Navigator.of(context).pop());
} else {
Navigator.of(context).pop();
}
},
return DesktopScaffold(
appBar: isDesktop
? const DesktopAppBar(
isCompactHeight: false,
leading: AppBarBackButton(),
)
: AppBar(
leading: AppBarBackButton(
onPressed: () {
if (textFieldFocusNode.hasFocus) {
textFieldFocusNode.unfocus();
Future<void>.delayed(const Duration(milliseconds: 100))
.then((value) => Navigator.of(context).pop());
} else {
Navigator.of(context).pop();
}
},
),
),
body: PlatformRestoreOptionsLayout(
isDesktop: isDesktop,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (!isDesktop)
const Spacer(
flex: 1,
),
if (!isDesktop)
Image(
image: AssetImage(
Assets.png.imageFor(coin: coin),
),
height: 100,
),
SizedBox(
height: isDesktop ? 24 : 16,
),
Text(
"Restore options",
textAlign: TextAlign.center,
style:
isDesktop ? STextStyles.desktopH2 : STextStyles.pageTitleH1,
),
SizedBox(
height: isDesktop ? 40 : 24,
),
if (coin == Coin.monero || coin == Coin.epicCash)
Text(
"Choose start date",
style: isDesktop
? STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textFieldActiveSearchIconRight,
)
: STextStyles.smallMed12,
textAlign: TextAlign.left,
),
if (coin == Coin.monero || coin == Coin.epicCash)
SizedBox(
height: isDesktop ? 16 : 8,
),
if (coin == Coin.monero || coin == Coin.epicCash)
// if (!isDesktop)
RestoreFromDatePicker(
onTap: chooseDate,
),
// if (isDesktop)
// // TODO desktop date picker
if (coin == Coin.monero || coin == Coin.epicCash)
const SizedBox(
height: 8,
),
if (coin == Coin.monero || coin == Coin.epicCash)
RoundedWhiteContainer(
child: Center(
child: Text(
"Choose the date you made the wallet (approximate is fine)",
style: isDesktop
? STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textSubtitle1,
)
: STextStyles.smallMed12.copyWith(
fontSize: 10,
),
),
),
),
if (coin == Coin.monero || coin == Coin.epicCash)
SizedBox(
height: isDesktop ? 24 : 16,
),
Text(
"Choose recovery phrase length",
style: isDesktop
? STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textFieldActiveSearchIconRight,
)
: STextStyles.smallMed12,
textAlign: TextAlign.left,
),
SizedBox(
height: isDesktop ? 16 : 8,
),
MobileMnemonicLengthSelector(
chooseMnemonicLength: chooseMnemonicLength,
),
if (!isDesktop)
const Spacer(
flex: 3,
),
if (isDesktop)
const SizedBox(
height: 32,
),
RestoreNextButton(
isDesktop: isDesktop,
onPressed: _nextEnabled ? nextPressed : null,
),
],
),
),
body: Container(
color: CFColors.almostWhite,
);
}
}
class PlatformRestoreOptionsLayout extends StatelessWidget {
const PlatformRestoreOptionsLayout({
Key? key,
required this.isDesktop,
required this.child,
}) : super(key: key);
final bool isDesktop;
final Widget child;
@override
Widget build(BuildContext context) {
if (isDesktop) {
return Container();
} else {
return Container(
color: CFColors.background,
child: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
@ -150,259 +352,162 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(
flex: 1,
),
Image(
image: AssetImage(
Assets.png.imageFor(coin: coin),
),
height: 100,
),
const SizedBox(
height: 16,
),
Text(
"Restore options",
textAlign: TextAlign.center,
style: STextStyles.pageTitleH1,
),
const SizedBox(
height: 24,
),
if (coin == Coin.monero || coin == Coin.epicCash)
Text(
"Choose start date",
style: STextStyles.smallMed12,
textAlign: TextAlign.left,
),
if (coin == Coin.monero || coin == Coin.epicCash)
const SizedBox(
height: 8,
),
if (coin == Coin.monero || coin == Coin.epicCash)
Container(
color: Colors.transparent,
child: TextField(
onTap: () async {
final height =
MediaQuery.of(context).size.height;
// check and hide keyboard
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 125));
}
final date = await showRoundedDatePicker(
context: context,
initialDate: DateTime.now(),
height: height * 0.5,
theme: ThemeData(
primarySwatch: CFColors.createMaterialColor(
CFColors.stackAccent),
),
//TODO pick a better initial date
// 2007 chosen as that is just before bitcoin launched
firstDate: DateTime(2007),
lastDate: DateTime.now(),
borderRadius:
Constants.size.circularBorderRadius * 2,
textPositiveButton: "SELECT",
styleDatePicker: _buildDatePickerStyle(),
styleYearPicker: _buildYearPickerStyle(),
);
if (date != null) {
_restoreFromDate = date;
_dateController.text =
Format.formatDate(date);
}
},
controller: _dateController,
style: STextStyles.field,
decoration: InputDecoration(
hintText: "Restore from...",
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.calendar,
color: CFColors.neutral50,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key("restoreOptionsViewDatePickerKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {},
),
),
if (coin == Coin.monero || coin == Coin.epicCash)
const SizedBox(
height: 8,
),
if (coin == Coin.monero || coin == Coin.epicCash)
RoundedWhiteContainer(
child: Center(
child: Text(
"Choose the date you made the wallet (approximate is fine)",
style: STextStyles.smallMed12.copyWith(
fontSize: 10,
),
),
),
),
if (coin == Coin.monero || coin == Coin.epicCash)
const SizedBox(
height: 16,
),
Text(
"Choose recovery phrase length",
style: STextStyles.smallMed12,
textAlign: TextAlign.left,
),
const SizedBox(
height: 8,
),
Stack(
children: [
TextField(
controller: _lengthController,
readOnly: true,
textInputAction: TextInputAction.none,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
child: RawMaterialButton(
splashColor: CFColors.splashLight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) {
return MnemonicWordCountSelectSheet(
lengthOptions:
Constants.possibleLengthsForCoin(
coin),
);
},
);
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"${ref.watch(mnemonicWordCountStateProvider.state).state} words",
style: STextStyles.itemSubtitle12,
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 8,
height: 4,
color: CFColors.gray3,
),
],
),
),
)
],
),
const Spacer(
flex: 3,
),
TextButton(
onPressed: _nextEnabled
? () async {
// hide keyboard if has focus
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pushNamed(
RestoreWalletView.routeName,
arguments: Tuple4(
walletName,
coin,
ref
.read(mnemonicWordCountStateProvider
.state)
.state,
_restoreFromDate,
),
);
}
}
: null,
style: _nextEnabled
? Theme.of(context)
.textButtonTheme
.style
?.copyWith(
backgroundColor:
MaterialStateProperty.all<Color>(
CFColors.stackAccent,
),
)
: Theme.of(context)
.textButtonTheme
.style
?.copyWith(
backgroundColor:
MaterialStateProperty.all<Color>(
CFColors.stackAccent.withOpacity(
0.25,
),
),
),
child: Text(
"Next",
style: STextStyles.button,
),
),
],
),
child: child,
),
),
);
},
),
),
);
}
}
}
class RestoreFromDatePicker extends StatefulWidget {
const RestoreFromDatePicker({Key? key, required this.onTap})
: super(key: key);
final VoidCallback onTap;
@override
State<RestoreFromDatePicker> createState() => _RestoreFromDatePickerState();
}
class _RestoreFromDatePickerState extends State<RestoreFromDatePicker> {
late final TextEditingController _dateController;
late final VoidCallback onTap;
@override
void initState() {
onTap = widget.onTap;
_dateController = TextEditingController();
super.initState();
}
@override
void dispose() {
_dateController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
child: TextField(
onTap: onTap,
controller: _dateController,
style: STextStyles.field,
decoration: InputDecoration(
hintText: "Restore from...",
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.calendar,
color: CFColors.neutral50,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key("restoreOptionsViewDatePickerKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {},
),
);
}
}
class MobileMnemonicLengthSelector extends ConsumerWidget {
const MobileMnemonicLengthSelector({
Key? key,
required this.chooseMnemonicLength,
}) : super(key: key);
final VoidCallback chooseMnemonicLength;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Stack(
children: [
const TextField(
// controller: _lengthController,
readOnly: true,
textInputAction: TextInputAction.none,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
child: RawMaterialButton(
splashColor: CFColors.splashLight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: chooseMnemonicLength,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${ref.watch(mnemonicWordCountStateProvider.state).state} words",
style: STextStyles.itemSubtitle12,
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 8,
height: 4,
color: CFColors.gray3,
),
],
),
),
)
],
);
}
}
class RestoreNextButton extends StatelessWidget {
const RestoreNextButton({Key? key, required this.isDesktop, this.onPressed})
: super(key: key);
final bool isDesktop;
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: isDesktop ? 70 : 0,
),
child: TextButton(
onPressed: onPressed,
style: onPressed != null
? CFColors.getPrimaryEnabledButtonColor(context)
: CFColors.getPrimaryDisabledButtonColor(context),
child: Text(
"Next",
style: STextStyles.button,
),
),
);
}

View file

@ -16,6 +16,7 @@ import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widge
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart';
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/coins/manager.dart';
@ -66,6 +67,7 @@ class RestoreWalletView extends ConsumerStatefulWidget {
class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
final _formKey = GlobalKey<FormState>();
late final int _seedWordCount;
late final bool isDesktop;
final HashSet<String> _wordListHashSet = HashSet.from(bip39wordlist.WORDLIST);
final ScrollController controller = ScrollController();
@ -85,13 +87,13 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
final text = data!.text!.trim();
if (text.isEmpty || _controllers.isEmpty) {
delegate.pasteText(SelectionChangedCause.toolbar);
unawaited(delegate.pasteText(SelectionChangedCause.toolbar));
return;
}
final words = text.split(" ");
if (words.isEmpty) {
delegate.pasteText(SelectionChangedCause.toolbar);
unawaited(delegate.pasteText(SelectionChangedCause.toolbar));
return;
}
@ -115,6 +117,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
@override
void initState() {
_seedWordCount = widget.seedWordsLength;
isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
textSelectionControls = Platform.isIOS
? CustomCupertinoTextSelectionControls(onPaste: onControlsPaste)
@ -190,11 +193,11 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
// TODO: do actual check to make sure it is a valid mnemonic for monero
if (bip39.validateMnemonic(mnemonic) == false &&
!(widget.coin == Coin.monero)) {
showFloatingFlushBar(
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid seed phrase!",
context: context,
);
));
} else {
if (!Platform.isLinux) Wakelock.enable();
final walletsService = ref.read(walletsServiceChangeNotifierProvider);
@ -206,7 +209,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
);
bool isRestoring = true;
// show restoring in progress
showDialog<dynamic>(
unawaited(showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: false,
@ -225,7 +228,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
},
);
},
);
));
var node = ref
.read(nodeServiceChangeNotifierProvider)
@ -233,7 +236,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
if (node == null) {
node = DefaultNodes.getNodeFor(widget.coin);
ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor(
await ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor(
coin: widget.coin,
node: node,
);
@ -282,26 +285,31 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
.addWallet(walletId: manager.walletId, manager: manager);
if (mounted) {
Navigator.of(context).pushNamedAndRemoveUntil(
HomeView.routeName, (route) => false);
if (isDesktop) {
Navigator.of(context)
.popUntil(ModalRoute.withName(DesktopHomeView.routeName));
} else {
unawaited(Navigator.of(context).pushNamedAndRemoveUntil(
HomeView.routeName, (route) => false));
}
}
showDialog<dynamic>(
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return const RestoreSucceededDialog();
},
).then(
(_) {
if (!Platform.isLinux) Wakelock.disable();
// timer.cancel();
},
);
if (!Platform.isLinux && !isDesktop) {
await Wakelock.disable();
}
}
} catch (e) {
if (!Platform.isLinux) Wakelock.disable();
if (!Platform.isLinux && !isDesktop) {
await Wakelock.disable();
}
// if (e is HiveError &&
// e.message == "Box has already been closed.") {
@ -316,7 +324,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
Navigator.pop(context);
// show restoring wallet failed dialog
showDialog<dynamic>(
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
@ -331,7 +339,9 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
}
}
if (!Platform.isLinux) Wakelock.disable();
if (!Platform.isLinux && !isDesktop) {
await Wakelock.disable();
}
}
}
}
@ -441,8 +451,71 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
});
}
controller.animateTo(controller.position.maxScrollExtent,
duration: const Duration(milliseconds: 300), curve: Curves.decelerate);
if (!isDesktop) {
controller.animateTo(
controller.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
);
}
}
Future<void> scanMnemonicQr() async {
try {
final qrResult = await scanner.scan();
final results = AddressUtils.decodeQRSeedData(qrResult.rawContent);
Logging.instance.log("scan parsed: $results", level: LogLevel.Info);
if (results["mnemonic"] != null) {
final list = (results["mnemonic"] as List)
.map((value) => value as String)
.toList(growable: false);
if (list.isNotEmpty) {
_clearAndPopulateMnemonic(list);
Logging.instance.log("mnemonic populated", level: LogLevel.Info);
} else {
Logging.instance
.log("mnemonic failed to populate", level: LogLevel.Info);
}
}
} on PlatformException catch (e) {
// likely failed to get camera permissions
Logging.instance
.log("Restore wallet qr scan failed: $e", level: LogLevel.Warning);
}
}
Future<void> pasteMnemonic() async {
debugPrint("restoreWalletPasteButton tapped");
final ClipboardData? data =
await widget.clipboard.getData(Clipboard.kTextPlain);
if (data?.text != null && data!.text!.isNotEmpty) {
final content = data.text!.trim();
final list = content.split(" ");
_clearAndPopulateMnemonic(list);
}
}
Future<void> requestRestore() async {
// wait for keyboard to disappear
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 100),
);
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return ConfirmRecoveryDialog(
onConfirm: attemptRestore,
);
},
);
}
@override
@ -479,35 +552,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
height: 20,
color: CFColors.stackAccent,
),
onPressed: () async {
try {
final qrResult = await scanner.scan();
final results =
AddressUtils.decodeQRSeedData(qrResult.rawContent);
Logging.instance
.log("scan parsed: $results", level: LogLevel.Info);
if (results["mnemonic"] != null) {
final list = (results["mnemonic"] as List)
.map((value) => value as String)
.toList(growable: false);
if (list.isNotEmpty) {
_clearAndPopulateMnemonic(list);
Logging.instance
.log("mnemonic populated", level: LogLevel.Info);
} else {
Logging.instance.log("mnemonic failed to populate",
level: LogLevel.Info);
}
}
} on PlatformException catch (e) {
// likely failed to get camera permissions
Logging.instance.log("Restore wallet qr scan failed: $e",
level: LogLevel.Warning);
}
},
onPressed: scanMnemonicQr,
),
),
),
@ -529,17 +574,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
height: 20,
color: CFColors.stackAccent,
),
onPressed: () async {
debugPrint("restoreWalletPasteButton tapped");
final ClipboardData? data =
await widget.clipboard.getData(Clipboard.kTextPlain);
if (data?.text != null && data!.text!.isNotEmpty) {
final content = data.text!.trim();
final list = content.split(" ");
_clearAndPopulateMnemonic(list);
}
},
onPressed: pasteMnemonic,
),
),
),
@ -641,66 +676,14 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
)
],
),
// if (widget.coin == Coin.monero ||
// widget.coin == Coin.epicCash)
// Padding(
// padding: const EdgeInsets.only(
// top: 8.0,
// ),
// child: ClipRRect(
// borderRadius: BorderRadius.circular(
// Constants.size.circularBorderRadius,
// ),
// child: TextField(
// key: Key("restoreMnemonicFormField_height"),
// inputFormatters: <TextInputFormatter>[
// FilteringTextInputFormatter.allow(
// RegExp("[0-9]*")),
// ],
// keyboardType:
// TextInputType.numberWithOptions(),
// controller: _heightController,
// focusNode: _heightFocusNode,
// style: STextStyles.field,
// decoration: standardInputDecoration(
// "Height",
// _heightFocusNode,
// ),
// ),
// ),
// ),
Padding(
padding: const EdgeInsets.only(
top: 8.0,
),
child: TextButton(
style: Theme.of(context)
.textButtonTheme
.style
?.copyWith(
backgroundColor:
MaterialStateProperty.all<Color>(
CFColors.stackAccent,
),
),
onPressed: () async {
// wait for keyboard to disappear
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 100),
);
showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return ConfirmRecoveryDialog(
onConfirm: attemptRestore,
);
},
);
},
style: CFColors.getPrimaryEnabledButtonColor(
context),
onPressed: requestRestore,
child: Text(
"Restore",
style: STextStyles.button,

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_wallets.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
@ -53,7 +54,7 @@ class _MyStackViewState extends ConsumerState<MyStackView> {
),
),
Expanded(
child: hasWallets ? Container() : const EmptyWallets(),
child: hasWallets ? const MyWallets() : const EmptyWallets(),
),
],
);

View file

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_table.dart';
import 'package:stackwallet/utilities/cfcolors.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
class MyWallets extends StatefulWidget {
const MyWallets({Key? key}) : super(key: key);
@override
State<MyWallets> createState() => _MyWalletsState();
}
class _MyWalletsState extends State<MyWallets> {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.greenAccent,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Favorite wallets",
style: STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textFieldActiveSearchIconRight,
),
),
const SizedBox(
height: 20,
),
// TODO favorites grid
Container(
color: Colors.deepPurpleAccent,
height: 210,
),
const SizedBox(
height: 40,
),
Row(
children: [
Text(
"All wallets",
style: STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textFieldActiveSearchIconRight,
),
),
const Spacer(),
BlueTextButton(
text: "Add new wallet",
onTap: () {
// TODO add wallet
},
),
],
),
const SizedBox(
height: 20,
),
const WalletTable(),
],
),
),
);
}
}

View file

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/utilities/cfcolors.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
class WalletTable extends ConsumerStatefulWidget {
const WalletTable({Key? key}) : super(key: key);
@override
ConsumerState<WalletTable> createState() => _WalletTableState();
}
class _WalletTableState extends ConsumerState<WalletTable> {
void tapRow(int index) {
print("row $index clicked");
}
TableRow getRowForCoin(
int index,
Map<Coin, List<ChangeNotifierProvider<Manager>>> providersByCoin,
) {
final coin = providersByCoin.keys.toList(growable: false)[index];
final walletCount = providersByCoin[coin]!.length;
final walletCountString =
walletCount == 1 ? "$walletCount wallet" : "$walletCount wallets";
return TableRow(
children: [
GestureDetector(
onTap: () {
tapRow(index);
},
child: Container(
decoration: BoxDecoration(
color: CFColors.background,
),
child: Row(
children: [
// logo/icon
const SizedBox(
width: 10,
),
Text(
coin.prettyName,
style: STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textDark,
),
)
],
),
),
),
GestureDetector(
onTap: () {
tapRow(index);
},
child: Container(
decoration: BoxDecoration(
color: CFColors.background,
),
child: Text(
walletCountString,
style: STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textSubtitle1,
),
),
),
),
GestureDetector(
onTap: () {
tapRow(index);
},
child: Container(
decoration: BoxDecoration(
color: CFColors.background,
),
child: PriceInfoRow(coin: coin),
),
),
],
);
}
@override
Widget build(BuildContext context) {
final providersByCoin = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManagerProvidersByCoin()));
return Table(
border: TableBorder.all(),
columnWidths: const <int, TableColumnWidth>{
0: FlexColumnWidth(1),
1: FlexColumnWidth(1.25),
2: FlexColumnWidth(1.75),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
for (int i = 0; i < providersByCoin.length; i++)
getRowForCoin(i, providersByCoin)
]);
}
}
class PriceInfoRow extends ConsumerWidget {
const PriceInfoRow({Key? key, required this.coin}) : super(key: key);
final Coin coin;
@override
Widget build(BuildContext context, WidgetRef ref) {
final tuple = ref.watch(priceAnd24hChangeNotifierProvider
.select((value) => value.getPrice(coin)));
final currency = ref
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
final priceString = Format.localizedStringAsFixed(
value: tuple.item1,
locale: ref.watch(localeServiceChangeNotifierProvider.notifier).locale,
decimalPlaces: 2,
);
final double percentChange = tuple.item2;
var percentChangedColor = CFColors.stackAccent;
if (percentChange > 0) {
percentChangedColor = CFColors.stackGreen;
} else if (percentChange < 0) {
percentChangedColor = CFColors.stackRed;
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"$priceString $currency/${coin.ticker}",
style: STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textSubtitle1,
),
),
Text(
"${percentChange.toStringAsFixed(2)}%",
style: STextStyles.desktopTextExtraSmall.copyWith(
color: percentChangedColor,
),
),
],
);
}
}

View file

@ -170,6 +170,7 @@ abstract class CFColors {
static const Color buttonBackPrimaryDisabled = Color(0xFFD7D7D7);
static const Color textFieldDefaultSearchIconLeft = Color(0xFFA9ACAC);
static const Color textFieldActiveSearchIconRight = Color(0xFF747778);
// button color themes
@ -186,8 +187,7 @@ abstract class CFColors {
),
);
static ButtonStyle? getSecondaryEnabledButtonColor(
BuildContext context) =>
static ButtonStyle? getSecondaryEnabledButtonColor(BuildContext context) =>
Theme.of(context).textButtonTheme.style?.copyWith(
backgroundColor: MaterialStateProperty.all<Color>(
CFColors.buttonBackSecondary,