add (follow) new paynym view

This commit is contained in:
julian 2022-12-21 13:46:50 -06:00
parent 08ad23cc73
commit 2297fbf028
10 changed files with 460 additions and 75 deletions

View file

@ -0,0 +1,306 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/paynym/paynym_account.dart';
import 'package:stackwallet/pages/paynym/subwidgets/featured_paynyms_widget.dart';
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart';
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.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/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class AddNewPaynymFollowView extends ConsumerStatefulWidget {
const AddNewPaynymFollowView({
Key? key,
required this.walletId,
required this.nymAccount,
}) : super(key: key);
final String walletId;
final PaynymAccount nymAccount;
static const String routeName = "/addNewPaynymFollow";
@override
ConsumerState<AddNewPaynymFollowView> createState() =>
_AddNewPaynymFollowViewState();
}
class _AddNewPaynymFollowViewState
extends ConsumerState<AddNewPaynymFollowView> {
late final TextEditingController _searchController;
late final FocusNode searchFieldFocusNode;
String _searchString = "";
bool _didSearch = false;
PaynymAccount? _searchResult;
Future<void> _search() async {
_didSearch = true;
bool didPopLoading = false;
unawaited(
showDialog<void>(
barrierDismissible: false,
context: context,
builder: (context) => const LoadingIndicator(
width: 200,
),
).then((_) => didPopLoading = true),
);
final paynymAccount =
await ref.read(paynymAPIProvider).nym(_searchString, true);
if (mounted) {
if (!didPopLoading) {
Navigator.of(context).pop();
}
setState(() {
_searchResult = paynymAccount;
});
}
}
@override
void initState() {
_searchController = TextEditingController();
searchFieldFocusNode = FocusNode();
super.initState();
}
@override
void dispose() {
_searchController.dispose();
searchFieldFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final isDesktop = Util.isDesktop;
return MasterScaffold(
isDesktop: isDesktop,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
titleSpacing: 0,
title: Text(
"Add new",
style: STextStyles.navBarTitle(context),
overflow: TextOverflow.ellipsis,
),
),
body: ConditionalParent(
condition: !isDesktop,
builder: (child) => SafeArea(
child: LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: child,
),
),
),
),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 10,
),
Text(
"Featured PayNyms",
style: STextStyles.sectionLabelMedium12(context),
),
const SizedBox(
height: 12,
),
const FeaturedPaynymsWidget(),
const SizedBox(
height: 24,
),
Text(
"Add new",
style: STextStyles.sectionLabelMedium12(context),
),
const SizedBox(
height: 12,
),
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(
"Paste payment code",
searchFieldFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 8),
child: UnconstrainedBox(
child: Row(
children: [
_searchController.text.isNotEmpty
? TextFieldIconButton(
child: const XIcon(),
onTap: () async {
_searchString = "";
setState(() {
_searchController.text = "";
});
},
)
: TextFieldIconButton(
key: const Key(
"paynymPasteAddressFieldButtonKey"),
onTap: () async {
final ClipboardData? data =
await Clipboard.getData(
Clipboard.kTextPlain);
if (data?.text != null &&
data!.text!.isNotEmpty) {
String content = data.text!.trim();
if (content.contains("\n")) {
content = content.substring(
0,
content.indexOf(
"\n",
),
);
}
_searchString = content;
setState(() {
_searchController.text = content;
_searchController.selection =
TextSelection.collapsed(
offset: content.length,
);
});
}
},
child: const ClipboardIcon(),
),
TextFieldIconButton(
key: const Key("paynymScanQrButtonKey"),
onTap: () async {
try {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
final qrResult =
await const BarcodeScannerWrapper()
.scan();
final pCodeString = qrResult.rawContent;
_searchString = pCodeString;
setState(() {
_searchController.text = pCodeString;
_searchController.selection =
TextSelection.collapsed(
offset: pCodeString.length,
);
});
} catch (_) {
// scan failed
}
},
child: const QrCodeIcon(),
)
],
),
),
),
),
),
),
const SizedBox(
height: 12,
),
SecondaryButton(
label: "Search",
onPressed: _search,
),
if (_didSearch)
const SizedBox(
height: 20,
),
if (_didSearch && _searchResult == null)
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Nothing found. Please check the payment code.",
style: STextStyles.label(context),
),
],
),
),
if (_didSearch && _searchResult != null)
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: PaynymCard(
label: _searchResult!.nymName,
paymentCodeString: _searchResult!.codes.first.code,
),
),
],
),
),
),
);
}
}

View file

@ -87,6 +87,7 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
unawaited(
showDialog<bool?>(
context: context,
barrierDismissible: false,
builder: (context) => const ClaimingPaynymDialog(),
).then((value) => shouldCancel = value == true),
);

View file

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/paynym/paynym_account.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
import 'package:stackwallet/pages/paynym/dialogs/paynym_qr_popup.dart';
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
import 'package:stackwallet/utilities/assets.dart';
@ -21,16 +22,17 @@ import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/share_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/toggle.dart';
import 'package:tuple/tuple.dart';
class PaynymHomeView extends StatefulWidget {
const PaynymHomeView({
Key? key,
required this.walletId,
required this.nymAccount,
required this.paynymAccount,
}) : super(key: key);
final String walletId;
final PaynymAccount nymAccount;
final PaynymAccount paynymAccount;
static const String routeName = "/paynymHome";
@ -73,7 +75,13 @@ class _PaynymHomeViewState extends State<PaynymHomeView> {
color: Theme.of(context).extension<StackColors>()!.textDark,
),
onPressed: () {
// todo add ?
Navigator.of(context).pushNamed(
AddNewPaynymFollowView.routeName,
arguments: Tuple2(
widget.walletId,
widget.paynymAccount,
),
);
},
),
),
@ -90,7 +98,7 @@ class _PaynymHomeViewState extends State<PaynymHomeView> {
color: Theme.of(context).extension<StackColors>()!.textDark,
),
onPressed: () {
// todo add ?
// todo info ?
},
),
),
@ -107,20 +115,20 @@ class _PaynymHomeViewState extends State<PaynymHomeView> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
PayNymBot(
paymentCodeString: widget.nymAccount.codes.first.code,
paymentCodeString: widget.paynymAccount.codes.first.code,
),
const SizedBox(
height: 10,
),
Text(
widget.nymAccount.nymName,
widget.paynymAccount.nymName,
style: STextStyles.desktopMenuItemSelected(context),
),
const SizedBox(
height: 4,
),
Text(
Format.shorten(widget.nymAccount.codes.first.code, 12, 5),
Format.shorten(widget.paynymAccount.codes.first.code, 12, 5),
style: STextStyles.label(context),
),
const SizedBox(
@ -143,7 +151,7 @@ class _PaynymHomeViewState extends State<PaynymHomeView> {
onPressed: () async {
await Clipboard.setData(
ClipboardData(
text: widget.nymAccount.codes.first.code,
text: widget.paynymAccount.codes.first.code,
),
);
unawaited(
@ -196,7 +204,7 @@ class _PaynymHomeViewState extends State<PaynymHomeView> {
showDialog<void>(
context: context,
builder: (context) => PaynymQrPopup(
paynymAccount: widget.nymAccount,
paynymAccount: widget.paynymAccount,
),
);
},

View file

@ -1,10 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart';
import 'package:stackwallet/utilities/featured_paynyms.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/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class FeaturedPaynymsWidget extends StatelessWidget {
@ -28,50 +25,10 @@ class FeaturedPaynymsWidget extends StatelessWidget {
.backgroundAppBar,
height: 1,
),
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
PayNymBot(
size: 32,
PaynymCard(
label: entries[i].key,
paymentCodeString: entries[i].value,
),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
entries[i].key,
style: STextStyles.w500_12(context),
),
const SizedBox(
height: 2,
),
Text(
Format.shorten(entries[i].value, 12, 5),
style: STextStyles.w500_12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
PrimaryButton(
width: 84,
buttonHeight: ButtonHeight.l,
label: "Follow",
onPressed: () {
// todo : follow
},
)
],
),
),
],
),
],

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
class PayNymBot extends StatelessWidget {
const PayNymBot({
@ -15,22 +14,14 @@ class PayNymBot extends StatelessWidget {
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(size / 2),
child: Stack(
children: [
SizedBox(
width: size,
height: size,
child: const LoadingIndicator(),
),
SizedBox(
child: SizedBox(
width: size,
height: size,
child: Image.network(
"https://paynym.is/$paymentCodeString/avatar",
// todo: loading indicator that doesn't lag
),
),
],
),
);
}
}

View file

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.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/desktop/primary_button.dart';
class PaynymCard extends StatefulWidget {
const PaynymCard({
Key? key,
required this.label,
required this.paymentCodeString,
}) : super(key: key);
final String label;
final String paymentCodeString;
@override
State<PaynymCard> createState() => _PaynymCardState();
}
class _PaynymCardState extends State<PaynymCard> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
PayNymBot(
size: 32,
paymentCodeString: widget.paymentCodeString,
),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.label,
style: STextStyles.w500_12(context),
),
const SizedBox(
height: 2,
),
Text(
Format.shorten(widget.paymentCodeString, 12, 5),
style: STextStyles.w500_12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
PrimaryButton(
width: 84,
buttonHeight: ButtonHeight.l,
label: "Follow",
onPressed: () {
// todo : follow
},
)
],
),
);
}
}

View file

@ -37,6 +37,7 @@ import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/intro_view.dart';
import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart';
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
@ -209,6 +210,21 @@ class RouteGenerator {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => PaynymHomeView(
walletId: args.item1,
paynymAccount: args.item2,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case AddNewPaynymFollowView.routeName:
if (args is Tuple2<String, PaynymAccount>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => AddNewPaynymFollowView(
walletId: args.item1,
nymAccount: args.item2,
),

View file

@ -173,10 +173,13 @@ class PaynymAPI {
// | 200 | Nym found and returned |
// | 404 | Nym not found |
// | 400 | Bad request |
Future<PaynymAccount?> nym(String code) async {
final map = await _post("/nym", {"nym": code});
Future<PaynymAccount?> nym(String code, [bool compact = false]) async {
final Map<String, dynamic> requestBody = {"nym": code};
if (compact) {
requestBody["compact"] = true;
}
try {
final map = await _post("/nym", requestBody);
return PaynymAccount.fromMap(map);
} catch (_) {
return null;

View file

@ -7,6 +7,29 @@ class STextStyles {
static StackColors _theme(BuildContext context) =>
Theme.of(context).extension<StackColors>()!;
static TextStyle sectionLabelMedium12(BuildContext context) {
switch (_theme(context).themeType) {
case ThemeType.light:
return GoogleFonts.inter(
color: _theme(context).textDark3,
fontWeight: FontWeight.w500,
fontSize: 12,
);
case ThemeType.oceanBreeze:
return GoogleFonts.inter(
color: _theme(context).textDark3,
fontWeight: FontWeight.w500,
fontSize: 12,
);
case ThemeType.dark:
return GoogleFonts.inter(
color: _theme(context).textDark3,
fontWeight: FontWeight.w500,
fontSize: 12,
);
}
}
static TextStyle pageTitleH1(BuildContext context) {
switch (_theme(context).themeType) {
case ThemeType.light:

View file

@ -63,6 +63,16 @@ class PrimaryButton extends StatelessWidget {
: STextStyles.desktopButtonDisabled(context);
}
} else {
if (buttonHeight == ButtonHeight.l) {
return STextStyles.button(context).copyWith(
fontSize: 10,
color: enabled
? Theme.of(context).extension<StackColors>()!.buttonTextPrimary
: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimaryDisabled,
);
}
return STextStyles.button(context).copyWith(
color: enabled
? Theme.of(context).extension<StackColors>()!.buttonTextPrimary