Merge pull request #225 from cypherstack/desktop

Desktop
This commit is contained in:
julian-CStack 2022-11-15 21:03:41 -06:00 committed by GitHub
commit 4fb35be79f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2624 additions and 1897 deletions

View file

@ -0,0 +1,11 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#E0E3E3"/>
<g clip-path="url(#clip0_5813_29086)">
<path d="M13.5 30.5625C13.5 29.8365 14.0878 29.25 14.8125 29.25H17.0544C17.5605 28.0893 18.7172 27.2812 20.0625 27.2812C21.4078 27.2812 22.5275 28.0893 23.0689 29.25H33.1875C33.9135 29.25 34.5 29.8365 34.5 30.5625C34.5 31.2885 33.9135 31.875 33.1875 31.875H23.0689C22.5275 33.0357 21.4078 33.8438 20.0625 33.8438C18.7172 33.8438 17.5605 33.0357 17.0544 31.875H14.8125C14.0878 31.875 13.5 31.2885 13.5 30.5625ZM21.375 30.5625C21.375 29.8365 20.7885 29.25 20.0625 29.25C19.3365 29.25 18.75 29.8365 18.75 30.5625C18.75 31.2885 19.3365 31.875 20.0625 31.875C20.7885 31.875 21.375 31.2885 21.375 30.5625ZM27.9375 20.7188C29.2828 20.7188 30.4025 21.5268 30.9439 22.6875H33.1875C33.9135 22.6875 34.5 23.274 34.5 24C34.5 24.726 33.9135 25.3125 33.1875 25.3125H30.9439C30.4025 26.4732 29.2828 27.2812 27.9375 27.2812C26.5922 27.2812 25.4355 26.4732 24.9311 25.3125H14.8125C14.0878 25.3125 13.5 24.726 13.5 24C13.5 23.274 14.0878 22.6875 14.8125 22.6875H24.9311C25.4355 21.5268 26.5922 20.7188 27.9375 20.7188ZM29.25 24C29.25 23.274 28.6635 22.6875 27.9375 22.6875C27.2115 22.6875 26.625 23.274 26.625 24C26.625 24.726 27.2115 25.3125 27.9375 25.3125C28.6635 25.3125 29.25 24.726 29.25 24ZM33.1875 16.125C33.9135 16.125 34.5 16.7128 34.5 17.4375C34.5 18.1635 33.9135 18.75 33.1875 18.75H24.3814C23.84 19.9107 22.7203 20.7188 21.375 20.7188C20.0297 20.7188 18.873 19.9107 18.3686 18.75H14.8125C14.0878 18.75 13.5 18.1635 13.5 17.4375C13.5 16.7128 14.0878 16.125 14.8125 16.125H18.3686C18.873 14.9663 20.0297 14.1562 21.375 14.1562C22.7203 14.1562 23.84 14.9663 24.3814 16.125H33.1875ZM20.0625 17.4375C20.0625 18.1635 20.649 18.75 21.375 18.75C22.101 18.75 22.6875 18.1635 22.6875 17.4375C22.6875 16.7128 22.101 16.125 21.375 16.125C20.649 16.125 20.0625 16.7128 20.0625 17.4375Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_5813_29086">
<rect width="21" height="21" fill="white" transform="translate(13.5 13.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -76,7 +76,7 @@ void main() async {
if (Util.isDesktop) { if (Util.isDesktop) {
setWindowTitle('Stack Wallet'); setWindowTitle('Stack Wallet');
setWindowMinSize(const Size(1200, 900)); setWindowMinSize(const Size(1200, 1100));
setWindowMaxSize(Size.infinite); setWindowMaxSize(Size.infinite);
} }

View file

@ -13,7 +13,9 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/address_book_card.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
@ -21,8 +23,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class AddressBookView extends ConsumerStatefulWidget { class AddressBookView extends ConsumerStatefulWidget {
const AddressBookView({Key? key, this.coin}) : super(key: key); const AddressBookView({Key? key, this.coin}) : super(key: key);
@ -103,288 +103,302 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
final addressBookEntriesFuture = ref.watch( final addressBookEntriesFuture = ref.watch(
addressBookServiceProvider.select((value) => value.addressBookEntries)); addressBookServiceProvider.select((value) => value.addressBookEntries));
return Scaffold( final isDesktop = Util.isDesktop;
backgroundColor: Theme.of(context).extension<StackColors>()!.background, return ConditionalParent(
appBar: AppBar( condition: !isDesktop,
leading: AppBarBackButton( builder: (child) {
onPressed: () { return Scaffold(
Navigator.of(context).pop(); backgroundColor:
}, Theme.of(context).extension<StackColors>()!.background,
), appBar: AppBar(
title: Text( leading: AppBarBackButton(
"Address book", onPressed: () {
style: STextStyles.navBarTitle(context), Navigator.of(context).pop();
), },
actions: [
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
), ),
child: AspectRatio( title: Text(
aspectRatio: 1, "Address book",
child: AppBarIconButton( style: STextStyles.navBarTitle(context),
key: const Key("addressBookFilterViewButton"), ),
size: 36, actions: [
shadows: const [], Padding(
color: Theme.of(context).extension<StackColors>()!.background, padding: const EdgeInsets.only(
icon: SvgPicture.asset( top: 10,
Assets.svg.filter, bottom: 10,
color: Theme.of(context) right: 10,
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
), ),
onPressed: () { child: AspectRatio(
Navigator.of(context).pushNamed( aspectRatio: 1,
AddressBookFilterView.routeName, child: AppBarIconButton(
); key: const Key("addressBookFilterViewButton"),
}, size: 36,
), shadows: const [],
), color:
), Theme.of(context).extension<StackColors>()!.background,
Padding( icon: SvgPicture.asset(
padding: const EdgeInsets.only( Assets.svg.filter,
top: 10, color: Theme.of(context)
bottom: 10, .extension<StackColors>()!
right: 10, .accentColorDark,
), width: 20,
child: AspectRatio( height: 20,
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("addressBookAddNewContactViewButton"),
size: 36,
shadows: const [],
color: Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
Navigator.of(context).pushNamed(
AddAddressBookEntryView.routeName,
);
},
),
),
),
],
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (value) {
setState(() {
_searchTerm = value;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 16,
),
Text(
"Favorites",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: addressBookEntriesFuture,
builder: (_, AsyncSnapshot<List<Contact>> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
_cacheFav = snapshot.data!;
}
if (_cacheFav == null) {
// TODO proper loading animation
return const LoadingIndicator();
} else {
if (_cacheFav!.isNotEmpty) {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cacheFav!
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider
.select((value) => value
.coins
.contains(e.coin))))
.isNotEmpty)
.where((e) =>
e.isFavorite &&
ref
.read(
addressBookServiceProvider)
.matches(_searchTerm, e))
.where(
(element) => element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key(
"favContactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"Your favorite contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
);
}
}
},
),
const SizedBox(
height: 16,
),
Text(
"All contacts",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: addressBookEntriesFuture,
builder: (_, AsyncSnapshot<List<Contact>> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
_cache = snapshot.data!;
}
if (_cache == null) {
// TODO proper loading animation
return const LoadingIndicator();
} else {
if (_cache!.isNotEmpty) {
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._cache!
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider
.select((value) => value
.coins
.contains(e.coin))))
.isNotEmpty)
.where((e) => ref
.read(addressBookServiceProvider)
.matches(_searchTerm, e))
.where(
(element) => !element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key(
"contactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"Your contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
);
}
}
},
),
],
), ),
onPressed: () {
Navigator.of(context).pushNamed(
AddressBookFilterView.routeName,
);
},
), ),
), ),
), ),
Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
right: 10,
),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
key: const Key("addressBookAddNewContactViewButton"),
size: 36,
shadows: const [],
color:
Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
width: 20,
height: 20,
),
onPressed: () {
Navigator.of(context).pushNamed(
AddAddressBookEntryView.routeName,
);
},
),
),
),
],
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
);
},
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 271,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: !isDesktop
? TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (value) {
setState(() {
_searchTerm = value;
});
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
),
)
: null,
), ),
); if (!isDesktop) const SizedBox(height: 16),
}, Text(
"Favorites",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: addressBookEntriesFuture,
builder: (_, AsyncSnapshot<List<Contact>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_cacheFav = snapshot.data!;
}
if (_cacheFav == null) {
// TODO proper loading animation
return const LoadingIndicator();
} else {
if (_cacheFav!.isNotEmpty) {
return RoundedWhiteContainer(
padding: EdgeInsets.all(!isDesktop ? 0 : 15),
child: Column(
children: [
..._cacheFav!
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider.select(
(value) =>
value.coins.contains(e.coin))))
.isNotEmpty)
.where((e) =>
e.isFavorite &&
ref
.read(addressBookServiceProvider)
.matches(_searchTerm, e))
.where((element) => element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key("favContactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"Your favorite contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
);
}
}
},
),
const SizedBox(
height: 16,
),
Text(
"All contacts",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
FutureBuilder(
future: addressBookEntriesFuture,
builder: (_, AsyncSnapshot<List<Contact>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
_cache = snapshot.data!;
}
if (_cache == null) {
// TODO proper loading animation
return const LoadingIndicator();
} else {
if (_cache!.isNotEmpty) {
return Column(
children: [
RoundedWhiteContainer(
padding: EdgeInsets.all(!isDesktop ? 0 : 15),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
..._cache!
.where((element) => element.addresses
.where((e) => ref.watch(
addressBookFilterProvider.select(
(value) => value.coins
.contains(e.coin))))
.isNotEmpty)
.where((e) => ref
.read(addressBookServiceProvider)
.matches(_searchTerm, e))
.where((element) => !element.isFavorite)
.map(
(e) => AddressBookCard(
key: Key(
"desktopContactCard_${e.id}_key"),
contactId: e.id,
),
),
],
),
),
),
],
);
} else {
return RoundedWhiteContainer(
child: Center(
child: Text(
"Your contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
);
}
}
},
),
],
),
), ),
); );
} }

View file

@ -224,7 +224,11 @@ class _AddAddressBookEntryViewState
const DesktopDialogCloseButton(), const DesktopDialogCloseButton(),
], ],
), ),
Expanded(child: child), Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: child,
)),
], ],
); );
}, },
@ -248,216 +252,473 @@ class _AddAddressBookEntryViewState
child: IntrinsicHeight( child: IntrinsicHeight(
child: Column( child: Column(
children: [ children: [
const SizedBox( if (!isDesktop) const SizedBox(height: 4),
height: 4, isDesktop
), ? Row(
GestureDetector( mainAxisAlignment:
onTap: () { MainAxisAlignment.spaceBetween,
if (_selectedEmoji != null) { children: [
setState(() { GestureDetector(
_selectedEmoji = null; onTap: () {
}); if (_selectedEmoji != null) {
return; setState(() {
} _selectedEmoji = null;
});
return;
}
///TODO if desktop make dialog ///TODO if desktop make dialog
!isDesktop !isDesktop
? showModalBottomSheet<dynamic>( ? showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent, backgroundColor:
context: context, Colors.transparent,
shape: const RoundedRectangleBorder( context: context,
borderRadius: BorderRadius.vertical( shape:
top: Radius.circular(20), const RoundedRectangleBorder(
), borderRadius:
), BorderRadius.vertical(
builder: (_) => const EmojiSelectSheet(), top: Radius.circular(20),
).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
})
: showDialog<dynamic>(
context: context,
builder: (context) {
return DesktopDialog(
maxHeight: 700,
maxWidth: 700,
child: Column(
children: [
Row(
children: [
Padding(
padding:
const EdgeInsets.all(32),
child: Text(
"Select emoji",
style:
STextStyles.desktopH3(
context),
textAlign: TextAlign.center,
),
), ),
], ),
), builder: (_) =>
Expanded( const EmojiSelectSheet(),
child: LayoutBuilder( ).then((value) {
builder: if (value is Emoji) {
(context, constraints) { setState(() {
return SingleChildScrollView( _selectedEmoji = value;
scrollDirection: });
Axis.vertical, }
child: ConstrainedBox( })
constraints: : showDialog<dynamic>(
BoxConstraints( context: context,
minHeight: constraints builder: (context) {
.maxHeight, return DesktopDialog(
minWidth: constraints maxHeight: 700,
.maxWidth, maxWidth: 700,
), child: Column(
child: IntrinsicHeight( children: [
child: Column( Row(
children: const [ children: [
Padding( Padding(
padding: EdgeInsets padding:
.symmetric( const EdgeInsets
horizontal: .all(32),
32), child: Text(
// child: "Select emoji",
// EmojiSelectSheet(), style: STextStyles
.desktopH3(
context),
textAlign:
TextAlign
.center,
), ),
], ),
],
),
Expanded(
child: LayoutBuilder(
builder: (context,
constraints) {
return SingleChildScrollView(
scrollDirection:
Axis.vertical,
child:
ConstrainedBox(
constraints:
BoxConstraints(
minHeight:
constraints
.maxHeight,
minWidth:
constraints
.maxWidth,
),
child:
IntrinsicHeight(
child: Column(
children: const [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 32),
// child:
// EmojiSelectSheet(),
),
],
),
),
),
);
},
), ),
), ),
],
),
);
}).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
},
child: SizedBox(
height: 56,
width: 56,
child: Stack(
children: [
Container(
height: 56,
width: 56,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 30,
width: 30,
)
: Text(
_selectedEmoji!.char,
style: STextStyles
.pageTitleH1(context),
), ),
); ),
}, ),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
width: 8,
height: 8,
),
), ),
), ),
],
),
);
}).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
},
child: SizedBox(
height: 48,
width: 48,
child: Stack(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
) )
: Text( ],
_selectedEmoji!.char, ),
style: STextStyles.pageTitleH1(
context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<StackColors>()!
.textWhite,
width: 8,
height: 8,
),
), ),
), ),
) const SizedBox(width: 8),
], SizedBox(
), width: isDesktop ? 450 : null,
), child: ClipRRect(
), borderRadius: BorderRadius.circular(
const SizedBox( Constants.size.circularBorderRadius,
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: ref
.read(contactNameIsNotEmptyStateProvider
.state)
.state
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController.text = "";
});
},
),
],
),
), ),
) child: TextField(
: null, autocorrect:
), Util.isDesktop ? false : true,
onChanged: (newValue) { enableSuggestions:
ref Util.isDesktop ? false : true,
.read( controller: nameController,
contactNameIsNotEmptyStateProvider.state) focusNode: nameFocusNode,
.state = newValue.isNotEmpty; style: STextStyles.field(context),
}, decoration: standardInputDecoration(
), "Enter contact name",
), nameFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state
? Padding(
padding:
const EdgeInsets.only(
right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController
.text = "";
});
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state = newValue.isNotEmpty;
},
),
),
),
],
)
: Column(
children: [
GestureDetector(
onTap: () {
if (_selectedEmoji != null) {
setState(() {
_selectedEmoji = null;
});
return;
}
///TODO if desktop make dialog
!isDesktop
? showModalBottomSheet<dynamic>(
backgroundColor:
Colors.transparent,
context: context,
shape:
const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) =>
const EmojiSelectSheet(),
).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
})
: showDialog<dynamic>(
context: context,
builder: (context) {
return DesktopDialog(
maxHeight: 700,
maxWidth: 700,
child: Column(
children: [
Row(
children: [
Padding(
padding:
const EdgeInsets
.all(32),
child: Text(
"Select emoji",
style: STextStyles
.desktopH3(
context),
textAlign:
TextAlign
.center,
),
),
],
),
Expanded(
child: LayoutBuilder(
builder: (context,
constraints) {
return SingleChildScrollView(
scrollDirection:
Axis.vertical,
child:
ConstrainedBox(
constraints:
BoxConstraints(
minHeight:
constraints
.maxHeight,
minWidth:
constraints
.maxWidth,
),
child:
IntrinsicHeight(
child: Column(
children: const [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 32),
// child:
// EmojiSelectSheet(),
),
],
),
),
),
);
},
),
),
],
),
);
}).then((value) {
if (value is Emoji) {
setState(() {
_selectedEmoji = value;
});
}
});
},
child: SizedBox(
height: 48,
width: 48,
child: Stack(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(24),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveBG,
),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.user,
height: 24,
width: 24,
)
: Text(
_selectedEmoji!.char,
style: STextStyles
.pageTitleH1(context),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(14),
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
child: Center(
child: _selectedEmoji == null
? SvgPicture.asset(
Assets.svg.plus,
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
width: 12,
height: 12,
)
: SvgPicture.asset(
Assets.svg.thickX,
color: Theme.of(context)
.extension<
StackColors>()!
.textWhite,
width: 8,
height: 8,
),
),
),
)
],
),
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect:
Util.isDesktop ? false : true,
enableSuggestions:
Util.isDesktop ? false : true,
controller: nameController,
focusNode: nameFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Enter contact name",
nameFocusNode,
context,
).copyWith(
suffixIcon: ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state
? Padding(
padding: const EdgeInsets.only(
right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
nameController
.text = "";
});
},
),
],
),
),
)
: null,
),
onChanged: (newValue) {
ref
.read(
contactNameIsNotEmptyStateProvider
.state)
.state = newValue.isNotEmpty;
},
),
),
],
),
if (!isDesktop) const SizedBox(height: 8),
if (forms.length <= 1) if (forms.length <= 1)
const SizedBox( const SizedBox(
height: 8, height: 8,

View file

@ -14,14 +14,13 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class NewContactAddressEntryForm extends ConsumerStatefulWidget { class NewContactAddressEntryForm extends ConsumerStatefulWidget {
const NewContactAddressEntryForm({ const NewContactAddressEntryForm({
Key? key, Key? key,
@ -70,6 +69,7 @@ class _NewContactAddressEntryFormState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
return Column( return Column(
children: [ children: [
TextField( TextField(
@ -168,6 +168,7 @@ class _NewContactAddressEntryFormState
addressLabelFocusNode, addressLabelFocusNode,
context, context,
).copyWith( ).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: addressLabelController.text.isNotEmpty suffixIcon: addressLabelController.text.isNotEmpty
? Padding( ? Padding(
padding: const EdgeInsets.only(right: 0), padding: const EdgeInsets.only(right: 0),
@ -212,6 +213,7 @@ class _NewContactAddressEntryFormState
addressFocusNode, addressFocusNode,
context, context,
).copyWith( ).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: UnconstrainedBox( suffixIcon: UnconstrainedBox(
child: Row( child: Row(
children: [ children: [

View file

@ -1,9 +1,11 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
// import 'package:document_file_save_plus/document_file_save_plus.dart'; // import 'package:document_file_save_plus/document_file_save_plus.dart';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
@ -71,19 +73,53 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
await image.toByteData(format: ui.ImageByteFormat.png); await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List(); Uint8List pngBytes = byteData!.buffer.asUint8List();
// if (shouldSaveInsteadOfShare) { if (shouldSaveInsteadOfShare) {
// await DocumentFileSavePlus.saveFile( if (Util.isDesktop) {
// pngBytes, final dir = Directory("${Platform.environment['HOME']}");
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png", if (!dir.existsSync()) {
// "image/png"); throw Exception(
// } else { "Home dir not found while trying to open filepicker on QR image save");
final tempDir = await getTemporaryDirectory(); }
final file = await File("${tempDir.path}/qrcode.png").create(); final path = await FilePicker.platform.saveFile(
await file.writeAsBytes(pngBytes); fileName: "qrcode.png",
initialDirectory: dir.path,
);
await Share.shareFiles(["${tempDir.path}/qrcode.png"], if (path != null) {
text: "Receive URI QR Code"); final file = File(path);
// } if (file.existsSync()) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.warning,
message: "$path already exists!",
context: context,
),
);
} else {
await file.writeAsBytes(pngBytes);
unawaited(
showFloatingFlushBar(
type: FlushBarType.success,
message: "$path saved!",
context: context,
),
);
}
}
} else {
// await DocumentFileSavePlus.saveFile(
// pngBytes,
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png",
// "image/png");
}
} else {
final tempDir = await getTemporaryDirectory();
final file = await File("${tempDir.path}/qrcode.png").create();
await file.writeAsBytes(pngBytes);
await Share.shareFiles(["${tempDir.path}/qrcode.png"],
text: "Receive URI QR Code");
}
} catch (e) { } catch (e) {
debugPrint(e.toString()); debugPrint(e.toString());
} }
@ -511,6 +547,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
borderColor: Theme.of(context) borderColor: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.background, .background,
width: isDesktop ? 370 : null,
child: Column( child: Column(
children: [ children: [
Text( Text(
@ -542,31 +579,37 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
height: 12, height: 12,
), ),
Row( Row(
mainAxisAlignment: isDesktop
? MainAxisAlignment.center
: MainAxisAlignment.start,
children: [ children: [
SecondaryButton( if (!isDesktop)
width: 170, SecondaryButton(
desktopMed: true, width: 170,
onPressed: () async { desktopMed: true,
await _capturePng(false); onPressed: () async {
}, await _capturePng(false);
label: "Share", },
icon: SvgPicture.asset( label: "Share",
Assets.svg.share, icon: SvgPicture.asset(
width: 20, Assets.svg.share,
height: 20, width: 20,
color: Theme.of(context) height: 20,
.extension<StackColors>()! color: Theme.of(context)
.buttonTextSecondary, .extension<StackColors>()!
.buttonTextSecondary,
),
),
if (!isDesktop)
const SizedBox(
width: 16,
), ),
),
const SizedBox(
width: 16,
),
PrimaryButton( PrimaryButton(
width: 170, width: 170,
desktopMed: true, desktopMed: true,
onPressed: () async { onPressed: () async {
// TODO: add save functionality instead of share // TODO: add save functionality instead of share
// save works on linux at the moment
await _capturePng(true); await _capturePng(true);
}, },
label: "Save", label: "Save",

View file

@ -14,11 +14,10 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
import '../../../widgets/rounded_white_container.dart';
class BaseCurrencySettingsView extends ConsumerStatefulWidget { class BaseCurrencySettingsView extends ConsumerStatefulWidget {
const BaseCurrencySettingsView({Key? key}) : super(key: key); const BaseCurrencySettingsView({Key? key}) : super(key: key);
@ -37,14 +36,20 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
final _searchFocusNode = FocusNode(); final _searchFocusNode = FocusNode();
void onTap(int index) { void onTap(int index) {
if (currenciesWithoutSelected[index] == current || current.isEmpty) { if (Util.isDesktop) {
// ignore if already selected currency setState(() {
return; current = currenciesWithoutSelected[index];
});
} else {
if (currenciesWithoutSelected[index] == current || current.isEmpty) {
// ignore if already selected currency
return;
}
current = currenciesWithoutSelected[index];
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
ref.read(prefsChangeNotifierProvider).currency = current;
} }
current = currenciesWithoutSelected[index];
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
ref.read(prefsChangeNotifierProvider).currency = current;
} }
BorderRadius? _borderRadius(int index) { BorderRadius? _borderRadius(int index) {
@ -82,6 +87,15 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
@override @override
void initState() { void initState() {
_searchController = TextEditingController(); _searchController = TextEditingController();
if (Util.isDesktop) {
currenciesWithoutSelected =
ref.read(baseCurrenciesProvider).map.keys.toList();
current = ref.read(prefsChangeNotifierProvider).currency;
if (current.isNotEmpty) {
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
}
}
super.initState(); super.initState();
} }
@ -94,20 +108,25 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
current = ref
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
currenciesWithoutSelected = ref
.watch(baseCurrenciesProvider.select((value) => value.map))
.keys
.toList();
if (current.isNotEmpty) {
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
}
currenciesWithoutSelected = _filtered();
final isDesktop = Util.isDesktop; final isDesktop = Util.isDesktop;
if (!isDesktop) {
current = ref
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
currenciesWithoutSelected = ref
.watch(baseCurrenciesProvider.select((value) => value.map))
.keys
.toList();
if (current.isNotEmpty) {
currenciesWithoutSelected.remove(current);
currenciesWithoutSelected.insert(0, current);
}
}
currenciesWithoutSelected = _filtered();
return ConditionalParent( return ConditionalParent(
condition: !isDesktop, condition: !isDesktop,
builder: (child) { builder: (child) {
@ -181,7 +200,20 @@ class _CurrencyViewState extends ConsumerState<BaseCurrencySettingsView> {
child: PrimaryButton( child: PrimaryButton(
label: "Save changes", label: "Save changes",
desktopMed: true, desktopMed: true,
onPressed: Navigator.of(context).pop, onPressed: () {
ref.read(prefsChangeNotifierProvider).currency =
current;
if (ref
.read(prefsChangeNotifierProvider)
.externalCalls) {
ref
.read(priceAnd24hChangeNotifierProvider)
.updatePrice();
}
Navigator.of(context).pop();
},
), ),
), ),
], ],

View file

@ -18,6 +18,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/progress_bar.dart';
@ -523,7 +524,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
if (mounted) { if (mounted) {
// pop encryption progress dialog // pop encryption progress dialog
Navigator.of(context).pop(); if (!isDesktop) Navigator.of(context).pop();
if (result) { if (result) {
await showDialog<dynamic>( await showDialog<dynamic>(
@ -607,14 +608,52 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
return; return;
} }
unawaited(showDialog<dynamic>( unawaited(
context: context, showDialog<dynamic>(
barrierDismissible: false, context: context,
builder: (_) => const StackDialog( barrierDismissible: false,
title: "Encrypting backup", builder: (_) {
message: "This shouldn't take long", if (Util.isDesktop) {
return DesktopDialog(
maxHeight: double.infinity,
maxWidth: 450,
child: Padding(
padding: const EdgeInsets.all(
32,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"Encrypting initial backup",
style:
STextStyles.desktopH3(
context),
),
const SizedBox(
height: 40,
),
Text(
"This shouldn't take long",
style: STextStyles
.desktopTextExtraExtraSmall(
context),
),
],
),
),
);
} else {
return const StackDialog(
title: "Encrypting initial backup",
message: "This shouldn't take long",
);
}
},
), ),
)); );
// make sure the dialog is able to be displayed for at least 1 second // make sure the dialog is able to be displayed for at least 1 second
await Future<void>.delayed( await Future<void>.delayed(
const Duration(seconds: 1)); const Duration(seconds: 1));
@ -637,7 +676,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
if (mounted) { if (mounted) {
// pop encryption progress dialog // pop encryption progress dialog
Navigator.of(context).pop(); if (!isDesktop) Navigator.of(context).pop();
if (result) { if (result) {
await showDialog<dynamic>( await showDialog<dynamic>(
@ -648,9 +687,67 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
title: "Backup saved to:", title: "Backup saved to:",
message: fileToSave, message: fileToSave,
) )
: const StackOkDialog( : !isDesktop
title: ? const StackOkDialog(
"Backup creation succeeded"), title:
"Backup creation succeeded")
: DesktopDialog(
maxHeight: double.infinity,
maxWidth: 500,
child: Padding(
padding:
const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Column(
mainAxisSize:
MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
const SizedBox(
height: 26),
Text(
"Stack backup saved to: \n",
style: STextStyles
.desktopH3(
context),
),
Text(
fileToSave,
style: STextStyles
.desktopTextExtraExtraSmall(
context),
),
const SizedBox(
height: 40,
),
Row(
children: [
// const Spacer(),
Expanded(
child:
PrimaryButton(
label: "Ok",
desktopMed:
true,
onPressed:
() {
// Navigator.of(
// context)
// .pop();
},
),
),
],
)
],
),
),
),
); );
passwordController.text = ""; passwordController.text = "";
passwordRepeatController.text = ""; passwordRepeatController.text = "";

View file

@ -424,6 +424,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
passwordFocusNode, passwordFocusNode,
context, context,
).copyWith( ).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: UnconstrainedBox( suffixIcon: UnconstrainedBox(
child: Row( child: Row(
children: [ children: [
@ -555,6 +556,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
passwordRepeatFocusNode, passwordRepeatFocusNode,
context, context,
).copyWith( ).copyWith(
labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
suffixIcon: UnconstrainedBox( suffixIcon: UnconstrainedBox(
child: Row( child: Row(
children: [ children: [
@ -631,7 +633,11 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
return DropdownMenuItem( return DropdownMenuItem(
value: e, value: e,
child: Text(message), child: Text(
message,
style:
STextStyles.desktopTextExtraExtraSmall(context),
),
); );
}, },
), ),

View file

@ -17,14 +17,13 @@ class SupportView extends StatelessWidget {
}) : super(key: key); }) : super(key: key);
static const String routeName = "/support"; static const String routeName = "/support";
final double iconSize = 28;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
final isDesktop = Util.isDesktop;
return ConditionalParent( return ConditionalParent(
condition: !isDesktop, condition: !isDesktop,
builder: (child) { builder: (child) {
@ -64,321 +63,168 @@ class SupportView extends StatelessWidget {
: const SizedBox( : const SizedBox(
height: 12, height: 12,
), ),
RoundedWhiteContainer( AboutItem(
padding: const EdgeInsets.all(0), linkUrl: "https://t.me/stackwallet",
child: RawMaterialButton( label: "Telegram",
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, buttonText: "@stackwallet",
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, iconAsset: Assets.socials.telegram,
shape: RoundedRectangleBorder( isDesktop: isDesktop,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
if (!isDesktop) {
launchUrl(
Uri.parse("https://t.me/stackwallet"),
mode: LaunchMode.externalApplication,
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
Assets.socials.telegram,
width: iconSize,
height: iconSize,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(
width: 12,
),
Text(
"Telegram",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
BlueTextButton(
text: isDesktop ? "@stackwallet" : "",
onTap: () {
launchUrl(
Uri.parse("https://t.me/stackwallet"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
), ),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
RoundedWhiteContainer( AboutItem(
padding: const EdgeInsets.all(0), linkUrl: "https://discord.gg/RZMG3yUm",
child: RawMaterialButton( label: "Discord",
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, buttonText: "Stack Wallet",
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, iconAsset: Assets.socials.discord,
shape: RoundedRectangleBorder( isDesktop: isDesktop,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
if (!isDesktop) {
launchUrl(
Uri.parse("https://discord.gg/RZMG3yUm"),
mode: LaunchMode.externalApplication,
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
Assets.socials.discord,
width: iconSize,
height: iconSize,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(
width: 12,
),
Text(
"Discord",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
BlueTextButton(
text: isDesktop ? "Stack Wallet" : "",
onTap: () {
launchUrl(
Uri.parse(
"https://discord.gg/RZMG3yUm"), //expired link?
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
), ),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
RoundedWhiteContainer( AboutItem(
padding: const EdgeInsets.all(0), linkUrl: "https://www.reddit.com/r/stackwallet/",
child: RawMaterialButton( label: "Reddit",
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, buttonText: "r/stackwallet",
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, iconAsset: Assets.socials.reddit,
shape: RoundedRectangleBorder( isDesktop: isDesktop,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
if (!isDesktop) {
launchUrl(
Uri.parse("https://www.reddit.com/r/stackwallet/"),
mode: LaunchMode.externalApplication,
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
Assets.socials.reddit,
width: iconSize,
height: iconSize,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(
width: 12,
),
Text(
"Reddit",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
BlueTextButton(
text: isDesktop ? "r/stackwallet" : "",
onTap: () {
launchUrl(
Uri.parse("https://www.reddit.com/r/stackwallet/"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
), ),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
RoundedWhiteContainer( AboutItem(
padding: const EdgeInsets.all(0), linkUrl: "https://twitter.com/stack_wallet",
child: RawMaterialButton( label: "Twitter",
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, buttonText: "@stack_wallet",
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, iconAsset: Assets.socials.twitter,
shape: RoundedRectangleBorder( isDesktop: isDesktop,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
if (!isDesktop) {
launchUrl(
Uri.parse("https://twitter.com/stack_wallet"),
mode: LaunchMode.externalApplication,
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
Assets.socials.twitter,
width: iconSize,
height: iconSize,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(
width: 12,
),
Text(
"Twitter",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
BlueTextButton(
text: isDesktop ? "@stack_wallet" : "",
onTap: () {
launchUrl(
Uri.parse("https://twitter.com/stack_wallet"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
), ),
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
RoundedWhiteContainer( AboutItem(
padding: const EdgeInsets.all(0), linkUrl: "mailto:support@stackwallet.com",
child: RawMaterialButton( label: "Email",
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, buttonText: "support@stackwallet.com",
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, iconAsset: Assets.svg.envelope,
shape: RoundedRectangleBorder( isDesktop: isDesktop,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
if (!isDesktop) {
launchUrl(
Uri.parse("mailto:support@stackwallet.com"),
mode: LaunchMode.externalApplication,
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
Assets.svg.envelope,
width: iconSize,
height: iconSize,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(
width: 12,
),
Text(
"Email",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
BlueTextButton(
text: isDesktop ? "support@stackwallet.com" : "",
onTap: () {
launchUrl(
Uri.parse("mailto:support@stackwallet.com"),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
), ),
], ],
), ),
); );
} }
} }
class AboutItem extends StatelessWidget {
const AboutItem({
Key? key,
required this.linkUrl,
required this.label,
required this.buttonText,
required this.iconAsset,
required this.isDesktop,
}) : super(key: key);
final String linkUrl;
final String label;
final String buttonText;
final String iconAsset;
final bool isDesktop;
@override
Widget build(BuildContext context) {
final double iconSize = isDesktop ? 20 : 28;
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ConditionalParent(
condition: !isDesktop,
builder: (child) => RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
launchUrl(
Uri.parse(linkUrl),
mode: LaunchMode.externalApplication,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: child,
),
),
child: Padding(
padding: isDesktop
? const EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
)
: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
ConditionalParent(
condition: isDesktop,
builder: (child) => Container(
width: 40,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10000),
color: Theme.of(context)
.extension<StackColors>()!
.buttonBackSecondary,
),
child: Center(
child: child,
),
),
child: SvgPicture.asset(
iconAsset,
width: iconSize,
height: iconSize,
color: Theme.of(context)
.extension<StackColors>()!
.bottomNavIconIcon,
),
),
const SizedBox(
width: 12,
),
Text(
label,
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
if (isDesktop)
BlueTextButton(
text: buttonText,
onTap: () {
launchUrl(
Uri.parse(linkUrl),
mode: LaunchMode.externalApplication,
);
},
),
],
),
),
),
);
}
}

View file

@ -203,15 +203,18 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
height: 32, height: 32,
width: 32, width: 32,
child: Center( child: Center(
child: SvgPicture.asset( child: MouseRegion(
hidePassword cursor: SystemMouseCursors.click,
? Assets.svg.eye child: SvgPicture.asset(
: Assets.svg.eyeSlash, hidePassword
color: Theme.of(context) ? Assets.svg.eye
.extension<StackColors>()! : Assets.svg.eyeSlash,
.textDark3, color: Theme.of(context)
width: 24, .extension<StackColors>()!
height: 19, .textDark3,
width: 24,
height: 19,
),
), ),
), ),
), ),
@ -354,22 +357,25 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
height: 32, height: 32,
width: 32, width: 32,
child: Center( child: Center(
child: SvgPicture.asset( child: MouseRegion(
fieldsMatch && passwordStrength == 1 cursor: SystemMouseCursors.click,
? Assets.svg.checkCircle child: SvgPicture.asset(
: hidePassword fieldsMatch && passwordStrength == 1
? Assets.svg.eye ? Assets.svg.checkCircle
: Assets.svg.eyeSlash, : hidePassword
color: fieldsMatch && ? Assets.svg.eye
passwordStrength == 1 : Assets.svg.eyeSlash,
? Theme.of(context) color: fieldsMatch &&
.extension<StackColors>()! passwordStrength == 1
.accentColorGreen ? Theme.of(context)
: Theme.of(context) .extension<StackColors>()!
.extension<StackColors>()! .accentColorGreen
.textDark3, : Theme.of(context)
width: 24, .extension<StackColors>()!
height: 19, .textDark3,
width: 24,
height: 19,
),
), ),
), ),
), ),

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
class DesktopLoginView extends ConsumerStatefulWidget { class DesktopLoginView extends ConsumerStatefulWidget {
@ -45,6 +46,15 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
Future<void> login() async { Future<void> login() async {
try { try {
unawaited(
showDialog(
context: context,
builder: (context) => const LoadingIndicator(
width: 200,
),
),
);
await ref await ref
.read(storageCryptoHandlerProvider) .read(storageCryptoHandlerProvider)
.initFromExisting(passwordController.text); .initFromExisting(passwordController.text);
@ -55,6 +65,9 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
// if no errors passphrase is correct // if no errors passphrase is correct
if (mounted) { if (mounted) {
// pop loading indicator
Navigator.of(context).pop();
unawaited( unawaited(
Navigator.of(context).pushNamedAndRemoveUntil( Navigator.of(context).pushNamedAndRemoveUntil(
DesktopHomeView.routeName, DesktopHomeView.routeName,
@ -63,6 +76,9 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
); );
} }
} catch (e) { } catch (e) {
// pop loading indicator
Navigator.of(context).pop();
await showFloatingFlushBar( await showFloatingFlushBar(
type: FlushBarType.warning, type: FlushBarType.warning,
message: e.toString(), message: e.toString(),
@ -159,15 +175,18 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
hidePassword = !hidePassword; hidePassword = !hidePassword;
}); });
}, },
child: SvgPicture.asset( child: MouseRegion(
hidePassword cursor: SystemMouseCursors.click,
? Assets.svg.eye child: SvgPicture.asset(
: Assets.svg.eyeSlash, hidePassword
color: Theme.of(context) ? Assets.svg.eye
.extension<StackColors>()! : Assets.svg.eyeSlash,
.textDark3, color: Theme.of(context)
width: 24, .extension<StackColors>()!
height: 24, .textDark3,
width: 24,
height: 24,
),
), ),
), ),
const SizedBox( const SizedBox(

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/contact.dart';
import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart';
@ -9,11 +10,11 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
@ -36,7 +37,7 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> {
late bool hasContacts = false; late bool hasContacts = false;
String filter = ""; String _searchTerm = "";
Future<void> selectCryptocurrency() async { Future<void> selectCryptocurrency() async {
await showDialog<dynamic>( await showDialog<dynamic>(
@ -123,25 +124,25 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> {
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
), ),
child: TextField( child: TextField(
autocorrect: false, autocorrect: Util.isDesktop ? false : true,
enableSuggestions: false, enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController, controller: _searchController,
focusNode: _searchFocusNode, focusNode: _searchFocusNode,
onChanged: (newString) { onChanged: (value) {
setState(() => filter = newString); setState(() {
_searchTerm = value;
});
}, },
style: STextStyles.field(context), style: STextStyles.field(context),
decoration: standardInputDecoration( decoration: standardInputDecoration(
"Search...", "Search",
_searchFocusNode, _searchFocusNode,
context, context,
).copyWith( ).copyWith(
labelStyle: STextStyles.fieldLabel(context)
.copyWith(fontSize: 16),
prefixIcon: Padding( prefixIcon: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, horizontal: 10,
vertical: 16, vertical: 20,
), ),
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.svg.search, Assets.svg.search,
@ -160,7 +161,6 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> {
onTap: () async { onTap: () async {
setState(() { setState(() {
_searchController.text = ""; _searchController.text = "";
filter = "";
}); });
}, },
), ),
@ -239,18 +239,11 @@ class _DesktopAddressBook extends ConsumerState<DesktopAddressBook> {
), ),
), ),
), ),
Padding( const Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 26), padding: EdgeInsets.symmetric(horizontal: 24, vertical: 26),
child: SizedBox( child: SizedBox(
width: 489, width: 489,
child: RoundedWhiteContainer( child: AddressBookView(),
child: Center(
child: Text(
"Your contacts will appear here",
style: STextStyles.itemSubtitle(context),
),
),
),
), ),
), ),
], ],

View file

@ -292,13 +292,13 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
eventBus: eventBus, eventBus: eventBus,
), ),
const SizedBox( const SizedBox(
width: 32, width: 2,
), ),
WalletKeysButton( WalletKeysButton(
walletId: walletId, walletId: walletId,
), ),
const SizedBox( const SizedBox(
width: 32, width: 12,
), ),
], ],
), ),

View file

@ -0,0 +1,369 @@
import 'package:decimal/decimal.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/animated_text.dart';
class DesktopFeeDropDown extends ConsumerStatefulWidget {
const DesktopFeeDropDown({
Key? key,
required this.walletId,
}) : super(key: key);
final String walletId;
@override
ConsumerState<DesktopFeeDropDown> createState() => _DesktopFeeDropDownState();
}
class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
late final String walletId;
FeeObject? feeObject;
FeeRateType feeRateType = FeeRateType.average;
final stringsToLoopThrough = [
"Calculating",
"Calculating.",
"Calculating..",
"Calculating...",
];
Future<Decimal> feeFor({
required int amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) async {
switch (feeRateType) {
case FeeRateType.fast:
if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate));
} else {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
Format.satoshisToAmount(
await manager.estimateFeeFor(amount, feeRate));
}
}
return ref.read(feeSheetSessionCacheProvider).fast[amount]!;
case FeeRateType.average:
if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).average[amount] =
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate));
} else {
ref.read(feeSheetSessionCacheProvider).average[amount] =
Format.satoshisToAmount(
await manager.estimateFeeFor(amount, feeRate));
}
}
return ref.read(feeSheetSessionCacheProvider).average[amount]!;
case FeeRateType.slow:
if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate));
} else {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
Format.satoshisToAmount(
await manager.estimateFeeFor(amount, feeRate));
}
}
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
}
}
String estimatedTimeToBeIncludedInNextBlock(
int targetBlockTime, int estimatedNumberOfBlocks) {
int time = targetBlockTime * estimatedNumberOfBlocks;
int hours = (time / 3600).floor();
if (hours > 1) {
return "~$hours hours";
} else if (hours == 1) {
return "~$hours hour";
}
// less than an hour
final string = (time / 60).toStringAsFixed(1);
if (string == "1.0") {
return "~1 minute";
} else {
if (string.endsWith(".0")) {
return "~${(time / 60).floor()} minutes";
}
return "~$string minutes";
}
}
@override
void initState() {
walletId = widget.walletId;
super.initState();
}
String? labelSlow;
String? labelAverage;
String? labelFast;
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId)));
return FutureBuilder(
future: manager.fees,
builder: (context, AsyncSnapshot<FeeObject> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
feeObject = snapshot.data!;
}
return DropdownButtonHideUnderline(
child: DropdownButton2(
offset: const Offset(0, -10),
isExpanded: true,
dropdownElevation: 0,
value: ref.watch(feeRateTypeStateProvider.state).state,
// selectedItemBuilder: (s) {
// return [
// ...FeeRateType.values.map(
// (e) => DropdownMenuItem(
// value: e,
// child: FeeDropDownChild(
// feeObject: feeObject,
// feeRateType: e,
// walletId: walletId,
// amount: amount,
// feeFor: feeFor,
// isSelected: true,
// ),
// ),
// ),
// ];
// },
items: [
...FeeRateType.values.map(
(e) => DropdownMenuItem(
value: e,
child: FeeDropDownChild(
feeObject: feeObject,
feeRateType: e,
walletId: walletId,
feeFor: feeFor,
isSelected: false,
),
),
),
],
onChanged: (newRateType) {
if (newRateType is FeeRateType) {
ref.read(feeRateTypeStateProvider.state).state = newRateType;
}
},
icon: SvgPicture.asset(
Assets.svg.chevronDown,
width: 12,
height: 6,
color: Theme.of(context).extension<StackColors>()!.textDark3,
),
buttonPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
buttonDecoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
dropdownDecoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
);
});
}
}
final sendAmountProvider =
StateProvider.autoDispose<Decimal>((_) => Decimal.zero);
class FeeDropDownChild extends ConsumerWidget {
const FeeDropDownChild({
Key? key,
required this.feeObject,
required this.feeRateType,
required this.walletId,
required this.feeFor,
required this.isSelected,
}) : super(key: key);
final FeeObject? feeObject;
final FeeRateType feeRateType;
final String walletId;
final Future<Decimal> Function({
required int amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) feeFor;
final bool isSelected;
static const stringsToLoopThrough = [
"Calculating",
"Calculating.",
"Calculating..",
"Calculating...",
];
String estimatedTimeToBeIncludedInNextBlock(
int targetBlockTime, int estimatedNumberOfBlocks) {
int time = targetBlockTime * estimatedNumberOfBlocks;
int hours = (time / 3600).floor();
if (hours > 1) {
return "~$hours hours";
} else if (hours == 1) {
return "~$hours hour";
}
// less than an hour
final string = (time / 60).toStringAsFixed(1);
if (string == "1.0") {
return "~1 minute";
} else {
if (string.endsWith(".0")) {
return "~${(time / 60).floor()} minutes";
}
return "~$string minutes";
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
debugPrint("BUILD: $runtimeType : $feeRateType");
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId)));
if (feeObject == null) {
return AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.textFieldActiveText,
),
);
} else {
return FutureBuilder(
future: feeFor(
coin: manager.coin,
feeRateType: FeeRateType.fast,
feeRate: feeRateType == FeeRateType.fast
? feeObject!.fast
: feeRateType == FeeRateType.slow
? feeObject!.slow
: feeObject!.medium,
amount: Format.decimalAmountToSatoshis(
ref.watch(sendAmountProvider.state).state,
),
),
builder: (_, AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${feeRateType.prettyName} (~${snapshot.data!} ${manager.coin.ticker})",
style:
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
if (feeObject != null)
Text(
estimatedTimeToBeIncludedInNextBlock(
Constants.targetBlockTimeInSeconds(manager.coin),
feeRateType == FeeRateType.fast
? feeObject!.numberOfBlocksFast
: feeRateType == FeeRateType.slow
? feeObject!.numberOfBlocksSlow
: feeObject!.numberOfBlocksAverage,
),
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
],
);
} else {
return AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
);
}
},
);
}
}
}

View file

@ -13,6 +13,7 @@ import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dia
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart'; import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart';
@ -94,11 +95,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
late VoidCallback onCryptoAmountChanged; late VoidCallback onCryptoAmountChanged;
Future<void> previewSend() async { Future<void> previewSend() async {
// wait for keyboard to disappear
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 100),
);
final manager = final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId); ref.read(walletsChangeNotifierProvider).getManager(walletId);
@ -794,35 +790,25 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
_addressToggleFlag = true; _addressToggleFlag = true;
} }
// _cryptoFocus.addListener(() { _cryptoFocus.addListener(() {
// if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
// if (_amountToSend == null) { if (_amountToSend == null) {
// setState(() { ref.refresh(sendAmountProvider);
// _calculateFeesFuture = calculateFees(0); } else {
// }); ref.read(sendAmountProvider.state).state = _amountToSend!;
// } else { }
// setState(() { }
// _calculateFeesFuture = });
// calculateFees(Format.decimalAmountToSatoshis(_amountToSend!));
// }); _baseFocus.addListener(() {
// } if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
// } if (_amountToSend == null) {
// }); ref.refresh(sendAmountProvider);
// } else {
// _baseFocus.addListener(() { ref.read(sendAmountProvider.state).state = _amountToSend!;
// if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { }
// if (_amountToSend == null) { }
// setState(() { });
// _calculateFeesFuture = calculateFees(0);
// });
// } else {
// setState(() {
// _calculateFeesFuture =
// calculateFees(Format.decimalAmountToSatoshis(_amountToSend!));
// });
// }
// }
// });
super.initState(); super.initState();
} }
@ -1371,6 +1357,24 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
), ),
), ),
const SizedBox(
height: 20,
),
Text(
"Transaction fee (estimated)",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
textAlign: TextAlign.left,
),
const SizedBox(
height: 10,
),
DesktopFeeDropDown(
walletId: walletId,
),
const SizedBox( const SizedBox(
height: 36, height: 36,
), ),

View file

@ -98,25 +98,26 @@ class _NetworkInfoButtonState extends ConsumerState<NetworkInfoButton> {
Widget _buildNetworkIcon(WalletSyncStatus status, BuildContext context) { Widget _buildNetworkIcon(WalletSyncStatus status, BuildContext context) {
const size = 24.0; const size = 24.0;
final color = _getColor(status, context);
switch (status) { switch (status) {
case WalletSyncStatus.unableToSync: case WalletSyncStatus.unableToSync:
return SvgPicture.asset( return SvgPicture.asset(
Assets.svg.radioProblem, Assets.svg.radioProblem,
color: Theme.of(context).extension<StackColors>()!.accentColorRed, color: color,
width: size, width: size,
height: size, height: size,
); );
case WalletSyncStatus.synced: case WalletSyncStatus.synced:
return SvgPicture.asset( return SvgPicture.asset(
Assets.svg.radio, Assets.svg.radio,
color: Theme.of(context).extension<StackColors>()!.accentColorGreen, color: color,
width: size, width: size,
height: size, height: size,
); );
case WalletSyncStatus.syncing: case WalletSyncStatus.syncing:
return SvgPicture.asset( return SvgPicture.asset(
Assets.svg.radioSyncing, Assets.svg.radioSyncing,
color: Theme.of(context).extension<StackColors>()!.accentColorYellow, color: color,
width: size, width: size,
height: size, height: size,
); );
@ -125,35 +126,46 @@ class _NetworkInfoButtonState extends ConsumerState<NetworkInfoButton> {
Widget _buildText(WalletSyncStatus status, BuildContext context) { Widget _buildText(WalletSyncStatus status, BuildContext context) {
String label; String label;
Color color;
switch (status) { switch (status) {
case WalletSyncStatus.unableToSync: case WalletSyncStatus.unableToSync:
label = "Unable to sync"; label = "Unable to sync";
color = Theme.of(context).extension<StackColors>()!.accentColorRed;
break; break;
case WalletSyncStatus.synced: case WalletSyncStatus.synced:
label = "Synchronised"; label = "Synchronized";
color = Theme.of(context).extension<StackColors>()!.accentColorGreen;
break; break;
case WalletSyncStatus.syncing: case WalletSyncStatus.syncing:
label = "Synchronising"; label = "Synchronizing";
color = Theme.of(context).extension<StackColors>()!.accentColorYellow;
break; break;
} }
return Text( return Text(
label, label,
style: STextStyles.desktopMenuItemSelected(context).copyWith( style: STextStyles.desktopMenuItemSelected(context).copyWith(
color: color, color: _getColor(status, context),
), ),
); );
} }
Color _getColor(WalletSyncStatus status, BuildContext context) {
switch (status) {
case WalletSyncStatus.unableToSync:
return Theme.of(context).extension<StackColors>()!.accentColorRed;
case WalletSyncStatus.synced:
return Theme.of(context).extension<StackColors>()!.accentColorGreen;
case WalletSyncStatus.syncing:
return Theme.of(context).extension<StackColors>()!.accentColorYellow;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return RawMaterialButton(
onTap: () { hoverColor: _getColor(_currentSyncStatus, context).withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(1000),
),
onPressed: () {
if (Util.isDesktop) { if (Util.isDesktop) {
// showDialog<void>( // showDialog<void>(
// context: context, // context: context,
@ -265,8 +277,11 @@ class _NetworkInfoButtonState extends ConsumerState<NetworkInfoButton> {
); );
} }
}, },
child: Container( child: Padding(
color: Colors.transparent, padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 32,
),
child: Row( child: Row(
children: [ children: [
_buildNetworkIcon(_currentSyncStatus, context), _buildNetworkIcon(_currentSyncStatus, context),

View file

@ -39,85 +39,93 @@ class _SendReceiveTabMenuState extends State<SendReceiveTabMenu> {
return Row( return Row(
children: [ children: [
Expanded( Expanded(
child: GestureDetector( child: MouseRegion(
onTap: () => _onChanged(0), cursor: SystemMouseCursors.click,
child: Container( child: GestureDetector(
color: Colors.transparent, onTap: () => _onChanged(0),
child: Column( child: Container(
children: [ color: Colors.transparent,
const SizedBox( child: Column(
height: 16, children: [
), const SizedBox(
Text( height: 16,
"Send",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: _selectedIndex == 0
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
), ),
), Text(
const SizedBox( "Send",
height: 19, style:
), STextStyles.desktopTextExtraSmall(context).copyWith(
Container( color: _selectedIndex == 0
height: 2, ? Theme.of(context)
decoration: BoxDecoration( .extension<StackColors>()!
color: _selectedIndex == 0 .accentColorBlue
? Theme.of(context) : Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.accentColorBlue .textSubtitle1,
: Theme.of(context) ),
.extension<StackColors>()!
.background,
), ),
), const SizedBox(
], height: 19,
),
Container(
height: 2,
decoration: BoxDecoration(
color: _selectedIndex == 0
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.background,
),
),
],
),
), ),
), ),
), ),
), ),
Expanded( Expanded(
child: GestureDetector( child: MouseRegion(
onTap: () => _onChanged(1), cursor: SystemMouseCursors.click,
child: Container( child: GestureDetector(
color: Colors.transparent, onTap: () => _onChanged(1),
child: Column( child: Container(
children: [ color: Colors.transparent,
const SizedBox( child: Column(
height: 16, children: [
), const SizedBox(
Text( height: 16,
"Receive",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: _selectedIndex == 1
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
), ),
), Text(
const SizedBox( "Receive",
height: 19, style:
), STextStyles.desktopTextExtraSmall(context).copyWith(
Container( color: _selectedIndex == 1
height: 2, ? Theme.of(context)
decoration: BoxDecoration( .extension<StackColors>()!
color: _selectedIndex == 1 .accentColorBlue
? Theme.of(context) : Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.accentColorBlue .textSubtitle1,
: Theme.of(context) ),
.extension<StackColors>()!
.background,
), ),
), const SizedBox(
], height: 19,
),
Container(
height: 2,
decoration: BoxDecoration(
color: _selectedIndex == 1
? Theme.of(context)
.extension<StackColors>()!
.accentColorBlue
: Theme.of(context)
.extension<StackColors>()!
.background,
),
),
],
),
), ),
), ),
), ),

View file

@ -16,8 +16,11 @@ class WalletKeysButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return RawMaterialButton(
onTap: () { shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(1000),
),
onPressed: () {
showDialog<void>( showDialog<void>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
@ -36,17 +39,12 @@ class WalletKeysButton extends StatelessWidget {
}, },
), ),
); );
// showDialog<void>(
// context: context,
// barrierDismissible: false,
// builder: (context) => UnlockWalletKeysDesktop(
// walletId: walletId,
// ),
// );
}, },
child: Container( child: Padding(
color: Colors.transparent, padding: const EdgeInsets.symmetric(
vertical: 19,
horizontal: 32,
),
child: Row( child: Row(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(

View file

@ -35,10 +35,13 @@ class _AdvancedSettings extends ConsumerState<AdvancedSettings> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset( Padding(
Assets.svg.circleLanguage, padding: const EdgeInsets.all(8.0),
width: 48, child: SvgPicture.asset(
height: 48, Assets.svg.circleSliders,
width: 48,
height: 48,
),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View file

@ -52,10 +52,13 @@ class _AppearanceOptionSettings
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset( Padding(
Assets.svg.circleSun, padding: const EdgeInsets.all(8.0),
width: 48, child: SvgPicture.asset(
height: 48, Assets.svg.circleSun,
width: 48,
height: 48,
),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View file

@ -492,7 +492,11 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
return DropdownMenuItem( return DropdownMenuItem(
value: e, value: e,
child: Text(message), child: Text(
message,
style: STextStyles.desktopTextExtraExtraSmall(
context),
),
); );
}, },
), ),

View file

@ -4,9 +4,9 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
class CurrencySettings extends ConsumerStatefulWidget { class CurrencySettings extends ConsumerStatefulWidget {
@ -18,6 +18,41 @@ class CurrencySettings extends ConsumerStatefulWidget {
ConsumerState<CurrencySettings> createState() => _CurrencySettings(); ConsumerState<CurrencySettings> createState() => _CurrencySettings();
} }
Future<void> chooseCurrency(BuildContext context) async {
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return DesktopDialog(
maxHeight: 800,
maxWidth: 600,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(32),
child: Text(
"Select currency",
style: STextStyles.desktopH3(context),
textAlign: TextAlign.center,
),
),
const DesktopDialogCloseButton(),
],
),
const Expanded(
child: BaseCurrencySettingsView(),
),
],
),
);
},
);
}
class _CurrencySettings extends ConsumerState<CurrencySettings> { class _CurrencySettings extends ConsumerState<CurrencySettings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,10 +67,13 @@ class _CurrencySettings extends ConsumerState<CurrencySettings> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset( Padding(
Assets.svg.circleDollarSign, padding: const EdgeInsets.all(8.0),
width: 48, child: SvgPicture.asset(
height: 48, Assets.svg.circleDollarSign,
width: 48,
height: 48,
),
), ),
Center( Center(
child: Padding( child: Padding(
@ -50,8 +88,8 @@ class _CurrencySettings extends ConsumerState<CurrencySettings> {
), ),
TextSpan( TextSpan(
text: text:
"\n\nProtect your Stack Wallet with a strong password. Stack Wallet does not store " "\n\nSelect a fiat currency to evaluate your crypto assets. We use CoinGecko conversion rates "
"your password, and is therefore NOT able to restore it. Keep your password safe and secure.", "when displaying your balance and transaction amounts.",
style: style:
STextStyles.desktopTextExtraExtraSmall(context), STextStyles.desktopTextExtraExtraSmall(context),
), ),
@ -62,12 +100,20 @@ class _CurrencySettings extends ConsumerState<CurrencySettings> {
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: const [ children: [
Padding( Padding(
padding: EdgeInsets.all( padding: const EdgeInsets.all(
10, 10,
), ),
child: NewPasswordButton(), child: PrimaryButton(
width: 210,
desktopMed: true,
enabled: true,
label: "Change currency",
onPressed: () {
chooseCurrency(context);
},
),
), ),
], ],
), ),
@ -79,71 +125,3 @@ class _CurrencySettings extends ConsumerState<CurrencySettings> {
); );
} }
} }
class NewPasswordButton extends ConsumerWidget {
const NewPasswordButton({
Key? key,
}) : super(key: key);
Future<void> chooseCurrency(BuildContext context) async {
// await showDialog<dynamic>(
// context: context,
// useSafeArea: false,
// barrierDismissible: true,
// builder: (context) {
// return CurrencyDialog();
// },
// );
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return DesktopDialog(
maxHeight: 800,
maxWidth: 600,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(32),
child: Text(
"Select currency",
style: STextStyles.desktopH3(context),
textAlign: TextAlign.center,
),
),
const DesktopDialogCloseButton(),
],
),
const Expanded(
child: BaseCurrencySettingsView(),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return SizedBox(
width: 200,
height: 48,
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {
chooseCurrency(context);
},
child: Text(
"Change currency",
style: STextStyles.button(context),
),
),
);
}
}

View file

@ -4,7 +4,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/language_settings/language_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/language_settings/language_dialog.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
class LanguageOptionSettings extends ConsumerStatefulWidget { class LanguageOptionSettings extends ConsumerStatefulWidget {
@ -17,6 +17,17 @@ class LanguageOptionSettings extends ConsumerStatefulWidget {
_LanguageOptionSettings(); _LanguageOptionSettings();
} }
Future<void> chooseLanguage(BuildContext context) async {
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return const LanguageDialog();
},
);
}
class _LanguageOptionSettings extends ConsumerState<LanguageOptionSettings> { class _LanguageOptionSettings extends ConsumerState<LanguageOptionSettings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -31,10 +42,13 @@ class _LanguageOptionSettings extends ConsumerState<LanguageOptionSettings> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset( Padding(
Assets.svg.circleLanguage, padding: const EdgeInsets.all(8.0),
width: 48, child: SvgPicture.asset(
height: 48, Assets.svg.circleLanguage,
width: 48,
height: 48,
),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -63,12 +77,20 @@ class _LanguageOptionSettings extends ConsumerState<LanguageOptionSettings> {
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: const [ children: [
Padding( Padding(
padding: EdgeInsets.all( padding: EdgeInsets.all(
10, 10,
), ),
child: ChangeLanguageButton(), child: PrimaryButton(
width: 210,
desktopMed: true,
enabled: true,
label: "Change language",
onPressed: () {
chooseLanguage(context);
},
),
), ),
], ],
), ),
@ -80,40 +102,3 @@ class _LanguageOptionSettings extends ConsumerState<LanguageOptionSettings> {
); );
} }
} }
class ChangeLanguageButton extends ConsumerWidget {
const ChangeLanguageButton({
Key? key,
}) : super(key: key);
Future<void> chooseLanguage(BuildContext context) async {
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return const LanguageDialog();
},
);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return SizedBox(
width: 200,
height: 48,
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {
chooseLanguage(context);
},
child: Text(
"Change language",
style: STextStyles.button(context),
),
),
);
}
}

View file

@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart';
import 'package:stackwallet/providers/global/node_service_provider.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
@ -11,8 +10,10 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class NodesSettings extends ConsumerStatefulWidget { class NodesSettings extends ConsumerStatefulWidget {
const NodesSettings({Key? key}) : super(key: key); const NodesSettings({Key? key}) : super(key: key);
@ -29,8 +30,22 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
late final TextEditingController searchNodeController; late final TextEditingController searchNodeController;
late final FocusNode searchNodeFocusNode; late final FocusNode searchNodeFocusNode;
late final ScrollController nodeScrollController;
String filter = ""; String filter = "";
List<Coin> _search(String filter, List<Coin> coins) {
if (filter.isEmpty) {
return coins;
}
return coins
.where((coin) =>
coin.prettyName.contains(filter) ||
coin.name.contains(filter) ||
coin.ticker.toLowerCase().contains(filter.toLowerCase()))
.toList();
}
@override @override
void initState() { void initState() {
_coins = _coins.toList(); _coins = _coins.toList();
@ -39,6 +54,8 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
searchNodeController = TextEditingController(); searchNodeController = TextEditingController();
searchNodeFocusNode = FocusNode(); searchNodeFocusNode = FocusNode();
nodeScrollController = ScrollController();
super.initState(); super.initState();
} }
@ -46,12 +63,15 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
void dispose() { void dispose() {
searchNodeController.dispose(); searchNodeController.dispose();
searchNodeFocusNode.dispose(); searchNodeFocusNode.dispose();
nodeScrollController.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
bool showTestNet = ref.watch( bool showTestNet = ref.watch(
prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
); );
@ -60,191 +80,218 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
? _coins ? _coins
: _coins.sublist(0, _coins.length - kTestNetCoinCount); : _coins.sublist(0, _coins.length - kTestNetCoinCount);
debugPrint("BUILD: $runtimeType"); coins = _search(filter, coins);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
right: 30,
),
child: RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
Assets.svg.circleNode,
width: 48,
height: 48,
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(10),
child: RichText(
textAlign: TextAlign.start,
text: TextSpan(
children: [
TextSpan(
text: "Nodes",
style: STextStyles.desktopTextSmall(context),
),
TextSpan(
text: "\n\nSelect a coin to see nodes",
style: STextStyles.desktopTextExtraExtraSmall(
context),
),
],
),
),
),
],
),
Padding(
padding: const EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: searchNodeController,
focusNode: searchNodeFocusNode,
onChanged: (newString) {
setState(() => filter = newString);
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
searchNodeFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
borderColor:
Theme.of(context).extension<StackColors>()!.background,
child: ListView.separated(
primary: false,
shrinkWrap: true,
itemBuilder: (context, index) {
final coin = coins[index];
final count = ref
.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodesFor(coin)))
.length;
return Padding( return Row(
padding: const EdgeInsets.all(0), crossAxisAlignment: CrossAxisAlignment.start,
child: RawMaterialButton( children: [
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, Expanded(
shape: RoundedRectangleBorder( child: Padding(
borderRadius: BorderRadius.circular( padding: const EdgeInsets.only(
Constants.size.circularBorderRadius, right: 32,
bottom: 32,
),
child: RoundedWhiteContainer(
radiusMultiplier: 2,
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
Assets.svg.circleNode,
width: 48,
height: 48,
),
const SizedBox(
height: 16,
),
Text(
"Nodes",
style: STextStyles.desktopTextSmall(context),
),
const SizedBox(
height: 8,
),
Text(
"Select a coin to see nodes",
style: STextStyles.desktopTextExtraExtraSmall(context),
),
const SizedBox(
height: 20,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: searchNodeController,
focusNode: searchNodeFocusNode,
onChanged: (newString) {
setState(() => filter = newString);
},
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
searchNodeFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
), ),
), ),
materialTapTargetSize: suffixIcon: searchNodeController.text.isNotEmpty
MaterialTapTargetSize.shrinkWrap, ? Padding(
onPressed: () { padding: const EdgeInsets.only(right: 0),
showDialog<void>( child: UnconstrainedBox(
context: context, child: Row(
builder: (context) => Navigator(
initialRoute: CoinNodesView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
CoinNodesView(
coin: coin,
rootNavigator: true,
),
const RouteSettings(
name: CoinNodesView.routeName,
),
),
];
},
),
);
},
child: Padding(
padding: const EdgeInsets.all(
12.0,
),
child: Row(
children: [
Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 24,
height: 24,
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( TextFieldIconButton(
"${coin.prettyName} nodes", child: const XIcon(),
style: STextStyles.titleBold12( onTap: () async {
context), setState(() {
), searchNodeController.text = "";
Text( filter = "";
count > 1 });
? "$count nodes" },
: "Default",
style: STextStyles.label(context),
), ),
], ],
), ),
],
),
Expanded(
child: SvgPicture.asset(
Assets.svg.chevronRight,
alignment: Alignment.centerRight,
), ),
)
: null,
),
),
),
],
),
const SizedBox(
height: 20,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: ListView.separated(
controller: nodeScrollController,
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
primary: false,
shrinkWrap: true,
itemBuilder: (context, index) {
final coin = coins[index];
final count = ref
.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodesFor(coin)))
.length;
return Padding(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onPressed: () {
showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: CoinNodesView.routeName,
onGenerateRoute:
RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
CoinNodesView(
coin: coin,
rootNavigator: true,
),
const RouteSettings(
name: CoinNodesView.routeName,
),
),
];
},
), ),
], );
},
child: Padding(
padding: const EdgeInsets.all(
12.0,
),
child: Row(
children: [
Row(
children: [
SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 24,
height: 24,
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"${coin.prettyName} nodes",
style: STextStyles.titleBold12(
context),
),
Text(
count > 1
? "$count nodes"
: "Default",
style: STextStyles.label(context),
),
],
),
],
),
Expanded(
child: SvgPicture.asset(
Assets.svg.chevronRight,
alignment: Alignment.centerRight,
),
),
],
),
), ),
), ),
), );
); },
}, separatorBuilder: (context, index) => Container(
separatorBuilder: (context, index) => Container( height: 1,
height: 1, color: Theme.of(context)
color: Theme.of(context) .extension<StackColors>()!
.extension<StackColors>()! .background,
.background, ),
itemCount: coins.length,
), ),
itemCount: coins.length,
), ),
), ),
), ],
], ),
), ),
), ),
), ),

View file

@ -139,392 +139,385 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
return Column( return Column(
children: [ children: [
Row( Padding(
children: [ padding: const EdgeInsets.only(
Expanded( right: 30,
child: RoundedWhiteContainer( ),
radiusMultiplier: 2, child: RoundedWhiteContainer(
padding: const EdgeInsets.all(24), // radiusMultiplier: 2,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset( Padding(
Assets.svg.circleLock, padding: const EdgeInsets.all(8.0),
width: 48, child: SvgPicture.asset(
height: 48, Assets.svg.circleLock,
), width: 48,
Column( height: 48,
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [
const SizedBox(
height: 16,
),
Text(
"Change Password",
style: STextStyles.desktopTextSmall(context),
),
const SizedBox(
height: 8,
),
Text(
"Protect your Stack Wallet with a strong password. Stack Wallet does not store "
"your password, and is therefore NOT able to restore it. Keep your password safe and secure.",
style:
STextStyles.desktopTextExtraExtraSmall(context),
),
const SizedBox(
height: 20,
),
changePassword
? SizedBox(
width: 512,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Current password",
style: STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key(
"desktopSecurityRestoreFromFilePasswordFieldKey"),
focusNode: passwordCurrentFocusNode,
controller: passwordCurrentController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Enter current password",
passwordCurrentFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"),
onTap: () async {
setState(() {
hidePassword =
!hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<
StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(height: 16),
Text(
"New password",
style: STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key(
"desktopSecurityCreateNewPasswordFieldKey1"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Enter new password",
passwordFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"desktopSecurityCreateNewPasswordButtonKey1"),
onTap: () async {
setState(() {
hidePassword =
!hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<
StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
passwordFeedback = "";
});
return;
}
final result =
zxcvbn.evaluate(newValue);
String suggestionsAndTips = "";
for (var sug in result
.feedback.suggestions!
.toSet()) {
suggestionsAndTips += "$sug\n";
}
suggestionsAndTips +=
result.feedback.warning!;
String feedback =
// "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
suggestionsAndTips;
passwordStrength = result.score! / 4;
// hack fix to format back string returned from zxcvbn
if (feedback
.contains("phrasesNo need")) {
feedback = feedback.replaceFirst(
"phrasesNo need",
"phrases\nNo need");
}
if (feedback.endsWith("\n")) {
feedback = feedback.substring(
0, feedback.length - 2);
}
setState(() {
passwordFeedback = feedback;
});
},
),
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: EdgeInsets.only(
left: 12,
right: 12,
top: passwordFeedback.isNotEmpty
? 4
: 0,
),
child: passwordFeedback.isNotEmpty
? Text(
passwordFeedback,
style: STextStyles.infoSmall(
context),
)
: null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.only(
left: 12,
right: 12,
top: 10,
),
child: ProgressBar(
key: const Key(
"desktopSecurityCreateStackBackUpProgressBar"),
width: 450,
height: 5,
fillColor: passwordStrength < 0.51
? Theme.of(context)
.extension<StackColors>()!
.accentColorRed
: passwordStrength < 1
? Theme.of(context)
.extension<StackColors>()!
.accentColorYellow
: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.buttonBackSecondary,
percent: passwordStrength < 0.25
? 0.03
: passwordStrength,
),
),
const SizedBox(height: 16),
Text(
"Confirm new password",
style: STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key(
"desktopSecurityCreateNewPasswordFieldKey2"),
focusNode: passwordRepeatFocusNode,
controller: passwordRepeatController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Confirm new password",
passwordRepeatFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"desktopSecurityCreateNewPasswordButtonKey2"),
onTap: () async {
setState(() {
hidePassword =
!hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<
StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(height: 20),
PrimaryButton(
width: 160,
desktopMed: true,
enabled: shouldEnableSave,
label: "Save changes",
onPressed: () async {
final didChangePW =
await attemptChangePW();
if (didChangePW) {
setState(() {
changePassword = false;
});
}
},
)
],
),
)
: PrimaryButton(
width: 210,
desktopMed: true,
enabled: true,
label: "Set up new password",
onPressed: () {
setState(() {
changePassword = true;
});
},
),
],
),
],
), ),
), Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Change Password",
style: STextStyles.desktopTextSmall(context),
),
const SizedBox(
height: 16,
),
Text(
"Protect your Stack Wallet with a strong password. Stack Wallet does not store "
"your password, and is therefore NOT able to restore it. Keep your password safe and secure.",
style: STextStyles.desktopTextExtraExtraSmall(context),
),
const SizedBox(
height: 20,
),
changePassword
? SizedBox(
width: 512,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Current password",
style:
STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key(
"desktopSecurityRestoreFromFilePasswordFieldKey"),
focusNode: passwordCurrentFocusNode,
controller: passwordCurrentController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Enter current password",
passwordCurrentFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"desktopSecurityRestoreFromFilePasswordFieldShowPasswordButtonKey"),
onTap: () async {
setState(() {
hidePassword =
!hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(height: 16),
Text(
"New password",
style:
STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key(
"desktopSecurityCreateNewPasswordFieldKey1"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Enter new password",
passwordFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"desktopSecurityCreateNewPasswordButtonKey1"),
onTap: () async {
setState(() {
hidePassword =
!hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
passwordFeedback = "";
});
return;
}
final result =
zxcvbn.evaluate(newValue);
String suggestionsAndTips = "";
for (var sug in result
.feedback.suggestions!
.toSet()) {
suggestionsAndTips += "$sug\n";
}
suggestionsAndTips +=
result.feedback.warning!;
String feedback =
// "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
suggestionsAndTips;
passwordStrength = result.score! / 4;
// hack fix to format back string returned from zxcvbn
if (feedback
.contains("phrasesNo need")) {
feedback = feedback.replaceFirst(
"phrasesNo need",
"phrases\nNo need");
}
if (feedback.endsWith("\n")) {
feedback = feedback.substring(
0, feedback.length - 2);
}
setState(() {
passwordFeedback = feedback;
});
},
),
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: EdgeInsets.only(
left: 12,
right: 12,
top:
passwordFeedback.isNotEmpty ? 4 : 0,
),
child: passwordFeedback.isNotEmpty
? Text(
passwordFeedback,
style: STextStyles.infoSmall(
context),
)
: null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.only(
left: 12,
right: 12,
top: 10,
),
child: ProgressBar(
key: const Key(
"desktopSecurityCreateStackBackUpProgressBar"),
width: 450,
height: 5,
fillColor: passwordStrength < 0.51
? Theme.of(context)
.extension<StackColors>()!
.accentColorRed
: passwordStrength < 1
? Theme.of(context)
.extension<StackColors>()!
.accentColorYellow
: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.buttonBackSecondary,
percent: passwordStrength < 0.25
? 0.03
: passwordStrength,
),
),
const SizedBox(height: 16),
Text(
"Confirm new password",
style:
STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key(
"desktopSecurityCreateNewPasswordFieldKey2"),
focusNode: passwordRepeatFocusNode,
controller: passwordRepeatController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Confirm new password",
passwordRepeatFocusNode,
context,
).copyWith(
labelStyle:
STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
GestureDetector(
key: const Key(
"desktopSecurityCreateNewPasswordButtonKey2"),
onTap: () async {
setState(() {
hidePassword =
!hidePassword;
});
},
child: SvgPicture.asset(
hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(height: 20),
PrimaryButton(
width: 160,
desktopMed: true,
enabled: shouldEnableSave,
label: "Save changes",
onPressed: () async {
final didChangePW =
await attemptChangePW();
if (didChangePW) {
setState(() {
changePassword = false;
});
}
},
)
],
),
)
: PrimaryButton(
width: 210,
desktopMed: true,
enabled: true,
label: "Set up new password",
onPressed: () {
setState(() {
changePassword = true;
});
},
),
],
),
),
],
), ),
const SizedBox( ),
width: 40,
),
],
), ),
], ],
); );

View file

@ -2,13 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import '../../../pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart';
class SyncingPreferencesSettings extends ConsumerStatefulWidget { class SyncingPreferencesSettings extends ConsumerStatefulWidget {
const SyncingPreferencesSettings({Key? key}) : super(key: key); const SyncingPreferencesSettings({Key? key}) : super(key: key);
@ -34,10 +33,13 @@ class _SyncingPreferencesSettings
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset( Padding(
Assets.svg.circleArrowRotate, padding: const EdgeInsets.all(8.0),
width: 48, child: SvgPicture.asset(
height: 48, Assets.svg.circleArrowRotate,
width: 48,
height: 48,
),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -54,7 +56,7 @@ class _SyncingPreferencesSettings
), ),
TextSpan( TextSpan(
text: text:
"\nSet up your syncing preferences for all wallets in your Stack.", "\n\nSet up your syncing preferences for all wallets in your Stack.",
style: STextStyles.desktopTextExtraExtraSmall( style: STextStyles.desktopTextExtraExtraSmall(
context), context),
), ),
@ -67,18 +69,24 @@ class _SyncingPreferencesSettings
///TODO: ONLY SHOW SYNC OPTIONS ON BUTTON PRESS ///TODO: ONLY SHOW SYNC OPTIONS ON BUTTON PRESS
Column( Column(
children: [ children: const [
SyncingOptionsView(), SyncingOptionsView(),
], ],
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: const [ children: [
Padding( Padding(
padding: EdgeInsets.all( padding: const EdgeInsets.all(
10, 10,
), ),
child: ChangePrefButton(), child: PrimaryButton(
width: 210,
desktopMed: true,
enabled: true,
label: "Change preferences",
onPressed: () {},
),
), ),
], ],
), ),
@ -90,26 +98,3 @@ class _SyncingPreferencesSettings
); );
} }
} }
class ChangePrefButton extends ConsumerWidget {
const ChangePrefButton({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return SizedBox(
width: 200,
height: 48,
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: () {},
child: Text(
"Change preferences",
style: STextStyles.button(context),
),
),
);
}
}

View file

@ -59,6 +59,7 @@ class _SVG {
String txExchangeFailed(BuildContext context) => String txExchangeFailed(BuildContext context) =>
"assets/svg/${Theme.of(context).extension<StackColors>()!.themeType.name}/tx-exchange-icon-failed.svg"; "assets/svg/${Theme.of(context).extension<StackColors>()!.themeType.name}/tx-exchange-icon-failed.svg";
String get circleSliders => "assets/svg/configuration.svg";
String get circlePlus => "assets/svg/plus-circle.svg"; String get circlePlus => "assets/svg/plus-circle.svg";
String get framedGear => "assets/svg/framed-gear.svg"; String get framedGear => "assets/svg/framed-gear.svg";
String get framedAddressBook => "assets/svg/framed-address-book.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg";

View file

@ -9,7 +9,6 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
class AddressBookCard extends ConsumerStatefulWidget { class AddressBookCard extends ConsumerStatefulWidget {
@ -59,111 +58,108 @@ class _AddressBookCardState extends ConsumerState<AddressBookCard> {
} }
} }
return ConditionalParent( return RoundedWhiteContainer(
condition: !isDesktop, padding: const EdgeInsets.all(4),
child: Row( child: RawMaterialButton(
children: [ // splashColor: Theme.of(context).extension<StackColors>()!.highlight,
Container( padding: const EdgeInsets.all(0),
width: 32, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
height: 32, shape: RoundedRectangleBorder(
decoration: BoxDecoration( borderRadius: BorderRadius.circular(
color: contact.id == "default" Constants.size.circularBorderRadius,
? Theme.of(context) ),
.extension<StackColors>()! ),
.myStackContactIconBG onPressed: () {
: Theme.of(context) showDialog<void>(
.extension<StackColors>()! context: context,
.textFieldDefaultBG, useSafeArea: true,
borderRadius: BorderRadius.circular(32), barrierDismissible: true,
builder: (_) => ContactPopUp(
contactId: contact.id,
), ),
child: contact.id == "default" );
? Center( },
child: SvgPicture.asset( child: Padding(
Assets.svg.stackIcon(context), padding: const EdgeInsets.all(8.0),
width: 20, child: Row(
), children: [
) Container(
: contact.emojiChar != null width: 32,
height: 32,
decoration: BoxDecoration(
color: contact.id == "default"
? Theme.of(context)
.extension<StackColors>()!
.myStackContactIconBG
: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(32),
),
child: contact.id == "default"
? Center( ? Center(
child: Text(contact.emojiChar!),
)
: Center(
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.svg.user, Assets.svg.stackIcon(context),
width: 18, width: 20,
), ),
), )
), : contact.emojiChar != null
const SizedBox( ? Center(
width: 12, child: Text(contact.emojiChar!),
), )
if (isDesktop) : Center(
Text( child: SvgPicture.asset(
contact.name, Assets.svg.user,
style: STextStyles.itemSubtitle12(context), width: 18,
), ),
if (isDesktop) ),
const SizedBox( ),
width: 16, const SizedBox(
), width: 12,
if (isDesktop) ),
Text( if (isDesktop)
coinsString,
style: STextStyles.label(context),
),
if (!isDesktop)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text( Text(
contact.name, contact.name,
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
if (isDesktop)
const SizedBox( const SizedBox(
height: 4, width: 16,
), ),
if (isDesktop)
Text( Text(
coinsString, coinsString,
style: STextStyles.label(context), style: STextStyles.label(context),
), ),
], if (!isDesktop)
), Column(
if (isDesktop) const Spacer(), crossAxisAlignment: CrossAxisAlignment.start,
if (isDesktop) children: [
SvgPicture.asset( Text(
widget.indicatorDown == true contact.name,
? Assets.svg.chevronDown style: STextStyles.itemSubtitle12(context),
: Assets.svg.chevronUp, ),
width: 10, const SizedBox(
height: 5, height: 4,
color: Theme.of(context).extension<StackColors>()!.textSubtitle2, ),
), Text(
], coinsString,
), style: STextStyles.label(context),
builder: (child) => RoundedWhiteContainer( ),
padding: const EdgeInsets.all(4), ],
child: RawMaterialButton( ),
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, if (isDesktop) const Spacer(),
padding: const EdgeInsets.all(0), // if (isDesktop)
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, // SvgPicture.asset(
shape: RoundedRectangleBorder( // widget.indicatorDown == true
borderRadius: BorderRadius.circular( // ? Assets.svg.chevronDown
Constants.size.circularBorderRadius, // : Assets.svg.chevronUp,
), // width: 10,
), // height: 5,
onPressed: () { // color:
showDialog<void>( // Theme.of(context).extension<StackColors>()!.textSubtitle2,
context: context, // ),
useSafeArea: true, ],
barrierDismissible: true,
builder: (_) => ContactPopUp(
contactId: contact.id,
),
);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
), ),
), ),
), ),

View file

@ -89,11 +89,14 @@ class _ExpandableState extends State<Expandable> with TickerProviderStateMixin {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
GestureDetector( MouseRegion(
onTap: toggle, cursor: SystemMouseCursors.click,
child: Container( child: GestureDetector(
color: Colors.transparent, onTap: toggle,
child: widget.header, child: Container(
color: Colors.transparent,
child: widget.header,
),
), ),
), ),
SizeTransition( SizeTransition(

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stackwallet/widgets/table_view/table_view_row.dart';
class TableView extends StatefulWidget { class TableView extends StatefulWidget {
const TableView({ const TableView({
@ -9,7 +8,7 @@ class TableView extends StatefulWidget {
this.shrinkWrap = false, this.shrinkWrap = false,
}) : super(key: key); }) : super(key: key);
final List<TableViewRow> rows; final List<Widget> rows;
final double rowSpacing; final double rowSpacing;
final bool shrinkWrap; final bool shrinkWrap;

View file

@ -26,53 +26,58 @@ class WalletInfoRow extends ConsumerWidget {
.getManagerProvider(walletId)); .getManagerProvider(walletId));
if (Util.isDesktop) { if (Util.isDesktop) {
return GestureDetector( return MouseRegion(
onTap: onPressed, cursor: SystemMouseCursors.click,
child: Container( child: GestureDetector(
color: Colors.transparent, onTap: onPressed,
child: Row( child: Container(
children: [ color: Colors.transparent,
Expanded( child: Row(
flex: 4, children: [
child: Row( Expanded(
children: [ flex: 4,
WalletInfoCoinIcon(coin: manager.coin), child: Row(
const SizedBox( children: [
width: 12, WalletInfoCoinIcon(coin: manager.coin),
), const SizedBox(
Text( width: 12,
manager.walletName,
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.textDark,
), ),
), Text(
], manager.walletName,
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
],
),
), ),
), Expanded(
Expanded( flex: 4,
flex: 4, child: WalletInfoRowBalanceFuture(
child: WalletInfoRowBalanceFuture( walletId: walletId,
walletId: walletId, ),
), ),
), Expanded(
Expanded( flex: 6,
flex: 6, child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end, children: [
children: [ SvgPicture.asset(
SvgPicture.asset( Assets.svg.chevronRight,
Assets.svg.chevronRight, width: 20,
width: 20, height: 20,
height: 20, color: Theme.of(context)
color: Theme.of(context) .extension<StackColors>()!
.extension<StackColors>()! .textSubtitle1,
.textSubtitle1, )
) ],
], ),
), )
) ],
], ),
), ),
), ),
); );

View file

@ -315,6 +315,7 @@ flutter:
- assets/svg/keys.svg - assets/svg/keys.svg
- assets/svg/arrow-down.svg - assets/svg/arrow-down.svg
- assets/svg/plus-circle.svg - assets/svg/plus-circle.svg
- assets/svg/configuration.svg
# coin icons # coin icons
- assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Bitcoin.svg
- assets/svg/coin_icons/Litecoin.svg - assets/svg/coin_icons/Litecoin.svg

View file

@ -70,7 +70,7 @@ void main() {
await widgetTester.tap(find.byType(RawMaterialButton)); await widgetTester.tap(find.byType(RawMaterialButton));
expect(find.byType(ContactPopUp), findsOneWidget); expect(find.byType(ContactPopUp), findsOneWidget);
} else if (Util.isDesktop) { } else if (Util.isDesktop) {
expect(find.byType(RawMaterialButton), findsNothing); expect(find.byType(RawMaterialButton), findsOneWidget);
} }
}); });
} }