mobile address list changes as per figma

This commit is contained in:
julian 2024-04-24 17:17:29 -06:00
parent 53b849c8c5
commit 420c73ed5d
4 changed files with 394 additions and 140 deletions

View file

@ -10,31 +10,46 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_buttons/simple_edit_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class AddressCard extends ConsumerStatefulWidget {
const AddressCard({
Key? key,
super.key,
required this.addressId,
required this.walletId,
required this.coin,
this.onPressed,
this.clipboard = const ClipboardWrapper(),
}) : super(key: key);
});
final int addressId;
final String walletId;
@ -47,6 +62,7 @@ class AddressCard extends ConsumerStatefulWidget {
}
class _AddressCardState extends ConsumerState<AddressCard> {
final _qrKey = GlobalKey();
final isDesktop = Util.isDesktop;
late Stream<AddressLabel?> stream;
@ -54,6 +70,72 @@ class _AddressCardState extends ConsumerState<AddressCard> {
AddressLabel? label;
Future<void> _capturePng(bool shouldSaveInsteadOfShare) async {
try {
final RenderRepaintBoundary boundary =
_qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage();
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
if (shouldSaveInsteadOfShare) {
if (Util.isDesktop) {
final dir = Directory("${Platform.environment['HOME']}");
if (!dir.existsSync()) {
throw Exception(
"Home dir not found while trying to open filepicker on QR image save");
}
final path = await FilePicker.platform.saveFile(
fileName: "qrcode.png",
initialDirectory: dir.path,
);
if (path != null) {
final file = File(path);
if (file.existsSync()) {
if (mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.warning,
message: "$path already exists!",
context: context,
),
);
}
} else {
await file.writeAsBytes(pngBytes);
if (mounted) {
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) {
//todo: comeback to this
debugPrint(e.toString());
}
}
@override
void initState() {
address = MainDB.instance.isar.addresses
@ -117,13 +199,29 @@ class _AddressCardState extends ConsumerState<AddressCard> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (label!.value.isNotEmpty)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label!.value,
label!.value.isNotEmpty ? label!.value : "No label",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
),
if (label!.value.isNotEmpty)
SimpleEditButton(
editValue: label!.value,
editLabel: 'label',
overrideTitle: 'Edit label',
disableIcon: true,
onValueChanged: (value) {
MainDB.instance.putAddressLabel(
label!.copyWith(
label: value,
),
);
},
),
],
),
SizedBox(
height: isDesktop ? 2 : 8,
),
@ -140,18 +238,152 @@ class _AddressCardState extends ConsumerState<AddressCard> {
const SizedBox(
height: 10,
),
if (label!.tags != null && label!.tags!.isNotEmpty)
Wrap(
spacing: 10,
runSpacing: 10,
children: label!.tags!
.map(
(e) => AddressTag(
tag: e,
Row(
children: [
CustomTextButton(
text: "Copy address",
onTap: () {
widget.clipboard
.setData(
ClipboardData(
text: address.value,
),
)
.toList(),
.then((value) {
if (context.mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
context: context,
),
);
}
});
},
),
const SizedBox(
width: 16,
),
CustomTextButton(
text: "Show QR code",
onTap: () async {
await showDialog<void>(
context: context,
builder: (_) {
return StackDialogBase(
child: Column(
children: [
if (label!.value.isNotEmpty)
Text(
label!.value,
style: STextStyles.w600_18(context),
),
if (label!.value.isNotEmpty)
const SizedBox(
height: 8,
),
Text(
address.value,
style:
STextStyles.w500_16(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
const SizedBox(
height: 16,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: QrImageView(
data: AddressUtils.buildUriString(
widget.coin,
address.value,
{},
),
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
const SizedBox(
height: 16,
),
Row(
children: [
if (!isDesktop)
Expanded(
child: SecondaryButton(
label: "Share",
buttonHeight: isDesktop
? ButtonHeight.l
: null,
icon: SvgPicture.asset(
Assets.svg.share,
width: 14,
height: 14,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
onPressed: () async {
await _capturePng(false);
},
),
),
if (isDesktop)
Expanded(
child: PrimaryButton(
buttonHeight: isDesktop
? ButtonHeight.l
: null,
onPressed: () async {
// TODO: add save functionality instead of share
// save works on linux at the moment
await _capturePng(true);
},
label: "Save",
icon: SvgPicture.asset(
Assets.svg.arrowDown,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary,
),
),
),
],
)
],
),
);
},
);
},
),
],
),
// if (label!.tags != null && label!.tags!.isNotEmpty)
// Wrap(
// spacing: 10,
// runSpacing: 10,
// children: label!.tags!
// .map(
// (e) => AddressTag(
// tag: e,
// ),
// )
// .toList(),
// ),
],
),
);

View file

@ -10,14 +10,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_card.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
@ -25,13 +23,8 @@ import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:tuple/tuple.dart';
import '../../../utilities/assets.dart';
import '../../../widgets/icon_widgets/x_icon.dart';
import '../../../widgets/textfield_icon_button.dart';
class WalletAddressesView extends ConsumerStatefulWidget {
const WalletAddressesView({
Key? key,
@ -50,10 +43,10 @@ class WalletAddressesView extends ConsumerStatefulWidget {
class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
final bool isDesktop = Util.isDesktop;
String _searchString = "";
final String _searchString = "";
late final TextEditingController _searchController;
final searchFieldFocusNode = FocusNode();
// late final TextEditingController _searchController;
// final searchFieldFocusNode = FocusNode();
Future<List<int>> _search(String term) async {
if (term.isEmpty) {
@ -119,19 +112,19 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
.findAll();
}
@override
void initState() {
_searchController = TextEditingController();
super.initState();
}
@override
void dispose() {
_searchController.dispose();
searchFieldFocusNode.dispose();
super.dispose();
}
// @override
// void initState() {
// _searchController = TextEditingController();
//
// super.initState();
// }
//
// @override
// void dispose() {
// _searchController.dispose();
// searchFieldFocusNode.dispose();
// super.dispose();
// }
@override
Widget build(BuildContext context) {
@ -165,74 +158,74 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
),
child: Column(
children: [
SizedBox(
width: isDesktop ? 490 : null,
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: searchFieldFocusNode,
onChanged: (value) {
setState(() {
_searchString = value;
});
},
style: isDesktop
? STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
height: 1.8,
)
: STextStyles.field(context),
decoration: standardInputDecoration(
"Search...",
searchFieldFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: EdgeInsets.symmetric(
horizontal: isDesktop ? 12 : 10,
vertical: isDesktop ? 18 : 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: isDesktop ? 20 : 16,
height: isDesktop ? 20 : 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 = "";
_searchString = "";
});
},
),
],
),
),
)
: null,
),
),
),
),
SizedBox(
height: isDesktop ? 20 : 16,
),
// SizedBox(
// width: isDesktop ? 490 : null,
// child: ClipRRect(
// borderRadius: BorderRadius.circular(
// Constants.size.circularBorderRadius,
// ),
// child: TextField(
// autocorrect: !isDesktop,
// enableSuggestions: !isDesktop,
// controller: _searchController,
// focusNode: searchFieldFocusNode,
// onChanged: (value) {
// setState(() {
// _searchString = value;
// });
// },
// style: isDesktop
// ? STextStyles.desktopTextExtraSmall(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .textFieldActiveText,
// height: 1.8,
// )
// : STextStyles.field(context),
// decoration: standardInputDecoration(
// "Search...",
// searchFieldFocusNode,
// context,
// desktopMed: isDesktop,
// ).copyWith(
// prefixIcon: Padding(
// padding: EdgeInsets.symmetric(
// horizontal: isDesktop ? 12 : 10,
// vertical: isDesktop ? 18 : 16,
// ),
// child: SvgPicture.asset(
// Assets.svg.search,
// width: isDesktop ? 20 : 16,
// height: isDesktop ? 20 : 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 = "";
// _searchString = "";
// });
// },
// ),
// ],
// ),
// ),
// )
// : null,
// ),
// ),
// ),
// ),
// SizedBox(
// height: isDesktop ? 20 : 16,
// ),
Expanded(
child: FutureBuilder(
future: _search(_searchString),
@ -249,7 +242,9 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
walletId: widget.walletId,
addressId: snapshot.data![index],
coin: coin,
onPressed: () {
onPressed: !isDesktop
? null
: () {
Navigator.of(context).pushNamed(
AddressDetailsView.routeName,
arguments: Tuple2(

View file

@ -317,6 +317,28 @@ class STextStyles {
}
}
static TextStyle w600_18(BuildContext context) {
switch (_theme(context).themeId) {
default:
return GoogleFonts.inter(
color: _theme(context).textDark,
fontWeight: FontWeight.w600,
fontSize: 18,
);
}
}
static TextStyle w500_16(BuildContext context) {
switch (_theme(context).themeId) {
default:
return GoogleFonts.inter(
color: _theme(context).textDark,
fontWeight: FontWeight.w500,
fontSize: 16,
);
}
}
static TextStyle w500_14(BuildContext context) {
switch (_theme(context).themeId) {
default:

View file

@ -15,16 +15,17 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/icon_widgets/pencil_icon.dart';
import 'package:tuple/tuple.dart';
import '../desktop/desktop_dialog.dart';
import '../icon_widgets/pencil_icon.dart';
class SimpleEditButton extends StatelessWidget {
const SimpleEditButton({
Key? key,
super.key,
this.editValue,
this.editLabel,
this.overrideTitle,
this.disableIcon = false,
this.onValueChanged,
this.onPressedOverride,
}) : assert(
@ -33,11 +34,12 @@ class SimpleEditButton extends StatelessWidget {
editValue == null &&
onValueChanged == null &&
onPressedOverride != null),
),
super(key: key);
);
final String? editValue;
final String? editLabel;
final String? overrideTitle;
final bool disableIcon;
final void Function(String)? onValueChanged;
final VoidCallback? onPressedOverride;
@ -101,17 +103,20 @@ class SimpleEditButton extends StatelessWidget {
},
child: Row(
children: [
if (!disableIcon)
SvgPicture.asset(
Assets.svg.pencil,
width: 10,
height: 10,
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
color:
Theme.of(context).extension<StackColors>()!.infoItemIcons,
),
if (!disableIcon)
const SizedBox(
width: 4,
),
Text(
"Edit",
overrideTitle ?? "Edit",
style: STextStyles.link2(context),
),
],