diff --git a/assets/images/icons/add_image.png b/assets/images/icons/add_image.png new file mode 100644 index 0000000..b6d7b14 Binary files /dev/null and b/assets/images/icons/add_image.png differ diff --git a/assets/images/icons/no_user.png b/assets/images/icons/no_user.png new file mode 100644 index 0000000..e7bdbef Binary files /dev/null and b/assets/images/icons/no_user.png differ diff --git a/lib/main.dart b/lib/main.dart index 3cb34ed..06de3a6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,6 +11,7 @@ import 'package:dinogrow/pages/mini-games/mini_games.dart'; import 'package:dinogrow/pages/mini-games/up/up.dart'; import 'package:dinogrow/pages/mini-games/up/down.dart'; import 'package:dinogrow/pages/mini-games/coming_soon.dart'; +import 'package:dinogrow/pages/profile.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -71,6 +72,11 @@ final GoRouter _router = GoRouter(routes: [ builder: (context, state) { return const ComingSoonScreen(); }), + GoRoute( + path: '/profile', + builder: (context, state) { + return const ProfileScreen(); + }), ]); class MyApp extends StatelessWidget { diff --git a/lib/pages/home.dart b/lib/pages/home.dart index b542ae8..d811bf3 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:go_router/go_router.dart'; import 'package:solana/solana.dart'; import 'package:dinogrow/pages/my-dinogrow/my_dinogrow.dart'; @@ -16,6 +17,7 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { + String? nickName; String? _publicKey; String? _balance; SolanaClient? client; @@ -41,47 +43,70 @@ class _HomeScreenState extends State { flexibleSpace: Column( children: [ SizedBox(height: statusBarHeight), - Card( - color: Colors.white, - shadowColor: Colors.white, - elevation: 9, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - const Text( - 'User: ', - style: TextStyle( - fontWeight: FontWeight.bold, color: Colors.black), - ), - const SizedBox(width: 3), - Expanded( - child: Text( - _publicKey == null - ? 'Loading...' - : '${_publicKey!.substring(0, 6)}...${_publicKey!.substring(_publicKey!.length - 6, _publicKey!.length)}', - maxLines: 1, - style: const TextStyle(color: Colors.black), - )), - const SizedBox(width: 12), - const Text( - 'Balance: ', - style: TextStyle( - fontWeight: FontWeight.bold, color: Colors.black), - ), - const SizedBox(width: 3), - Text( - _balance != null - ? double.parse(_balance ?? '0').toStringAsFixed(2) - : 'Loading...', - maxLines: 1, - style: const TextStyle(color: Colors.black)), - const SizedBox(width: 3), - const Text('SOL', + GestureDetector( + onTap: () { + GoRouter.of(context).push('/profile'); + }, + child: Card( + color: Colors.white, + shadowColor: Colors.white, + elevation: 9, + child: Padding( + padding: const EdgeInsets.only( + top: 6, bottom: 6, left: 12, right: 12), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(42), + child: Image.asset( + 'assets/images/icons/no_user.png', + width: 40, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + children: [ + Text( + (nickName ?? '(No nickname)'), + maxLines: 1, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 16), + ), + const SizedBox(height: 3), + Text( + _publicKey == null + ? 'Loading...' + : '${_publicKey!.substring(0, 6)}...${_publicKey!.substring(_publicKey!.length - 6, _publicKey!.length)}', + maxLines: 1, + style: const TextStyle( + color: Colors.black, fontSize: 10), + ) + ], + )), + const SizedBox(width: 12), + const Text( + 'Balance: ', style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black)), - ], + fontWeight: FontWeight.bold, color: Colors.black), + ), + const SizedBox(width: 3), + Text( + _balance != null + ? double.parse(_balance ?? '0') + .toStringAsFixed(2) + : 'Loading...', + maxLines: 1, + style: const TextStyle(color: Colors.black)), + const SizedBox(width: 3), + const Text('SOL', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black)), + ], + ), ), ), ), @@ -92,20 +117,40 @@ class _HomeScreenState extends State { indicatorSize: TabBarIndicatorSize.tab, tabs: [ Tab( - icon: Icon(Icons.videogame_asset), + height: 60, + icon: Icon( + Icons.videogame_asset, + size: 18, + ), text: 'Games', + iconMargin: EdgeInsets.all(6), ), Tab( - icon: Icon(Icons.emoji_events), + height: 60, + icon: Icon( + Icons.emoji_events, + size: 18, + ), text: 'Ranking', + iconMargin: EdgeInsets.all(6), ), Tab( - icon: Icon(Icons.pets), + height: 60, + icon: Icon( + Icons.pets, + size: 18, + ), text: 'My Dino', + iconMargin: EdgeInsets.all(6), ), Tab( - icon: Icon(Icons.wallet), + height: 60, + icon: Icon( + Icons.wallet, + size: 18, + ), text: 'Wallet', + iconMargin: EdgeInsets.all(6), ), ], ), diff --git a/lib/pages/my-dinogrow/my_dinogrow.dart b/lib/pages/my-dinogrow/my_dinogrow.dart index 14fba5b..fb31256 100644 --- a/lib/pages/my-dinogrow/my_dinogrow.dart +++ b/lib/pages/my-dinogrow/my_dinogrow.dart @@ -6,7 +6,6 @@ import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:solana/solana.dart'; -import 'package:solana_common/utils/convert.dart'; import 'package:solana_web3/solana_web3.dart' as web3; import 'package:url_launcher/url_launcher.dart'; @@ -378,7 +377,6 @@ class _MydinogrowScreenState extends State { String hdPath = dotenv.env['HD_PATH'].toString(); final walletdino = await solana.Ed25519HDKeyPair.fromSeedWithHdPath( seed: keypair.secretKey, hdPath: hdPath); - final mainWalletSolana = await solana.Ed25519HDKeyPair.fromMnemonic( mainWalletKey!, @@ -508,7 +506,7 @@ class _MydinogrowScreenState extends State { final snackBar = SnackBar( content: Text('Error: $e', style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red, - ); + ); ScaffoldMessenger.of(context).showSnackBar(snackBar); } finally { if (mounted) { diff --git a/lib/pages/profile.dart b/lib/pages/profile.dart new file mode 100644 index 0000000..5ade3fb --- /dev/null +++ b/lib/pages/profile.dart @@ -0,0 +1,245 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:go_router/go_router.dart'; +import 'dart:async'; +import 'dart:io'; + +import '../ui/widgets/widgets.dart'; + +class ProfileScreen extends StatefulWidget { + const ProfileScreen({super.key}); + + @override + State createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + final _formKey = GlobalKey(); + final nicknameController = TextEditingController(); + final bioController = TextEditingController(); + final statusController = TextEditingController(); + String? password; + bool _loading = false; + String? key; + final storage = const FlutterSecureStorage(); + File imageProfile = File(''); + + @override + void initState() { + super.initState(); + + _checkForSavedLogin().then((credentialsFound) { + if (!credentialsFound) { + GoRouter.of(context).push("/setup"); + } else { + setState(() { + _loading = false; + }); + } + }); + } + + Widget build(BuildContext context) { + if (_loading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Scaffold( + extendBodyBehindAppBar: true, + appBar: appBar(context), + body: SingleChildScrollView( + child: Container( + height: MediaQuery.of(context).size.height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/images/ui/intro_jungle_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Expanded(child: SizedBox()), + GestureDetector( + onTap: pickImage, + child: Container( + width: 150, + height: 150, + decoration: BoxDecoration( + color: Colors.black, + image: DecorationImage( + scale: 3, + fit: imageProfile.path.isNotEmpty + ? BoxFit.cover + : BoxFit.scaleDown, + image: imageProfile.path.isNotEmpty + ? Image.file( + imageProfile, + fit: BoxFit.cover, + ).image + : const AssetImage( + 'assets/images/icons/add_image.png'), + ), + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 6), + ), + ), + ), + const Expanded(child: SizedBox()), + const TextBoxWidget( + text: + 'Edit your profile to share it to our community ^.^'), + const Expanded(child: SizedBox()), + Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + controller: nicknameController, + decoration: InputDecoration( + labelText: 'Nickname', + filled: true, + fillColor: Colors.black, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 12), + TextFormField( + controller: bioController, + decoration: InputDecoration( + labelText: 'Bio', + filled: true, + fillColor: Colors.black, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 12), + TextFormField( + controller: statusController, + decoration: InputDecoration( + labelText: 'Status', + filled: true, + fillColor: Colors.black, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 30), + IntroButtonWidget( + text: 'Save', + onPressed: () => onWant2Save(context), + ), + ], + ), + ), + const Expanded(child: SizedBox()), + ]), + ), + ), + ), + ), + ); + } + + Future pickImage() async { + try { + final image = await ImagePicker().pickImage(source: ImageSource.gallery); + if (image == null) return; + final imageTemp = File(image.path); + setState(() { + imageProfile = imageTemp; + }); + } catch (e) { + print('Failed to pick image: $e'); + } + } + + Future _checkForSavedLogin() async { + key = await storage.read(key: 'mnemonic'); + password = await storage.read(key: 'password'); + if (key == null || password == null) { + return false; + } else { + return true; + } + } + + void onWant2Save(BuildContext context) { + if (nicknameController.text.isNotEmpty && + bioController.text.isNotEmpty && + statusController.text.isNotEmpty) { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Update profile transaction'), + content: const Text( + 'Before to continue, are you sure to dave your data? Remember the transaction has a variable cost so please confirm if you have at least 0.05 SOL in your wallet balance.'), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + _onSubmit(); + }, + child: const Text('Confirm'), + ), + ], + ); + }); + } else { + const snackBar = SnackBar( + content: Text( + 'Error: Please fill all fields to continue', + style: const TextStyle(color: Colors.white), + ), + backgroundColor: Colors.red, + ); + + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + } + + void _onSubmit() async { + try { + setState(() { + _loading = true; + }); + + // TODO update profile + GoRouter.of(context).pop(); + } catch (e) { + final snackBar = SnackBar( + content: Text( + e.toString(), + style: const TextStyle(color: Colors.white), + ), + backgroundColor: Colors.red, + ); + + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } finally { + setState(() { + _loading = false; + }); + } + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 38dd0bc..3ccd551 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 65240e9..9ce94c4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux flutter_secure_storage_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e14e235..c1b6dae 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,13 @@ import FlutterMacOS import Foundation import connectivity_plus +import file_selector_macos import flutter_secure_storage_macos import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 4a33caa..abe4bb5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -201,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + url: "https://pub.dev" + source: hosted + version: "0.3.3+6" crypto: dependency: transitive description: @@ -281,6 +289,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -334,6 +374,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c + url: "https://pub.dev" + source: hosted + version: "2.0.16" flutter_secure_storage: dependency: "direct main" description: @@ -472,6 +520,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "0c7b83bbe2980c8a8e36e974f055e11e51675784e13a4762889feed0f3937ff2" + url: "https://pub.dev" + source: hosted + version: "0.8.8+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "50bc9ae6a77eea3a8b11af5eb6c661eeb858fdd2f734c2a4fd17086922347ef7" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: c5538cacefacac733c724be7484377923b476216ad1ead35a0d2eadcdc0fc497 + url: "https://pub.dev" + source: hosted + version: "0.8.8+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 + url: "https://pub.dev" + source: hosted + version: "2.9.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" io: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d666755..0e6abdd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: borsh_annotation: ^0.3.1+3 solana_web3: ^0.0.8 solana_common: ^0.0.5 + image_picker: ^1.0.4 dev_dependencies: flutter_test: @@ -44,4 +45,5 @@ flutter: - assets/images/up/maps/01/ - assets/images/dinopad/ - assets/images/ui/ + - assets/images/icons/ - .env diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 80422a9..624e8d4 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,12 +7,15 @@ #include "generated_plugin_registrant.h" #include +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 214b998..0d1b0c7 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus + file_selector_windows flutter_secure_storage_windows url_launcher_windows )