From da398ee0c85becf3ba8edc29e196a18aef022712 Mon Sep 17 00:00:00 2001 From: Dan Lopez Date: Thu, 12 Oct 2023 00:47:34 -0500 Subject: [PATCH] feat: get and show user nfts --- android/app/src/main/AndroidManifest.xml | 1 + lib/pages/generate_phrase.dart | 117 +++++++++--------- lib/pages/home.dart | 2 +- lib/pages/input_phrase.dart | 92 +++++++------- lib/pages/my-dinogrow/my_dinogrow.dart | 119 ++++++++++++++++--- lib/pages/setup_account.dart | 37 +++--- lib/ui/widgets/GameCard/game_card.dart | 27 ++++- lib/ui/widgets/IntroButton/intro_button.dart | 6 +- pubspec.lock | 2 +- pubspec.yaml | 1 + 10 files changed, 263 insertions(+), 141 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3e5c454..8ae31bc 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + { fit: BoxFit.cover, ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(height: 60), - Container( - color: Colors.orange[700], - padding: const EdgeInsets.all(8), - child: const Text( - 'Important! Copy and save the recovery phrase in a secure location. This cannot be recovered later.', - style: TextStyle(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, + child: Padding( + padding: const EdgeInsets.all(23), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(height: 60), + Container( + color: Colors.orange[700], + padding: const EdgeInsets.all(8), + child: const Text( + 'Important! Copy and save the recovery phrase in a secure location. This cannot be recovered later.', + style: TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), ), - ), - TextBoxWidget(text: _mnemonic), - IntroButtonWidget( - text: 'Copy phrase', - onPressed: () { - Clipboard.setData(ClipboardData(text: _mnemonic)); - const snackBar = SnackBar( - content: Text('Copied!'), - ); + TextBoxWidget(text: _mnemonic), + IntroButtonWidget( + text: 'Copy phrase', + onPressed: () { + Clipboard.setData(ClipboardData(text: _mnemonic)); + const snackBar = SnackBar( + content: Text('Copied!'), + ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - }, - ), - Container( - decoration: const BoxDecoration(color: Colors.black), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Checkbox( - value: _copied, - onChanged: (value) { - setState(() { - _copied = value!; - }); - }, - ), - const Text("I have stored the recovery phrase securely"), - ], - )), - IntroButtonWidget( - text: _copied ? 'Continue' : 'Go Back', - onPressed: _copied - ? () { - // GoRouter.of(context).push("/passwordSetup/$_mnemonic"); - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) { - return SetupPasswordScreen(mnemonic: _mnemonic); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + ), + Container( + decoration: const BoxDecoration(color: Colors.black), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Checkbox( + value: _copied, + onChanged: (value) { + setState(() { + _copied = value!; + }); }, - ); - } - : () { - GoRouter.of(context).push("/"); - }, - ), - const SizedBox(height: 60), - ], + ), + const Text("I have stored the recovery phrase securely"), + ], + )), + IntroButtonWidget( + text: 'Continue', + variant: _copied ? 'primary' : 'disabled', + onPressed: _copied + ? () { + // GoRouter.of(context).push("/passwordSetup/$_mnemonic"); + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return SetupPasswordScreen(mnemonic: _mnemonic); + }, + ); + } + : () {}, + ), + const SizedBox(height: 60), + ], + ), ), ), ); diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 7657c8f..d096ba8 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -115,7 +115,7 @@ class _HomeScreenState extends State { children: [ const MiniGamesScreen(), RankingScreen(), - const MydinogrowScreen(), + MydinogrowScreen(address: _publicKey ?? ''), WalletScreen(address: _publicKey ?? '', balance: _balance), ], ), diff --git a/lib/pages/input_phrase.dart b/lib/pages/input_phrase.dart index 0c1e543..a205f00 100644 --- a/lib/pages/input_phrase.dart +++ b/lib/pages/input_phrase.dart @@ -66,53 +66,57 @@ class _InputPhraseScreenState extends State { fit: BoxFit.cover, ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(height: 60), - const TextBoxWidget(text: 'Please enter your recovery phrase'), - Center( - child: Form( - key: _formKey, - child: SizedBox( - width: 300, - child: GridView.count( - padding: const EdgeInsets.all(3), - crossAxisSpacing: 10, - mainAxisSpacing: 3, - shrinkWrap: true, - crossAxisCount: 3, - children: List.generate(12, (index) { - return SizedBox( - height: 50, - child: TextFormField( - controller: controllers[index], - decoration: InputDecoration( - filled: true, - fillColor: Colors.black, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(height: 60), + const TextBoxWidget( + text: 'Please enter your recovery phrase'), + Center( + child: Form( + key: _formKey, + child: SizedBox( + width: 300, + child: GridView.count( + padding: const EdgeInsets.all(3), + crossAxisSpacing: 10, + mainAxisSpacing: 3, + shrinkWrap: true, + crossAxisCount: 3, + children: List.generate(12, (index) { + return SizedBox( + height: 50, + child: TextFormField( + controller: controllers[index], + decoration: InputDecoration( + filled: true, + fillColor: Colors.black, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: '${index + 1}', ), - hintText: '${index + 1}', + textInputAction: TextInputAction.next, ), - textInputAction: TextInputAction.next, - ), - ); - }), - )), + ); + }), + )), + ), ), - ), - validationFailed - ? const TextBoxWidget(text: 'Invalid keyphrase') - : const SizedBox(), - IntroButtonWidget( - text: 'Continue', - onPressed: () { - _onSubmit(context); - }, - ), - const SizedBox(height: 32), - ], + validationFailed + ? const TextBoxWidget(text: 'Invalid keyphrase') + : const SizedBox(), + IntroButtonWidget( + text: 'Continue', + onPressed: () { + _onSubmit(context); + }, + ), + const SizedBox(height: 32), + ], + ), ), ), ), diff --git a/lib/pages/my-dinogrow/my_dinogrow.dart b/lib/pages/my-dinogrow/my_dinogrow.dart index 61e7f68..642bfeb 100644 --- a/lib/pages/my-dinogrow/my_dinogrow.dart +++ b/lib/pages/my-dinogrow/my_dinogrow.dart @@ -1,11 +1,16 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:go_router/go_router.dart'; +import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; +import 'dart:convert'; import '../../ui/widgets/widgets.dart'; class MydinogrowScreen extends StatefulWidget { - const MydinogrowScreen({super.key}); + final String address; + + const MydinogrowScreen({super.key, required this.address}); @override State createState() => _MydinogrowScreenState(); @@ -13,6 +18,10 @@ class MydinogrowScreen extends StatefulWidget { class _MydinogrowScreenState extends State { final storage = const FlutterSecureStorage(); + bool _loading = true; + var userNfts = []; + int nftSelected = 0; + final filters = [ Colors.white, ...List.generate( @@ -46,27 +55,60 @@ class _MydinogrowScreenState extends State { child: ListView.builder( scrollDirection: Axis.horizontal, shrinkWrap: true, - itemCount: 12, + itemCount: userNfts.length, itemBuilder: (context, index) { - // final item = items[index]; - - return Container( - margin: const EdgeInsets.only(right: 12), - child: ClipRRect( - borderRadius: BorderRadius.circular(45), - child: returnImageColorFc(index), + return GestureDetector( + onTap: () { + setState(() { + nftSelected = index; + }); + }, + child: Container( + margin: const EdgeInsets.only(right: 12), + decoration: BoxDecoration( + border: Border.all( + color: nftSelected == index + ? Colors.white + : Colors.transparent, + width: 3, + ), + borderRadius: BorderRadius.circular(45), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(45), + child: Image.network( + userNfts[index]['imageUrl'], + width: 90, + height: 90, + fit: BoxFit.cover, + colorBlendMode: BlendMode.color, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Container( + color: Colors.black, + width: 120, + height: 120, + ); + }, + ), + // child: returnImageColorFc(index), + ), ), ); }, ), ), const SizedBox(height: 30), - const GameCardWidget( - text: 'Mini Dino', - route: "/mini_games/up", + GameCardWidget( + text: userNfts.isNotEmpty ? userNfts[nftSelected]['name'] : '', + urlImage: + userNfts.isNotEmpty ? userNfts[nftSelected]['imageUrl'] : '', ), - const SizedBox(height: 30), - const TextBoxWidget(text: "Hi ^.^ I'm Mini Dino"), + // const SizedBox(height: 30), + // TextBoxWidget(text: "Hi ^.^ I'm $nameNft"), ]; bool showDinos = false; @@ -74,10 +116,19 @@ class _MydinogrowScreenState extends State { @override void initState() { super.initState(); + fetchNfts(); } @override Widget build(BuildContext context) { + if (_loading) { + return const Center( + child: CircularProgressIndicator( + color: Colors.white, + ), + ); + } + return Scaffold( backgroundColor: Colors.transparent, body: Container( @@ -90,7 +141,7 @@ class _MydinogrowScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - ...(showDinos + ...(showDinos && userNfts.isNotEmpty ? myDinosContent(returnImageColor) : mintContent(onClaim)), const SizedBox(height: 30), @@ -98,6 +149,7 @@ class _MydinogrowScreenState extends State { text: 'Log out', onPressed: () => logout(context), size: 'fit', + variant: 'disabled', ) ]), ), @@ -140,4 +192,41 @@ class _MydinogrowScreenState extends State { color: filters[index], ); } + + Future fetchNfts() async { + try { + setState(() { + _loading = true; + }); + + await dotenv.load(fileName: ".env"); + + final response = await http.post( + Uri.parse(dotenv.env['QUICKNODE_RPC_URL'].toString()), + headers: { + 'Content-Type': 'application/json', + "x-qn-api-version": '1' + }, + body: jsonEncode({ + "method": "qn_fetchNFTs", + "params": { + "wallet": widget.address, + // "AUMbL5J7wQuNxV7tpj1mq4SzPxqReDA6VzfkCzpJjcUi", + "page": 1, + "perPage": 10 + } + })); + + final dataResponse = jsonDecode(response.body); + final arrayAssets = dataResponse['result']['assets']; + + setState(() { + userNfts = arrayAssets.where((nft) => nft['imageUrl'] != '').toList(); + }); + } finally { + setState(() { + _loading = false; + }); + } + } } diff --git a/lib/pages/setup_account.dart b/lib/pages/setup_account.dart index c8da76d..40c0bbb 100644 --- a/lib/pages/setup_account.dart +++ b/lib/pages/setup_account.dart @@ -17,23 +17,26 @@ class SetUpScreen extends StatelessWidget { fit: BoxFit.cover, ), ), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const IntroLogoWidget(), - const SizedBox(height: 120), - IntroButtonWidget( - text: 'I have a recovery Phrase', - onPressed: () => context.push('/inputphrase'), - ), - const SizedBox(height: 30), - IntroButtonWidget( - text: 'Generate new wallet', - onPressed: () => context.push('/generatePhrase'), - ), - ], + child: Padding( + padding: const EdgeInsets.all(24), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const IntroLogoWidget(), + const SizedBox(height: 120), + IntroButtonWidget( + text: 'I have a recovery Phrase', + onPressed: () => context.push('/inputphrase'), + ), + const SizedBox(height: 30), + IntroButtonWidget( + text: 'Generate new wallet', + onPressed: () => context.push('/generatePhrase'), + ), + ], + ), ), ), ), diff --git a/lib/ui/widgets/GameCard/game_card.dart b/lib/ui/widgets/GameCard/game_card.dart index 567b5ac..f6598e2 100644 --- a/lib/ui/widgets/GameCard/game_card.dart +++ b/lib/ui/widgets/GameCard/game_card.dart @@ -4,11 +4,13 @@ import 'package:go_router/go_router.dart'; class GameCardWidget extends StatelessWidget { final String text; final String? route; + final String? urlImage; const GameCardWidget({ super.key, required this.text, this.route, + this.urlImage, }); @override @@ -31,10 +33,27 @@ class GameCardWidget extends StatelessWidget { children: [ ClipRRect( borderRadius: BorderRadius.circular(12), - child: Image.asset( - 'assets/images/logo.jpeg', - width: 120, - ), + child: (urlImage ?? '').isNotEmpty + ? Image.network( + urlImage ?? '', + width: 120, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Center( + child: Image.asset( + 'assets/images/logo.jpeg', + width: 120, + ), + ); + }, + ) + : Image.asset( + 'assets/images/logo.jpeg', + width: 120, + ), ), const SizedBox(height: 16), Text( diff --git a/lib/ui/widgets/IntroButton/intro_button.dart b/lib/ui/widgets/IntroButton/intro_button.dart index 55655c4..fc462b9 100644 --- a/lib/ui/widgets/IntroButton/intro_button.dart +++ b/lib/ui/widgets/IntroButton/intro_button.dart @@ -4,12 +4,14 @@ class IntroButtonWidget extends StatelessWidget { final VoidCallback? onPressed; final String text; final String? size; + final String? variant; const IntroButtonWidget({ super.key, this.onPressed, required this.text, this.size, + this.variant, }); @override @@ -18,7 +20,9 @@ class IntroButtonWidget extends StatelessWidget { onPressed: onPressed, style: ElevatedButton.styleFrom( minimumSize: size == 'fit' ? null : const Size.fromHeight(50), - backgroundColor: const Color.fromRGBO(241, 189, 57, 1), + backgroundColor: variant == 'disabled' + ? Colors.grey + : const Color.fromRGBO(241, 189, 57, 1), padding: const EdgeInsets.all(18), shadowColor: Colors.purple, elevation: 3, diff --git a/pubspec.lock b/pubspec.lock index 8a1dcaa..deb3081 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -249,7 +249,7 @@ packages: source: hosted version: "0.2.0" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" diff --git a/pubspec.yaml b/pubspec.yaml index a90ade4..42973f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: flutter_joystick: ^0.0.3 url_launcher: ^6.1.14 qr_flutter: ^4.1.0 + http: ^0.13.6 dev_dependencies: flutter_test: