From db3da5b6bef7c82a1ad9302f9640a45ba4e02ab2 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 16:10:03 +0800 Subject: [PATCH 01/23] initial --- mobile-app/assets/quests/quests_top_logo.png | Bin 0 -> 4316 bytes .../complete_setup_action_sheet.dart | 410 ++++++++++++++++++ .../lib/features/components/quest_card.dart | 180 ++++++++ .../features/components/scaffold_base.dart | 6 +- .../main/screens/quests/quests_screen.dart | 108 +++-- .../lib/features/styles/app_colors_theme.dart | 7 + mobile-app/pubspec.yaml | 1 + 7 files changed, 672 insertions(+), 40 deletions(-) create mode 100644 mobile-app/assets/quests/quests_top_logo.png create mode 100644 mobile-app/lib/features/components/complete_setup_action_sheet.dart create mode 100644 mobile-app/lib/features/components/quest_card.dart diff --git a/mobile-app/assets/quests/quests_top_logo.png b/mobile-app/assets/quests/quests_top_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f65ab04ab121aecfacadbbb276a8ee404106f6be GIT binary patch literal 4316 zcmV<25F_u2P)Ky>bjWhbP1e@XvjZcunBitN^hB+bh7V0A~f3SAd93UJ(m0QdWR?$~g(d z&f7gk(|xOYW@Jf`?5@%L+3GKc$d+fMnW^sT>gwt$gbX;_?$u!kYZ~<$fO>#NHvq5! z2I@vZonNQVK?4IoIHw=a4VnSH{-%uTLk;>Xo%M6bnLnTOnhJE&@qrp8eP(ok6m=m3 z{bBMlA0D*PcECpTyJ=d<$8GYjCm;Ltxm^{)(aNvueMpJAfHhEVa;_+C`j=V}Udjo# z=y|VcVgLATe|tMskrNqGpRSVAm5Pv0 zBn9|BaWYo|9*qr;#=Y0zSvt5)l3)5w>4WkZY$W*hIEmFA+8k6(PV+FpC4F91BGt=fA?_+Pu&|7*vVsI#A(+b{>~&--rb?A`DJU%-MO z>N@?;*)M7I;@=rFS5a~BKJ~>0xRRZm4tN0#!Mv8piQQ_6;+;^0R^TaM1sDRpN^?Wg zjV`3=4_T7WK?pwtI=@@Qt`twPXr%Vw8goXSi$f(=GYYZd=@Yf9b+RDdyILIz?9P$y zyw3y2VEC(ujTUD`c=y-!HslQ5_m**ut&8NezM4^gWh025NnbK(>7i!#-#-65S}cjQ zxo~xuroS{&?|pZ&@i+@hf*Z-*x&^hZkQyyHg zhL7Vdq1S`B1xtuz2U8r^m#NXha_7%=JstsnNPbqAZyMQ(7c~n0(tGbV4Mxh z`(UKjAdS3xE1mRvF0X@cC=Bo!$f)aYZix##H-?7mff%`1a)bQe?w@~Aq1f9Z7`iWd znw%Hpbg14s9$L4_IUx2CihM8(tu7X^&yxLzU!R=5a!p3SU^Fes+)DM7grtLEzRKtB z@qaqMZ}%SZ&jEEJyyDu_9qDZ5W)osqM|15)H47p|KVmXz*uvFtTHIfs8Ky{&DI5dYI+PU-e zINFBexdrfy;OkF4-l5cRJ}FLX#v>8ePaE!8*x#S-AkwK;L!e2`Wze?YT9eK9Ve$jbqykXHf^($mi5#hW4GbDK7yDrcOYkdqmV((wbmvYg2J%vulC47h9X zyu%Z&f>yRaqpQZQQ0kwX1n1V{23Q0J%?sDa15Ufn8HXVwM)6}wI(`#3thfWl`{et! zCI;8i0h$Hn(D`l~G!tthm{f{z=I2>CaSfBPbo|UPR+N(o8WyT~RI?k|9W{)OGeMLA z8opWQOvH7+a(sKR(=K5c)j}AX-J}_}w{6Xv4dPll)DHVJD8FSFkynh9unX(UlhdDR zmbn835Qj@OX4O2B%1KFlFR08Jl!Vc29*gi+;VptI-C@~TUC5_p0Jk|YFlRg_zn0II zK1EPd`(@4?Eh*&W76Ezr1(i(E4Vl7<8tE6 zK(mgKDMF8@WbU49?xsr0kte=<#00Di?VwdbInApvBUOZcy6Gt$PRg-*yz!7a+B4S; z_Jt6Yk4Tlu?lv+erx#VS5ThD1r-$S3Dd*HeOq?#e3^34M3412s9CY6uZ$48sTw$fi z)8?}MQ#=jxmf<9<5ZOW;$qC`8d_N+##%q_^$ zMy=mjSb1lSxaPYb8TZI<-vCn(;_WF`9^XfI`hD(v-++92L#U&sSEU==wn#HKvh0B4 z*^fQ5i4|iPu8|WfN|wt=PLNqHfh29;L5+m}8(9LbAKZYFkZhYP-R$;MmbmlHZ69$B zod&hIZE8n9q^;>ap8eP(XG%`p>AxqnjyQAHB65Ow7g|jksC-pnn!JeXGdPa|SqFX> z;S!MKbMxfKSPdOJeTzDr*_GDO$fS z=z@r1w$F^ir9LIcYu-eE4JK5ghn07#AjwoJDSquK4qklY(v#q+G(2z_Kyi)p8qdX} z6gbXSSE1A6cv{?aK7mHsF69a~)8E06NsS4<+Z=RQ;R>3?!xa&>V8D*CN6LTi2(jyWXk2MB zH_ikGOHx{N6t58iiYwD4d-k$a4&0FcdV2astOUVM@kRF_t%U-%=!|)vg=ILpPg?Q^ z5$wiT9%?mGDDJC(^*m=PMpSTUx#UDX_pa-)w$fydMiliacmJS@_NtE*AzM; zY45c}UP`MrED=0Qk<2RD(8*d(wr~B^JCw|@k{V34i8pI%pE~H4I7)k>Sk7t3W`+%E zN^{4iH$R_u(QrY&#z1qfur2ytj-#uK3e6{QYIRyMgjBHdNBJgs9ss~_B`)fyrfXBW zbMn{#&TU$Bd3WN`~_6WB_ zM>&xOXr<97BclpwN%5a)ct$gF78yuNVTfQkegEsm(piHEWWDdP z>jGAG&=^0gr~&IE~m)vUfsUL@LI^UoaXNG=cJb^u>1A~P&LrjqG!WpcfzAEE5j-k%;5&d*Tjk% zGKLka27Fu<<Ds(cEL_&0 z-1s=fHk8LXE2+4_4In4jv?oPdYPB@0>}$Bb?9;4yu>83ehP66M>}V_}Zj-Yt49{p* zS$gHcxIaC-_ippxieqH(icJ`r{e_YtrbGemubiwGW^(hr4u`s>5TECMuht)HI=n?N z%Y+uGS6d5Ykrho2!{WW#oS~?Y`;1pNLr_1Lna#VZtH$$)^aeCZPCJFh&Q-tG;lp^S@%u?@1VN!#%{VCIjlN4h~YC*)`@L=Y_yvL8V zcBsykl8q8P9VxrEhU8w!LHjp7=yMYXTz?Cof4`vp!hD_ot9TjKJf8&*VB_^k?3-8B z-bvaesDTb(@GqFZDWGhA7wqJ^U-A9v5;^73pDHm**wHNt;%+#ea?U<9U#ZUH@nk`z z%w;VJm?bAUuO9oAI*-S*D3z%e6bH<@$B2>jXoe{w*gYPPXNpRdP2=TuNgyWs6*&tG zcsw4@0#vSkqz%NB#2p!I<(b>e const CompleteSetupActionSheet(), + ); +} + +class CompleteSetupActionSheet extends ConsumerStatefulWidget { + const CompleteSetupActionSheet({super.key}); + + @override + ConsumerState createState() => _CompleteSetupActionSheetState(); +} + +class _CompleteSetupActionSheetState extends ConsumerState { + final _taskmasterService = TaskmasterService(); + final _ethAddressController = TextEditingController(); + final _xHandleController = TextEditingController(); + final _bioController = TextEditingController(); + + bool _isSubmitting = false; + bool _hasInitialized = false; + + bool get _isBioValid => _bioController.text.contains('@QuantusNetwork'); + + @override + void dispose() { + _ethAddressController.dispose(); + _xHandleController.dispose(); + _bioController.dispose(); + super.dispose(); + } + + void _initializeData(AccountAssociations associations) { + if (_hasInitialized) return; + + if (associations.ethAddress != null) { + _ethAddressController.text = associations.ethAddress!; + } + if (associations.xUsername != null) { + _xHandleController.text = associations.xUsername!; + } + // Bio is not stored, so we leave it empty as per requirements. + + _hasInitialized = true; + } + + Future _handleLinkAccounts() async { + final ethAddress = _ethAddressController.text.trim(); + final xHandle = _xHandleController.text.trim(); + + setState(() => _isSubmitting = true); + + try { + // 1. Opt-in to reward program (idempotent-ish, or safe to call) + // Requirement: "If it is a new user we set their opt in to true" + await _taskmasterService.optInRewardProgram(); + + // 2. Link ETH Address + if (ethAddress.isNotEmpty) { + await _taskmasterService.associateEthAddress(ethAddress); + } + + // 3. Link X Handle + if (xHandle.isNotEmpty) { + final handle = xHandle.startsWith('@') ? xHandle.substring(1) : xHandle; + await _taskmasterService.associateXHandle(handle); + } + + // 4. Refresh data + ref.invalidate(accountAssociationsProvider); + ref.invalidate(optInPositionProvider); + + if (mounted) { + context.showSuccessSnackbar( + title: 'Success', + message: 'Accounts linked successfully!', + ); + Navigator.pop(context); + } + } catch (e) { + if (mounted) { + context.showErrorSnackbar( + title: 'Error', + message: e.toString(), + ); + } + } finally { + if (mounted) { + setState(() => _isSubmitting = false); + } + } + } + + @override + Widget build(BuildContext context) { + final associationsAsync = ref.watch(accountAssociationsProvider); + + associationsAsync.whenData((associations) { + _initializeData(associations); + }); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 40), + decoration: const ShapeDecoration( + color: Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: BorderSide( + width: 1, + strokeAlign: BorderSide.strokeAlignOutside, + color: Color(0x66F4F6F9), + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + const SizedBox(height: 64), + _buildForm(), + const SizedBox(height: 32), // Spacing before button + _buildLinkButton(), + SizedBox(height: MediaQuery.of(context).viewInsets.bottom), + ], + ), + ); + } + + Widget _buildHeader() { + return SizedBox( + width: double.infinity, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'COMPLETE ACCOUNT SETUP', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + SizedBox( + width: 264, + child: Text( + 'Link your accounts once to participate in referrals and raids.', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.50), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + const SizedBox(width: 18), + // Close button + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: context.themeColors.textPrimary, size: 24), + ), + ], + ), + ); + } + + Widget _buildForm() { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInputSection( + title: 'Connect your wallet', + controller: _ethAddressController, + hintText: 'Enter ETH Address', + ), + const SizedBox(height: 32), + _buildInputSection( + title: 'Connect your X handle', + controller: _xHandleController, + hintText: 'Enter username', + ), + const SizedBox(height: 32), + _buildBioSection(), + ], + ); + } + + Widget _buildInputSection({ + required String title, + required TextEditingController controller, + required String hintText, + }) { + return Container( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 323, + child: Text( + title, + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(height: 15), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: ShapeDecoration( + color: const Color(0x0AF4F6F9), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x19F4F6F9), + ), + borderRadius: BorderRadius.circular(4), + ), + ), + child: TextField( + controller: controller, + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + decoration: InputDecoration.collapsed( + hintText: '|$hintText', + hintStyle: const TextStyle( + color: Color(0x7FF4F6F9), + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildBioSection() { + return Container( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 323, + child: Text( + 'Verify your X account', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(height: 4), + const SizedBox( + width: 323, + child: Text( + 'To confirm this account belongs to you, please update your X bio to include @QuantusNetwork', + style: TextStyle( + color: Color(0x7FF4F6F9), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 15), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: ShapeDecoration( + color: const Color(0x0AF4F6F9), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x19F4F6F9), + ), + borderRadius: BorderRadius.circular(4), + ), + ), + child: TextField( + controller: _bioController, + onChanged: (_) => setState(() {}), + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + decoration: const InputDecoration.collapsed( + hintText: '|Updated Bio', + hintStyle: TextStyle( + color: Color(0x7FF4F6F9), + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildLinkButton() { + final bool isEnabled = _isBioValid && !_isSubmitting; + + return GestureDetector( + onTap: isEnabled ? _handleLinkAccounts : null, + child: Opacity( + opacity: isEnabled ? 1.0 : 0.40, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: ShapeDecoration( + gradient: const LinearGradient( + begin: Alignment(0.02, 0.50), + end: Alignment(1.00, 0.50), + colors: [Color(0x7F0000FF), Color(0x19ED4CCE), Color(0x7FFFE91F)], + ), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x33F4F6F9), + ), + borderRadius: BorderRadius.circular(42), + ), + ), + child: Center( + child: _isSubmitting + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: context.themeColors.textPrimary, + strokeWidth: 2, + ), + ) + : const Text( + 'Link Accounts', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ); + } +} diff --git a/mobile-app/lib/features/components/quest_card.dart b/mobile-app/lib/features/components/quest_card.dart new file mode 100644 index 00000000..07a43c44 --- /dev/null +++ b/mobile-app/lib/features/components/quest_card.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; + +class QuestCard extends StatelessWidget { + final String title; + final String description; + final String actionLabel; + final VoidCallback? onTap; + final List gradientColors; + final List? gradientStops; + final AlignmentGeometry gradientCenter; + final Color borderColor; + final bool isDisabled; + + // Background rectangle positioning + final double bgRectLeft; + final double bgRectTop; + final double bgRectWidth; + final double bgRectHeight; + + const QuestCard({ + super.key, + required this.title, + required this.description, + required this.actionLabel, + required this.onTap, + required this.gradientColors, + this.gradientStops, + required this.gradientCenter, + required this.borderColor, + this.isDisabled = false, + this.bgRectLeft = -127, + this.bgRectTop = -198, + this.bgRectWidth = 531, + this.bgRectHeight = 531, + }); + + factory QuestCard.referFriends({ + required VoidCallback? onTap, + bool isDisabled = false, + }) { + return QuestCard( + title: 'REFER FRIENDS', + description: 'Earn for every friend who joins\nQuantus using your link.', + actionLabel: 'View Referrals', + onTap: onTap, + gradientColors: const [ + Color(0xFF0C1014), + Color(0xFF0000FF), + Color(0xFFED4CCE), + Color(0xFFFFE91F), + ], + // Exact stops from Figma: 45%, 53%, 62%, 65% + gradientStops: const [0.45, 0.53, 0.62, 0.65], + gradientCenter: const Alignment(0.77, 0.22), + borderColor: const Color(0x7F6734BA), + isDisabled: isDisabled, + // Default positioning for Refer Friends + bgRectLeft: -127, + bgRectTop: -198, + ); + } + + factory QuestCard.kingOfTheShill({ + required VoidCallback? onTap, + bool isDisabled = false, + }) { + return QuestCard( + title: 'KING OF THE SHILL', + description: 'Participate in social raids and earn rewards for verified posts.', + actionLabel: 'View Raids', + onTap: onTap, + gradientColors: const [ + Color(0xFF0C1014), + Color(0xFFED4CCE), + Color(0xFFFFE91F), + ], + // Exact stops from Figma: 45%, 54%, 57% + gradientStops: const [0.45, 0.54, 0.57], + gradientCenter: const Alignment(0.77, 0.22), + borderColor: const Color(0x7F773F56), + isDisabled: isDisabled, + // Same positioning for now, but exposed for adjustment + bgRectLeft: -127, + bgRectTop: -198, + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: isDisabled ? null : onTap, + child: Opacity( + opacity: isDisabled ? 0.5 : 1.0, + child: Container( + width: double.infinity, + height: 246, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: borderColor), + borderRadius: BorderRadius.circular(4), + ), + ), + child: Stack( + children: [ + Positioned( + left: bgRectLeft, + top: bgRectTop, + child: Container( + width: bgRectWidth, + height: bgRectHeight, + decoration: ShapeDecoration( + gradient: RadialGradient( + center: gradientCenter, + radius: 1.39, + colors: gradientColors, + stops: gradientStops, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + description, + style: TextStyle( + color: context.themeColors.textPrimary.withValues(alpha: 0.5), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + actionLabel, + style: context.themeText.smallTitle, + ), + const SizedBox(width: 8), + Icon( + Icons.arrow_forward, + size: 18, + color: context.themeColors.textPrimary, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile-app/lib/features/components/scaffold_base.dart b/mobile-app/lib/features/components/scaffold_base.dart index 67b1b7e5..95275ddc 100644 --- a/mobile-app/lib/features/components/scaffold_base.dart +++ b/mobile-app/lib/features/components/scaffold_base.dart @@ -19,6 +19,7 @@ class ScaffoldBase extends StatelessWidget { final double dim; final bool extendBodyBehindAppBar; final bool extendBodyBehindNavBar; + final Color? backgroundColor; // Default constructor - static content const ScaffoldBase({ @@ -30,6 +31,7 @@ class ScaffoldBase extends StatelessWidget { this.decorations, this.dim = 0.25, this.padding = const EdgeInsets.symmetric(horizontal: 24.0), + this.backgroundColor, required Widget this.child, }) : slivers = null, scrollController = null, @@ -46,6 +48,7 @@ class ScaffoldBase extends StatelessWidget { this.decorations, this.dim = 0.25, this.padding = const EdgeInsets.symmetric(horizontal: 24.0), + this.backgroundColor, required ScrollController this.scrollController, this.scrollPhysics = const AlwaysScrollableScrollPhysics(), required Widget this.child, @@ -62,6 +65,7 @@ class ScaffoldBase extends StatelessWidget { this.decorations, this.dim = 0.25, this.padding = const EdgeInsets.symmetric(horizontal: 24.0), + this.backgroundColor, required ScrollController this.scrollController, this.scrollPhysics = const AlwaysScrollableScrollPhysics(), required RefreshCallback this.onRefresh, @@ -73,7 +77,7 @@ class ScaffoldBase extends StatelessWidget { return Scaffold( extendBodyBehindAppBar: extendBodyBehindAppBar, appBar: appBar, - backgroundColor: context.themeColors.background, + backgroundColor: backgroundColor ?? context.themeColors.background, body: Stack( children: [ if (decorations != null) ...decorations!, diff --git a/mobile-app/lib/features/main/screens/quests/quests_screen.dart b/mobile-app/lib/features/main/screens/quests/quests_screen.dart index 4c296074..be4e0c79 100644 --- a/mobile-app/lib/features/main/screens/quests/quests_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/quests_screen.dart @@ -2,23 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/button.dart'; -import 'package:resonance_network_wallet/features/components/list_item.dart'; +import 'package:resonance_network_wallet/features/components/complete_setup_action_sheet.dart'; import 'package:resonance_network_wallet/features/components/loading_text_animation.dart'; +import 'package:resonance_network_wallet/features/components/quest_card.dart'; import 'package:resonance_network_wallet/features/components/quests_promo_video.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; import 'package:resonance_network_wallet/features/components/sphere.dart'; import 'package:resonance_network_wallet/features/main/screens/navbar.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/account_associations_status.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/king_of_the_shill_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/referrals_quest_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/optin_position_status.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/quest_title.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_stats_providers.dart'; import 'package:resonance_network_wallet/providers/opt_in_position_providers.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; class QuestsScreen extends ConsumerStatefulWidget { final bool playPromoVideo; @@ -121,14 +119,14 @@ class _QuestsScreenState extends ConsumerState { Widget build(BuildContext context) { // Show loading state for users who haven't opted in to the reward program or if the promo video is not playing yet if (_isLoadingParticipation || !widget.playPromoVideo) { - return const ScaffoldBase( + return ScaffoldBase( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, spacing: 12, children: [ - QuestTitle(padding: EdgeInsetsGeometry.zero), - LoadingTextAnimation(), + Image.asset('assets/quests/quests_top_logo.png', height: 24), + const LoadingTextAnimation(), ], ), ), @@ -137,7 +135,7 @@ class _QuestsScreenState extends ConsumerState { if (!_isRewardProgramParticipant) { return Scaffold( - backgroundColor: Colors.black, + backgroundColor: context.themeColors.background2, body: Stack( children: [ QuestsPromoVideo( @@ -182,7 +180,14 @@ class _QuestsScreenState extends ConsumerState { ); } + final associationsAsync = ref.watch(accountAssociationsProvider); + final hasCompletedSetup = associationsAsync.maybeWhen( + data: (associations) => associations.ethAddress != null || associations.xUsername != null, + orElse: () => false, + ); + return ScaffoldBase.refreshable( + backgroundColor: const Color(0xFF0C1014), onRefresh: () async { refreshStatsData(); refreshAssociationsData(); @@ -195,45 +200,70 @@ class _QuestsScreenState extends ConsumerState { slivers: [ SliverToBoxAdapter( child: Column( - mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const QuestTitle(), - const OptinPositionStatus(), - SizedBox(height: context.isSmallHeight ? 18 : 37.0), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 4, - children: [ - Text('Associated Accounts', style: context.themeText.smallParagraph, textAlign: TextAlign.start), - const AccountAssociationsStatus(), - ], - ), + const SizedBox(height: 24), + _buildHeader(context, hasCompletedSetup), + const SizedBox(height: 48), + _buildQuestCards(context, hasCompletedSetup), ], ), ), + ], + ); + } - SliverToBoxAdapter(child: SizedBox(height: context.isSmallHeight ? 18 : 37.0)), - SliverToBoxAdapter(child: Text('Quests', style: context.themeText.smallParagraph)), - const SliverToBoxAdapter(child: SizedBox(height: 4)), - SliverList( - delegate: SliverChildListDelegate([ - ListItem( - title: 'King of The Shill', - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const KingOfTheShillScreen())); - }, + Widget _buildHeader(BuildContext context, bool hasCompletedSetup) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset( + 'assets/quests/quests_top_logo.png', + height: 24, + fit: BoxFit.contain, + ), + GestureDetector( + onTap: () => showCompleteSetupActionSheet(context), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: ShapeDecoration( + color: context.themeColors.settingCard, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), - const SizedBox(height: 12), - ListItem( - title: 'Referrals', - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const ReferralsQuestScreen())); - }, + child: Text( + hasCompletedSetup ? 'Setup' : 'Complete Setup', + style: context.themeText.smallParagraph, ), - ]), + ), ), ], ); } + + Widget _buildQuestCards(BuildContext context, bool hasCompletedSetup) { + return Column( + children: [ + QuestCard.referFriends( + isDisabled: !hasCompletedSetup, + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ReferralsQuestScreen()), + ); + }, + ), + const SizedBox(height: 40), + QuestCard.kingOfTheShill( + isDisabled: !hasCompletedSetup, + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const KingOfTheShillScreen()), + ); + }, + ), + ], + ); + } + } diff --git a/mobile-app/lib/features/styles/app_colors_theme.dart b/mobile-app/lib/features/styles/app_colors_theme.dart index f3e9970e..e7de2065 100644 --- a/mobile-app/lib/features/styles/app_colors_theme.dart +++ b/mobile-app/lib/features/styles/app_colors_theme.dart @@ -13,6 +13,7 @@ class AppColorsTheme extends ThemeExtension { final Color pink; final Color yellow; final Color background; + final Color background2; final Color surface; final Color surfaceActive; final Color error; @@ -52,6 +53,7 @@ class AppColorsTheme extends ThemeExtension { required this.pink, required this.yellow, required this.background, + required this.background2, required this.surface, required this.surfaceActive, required this.error, @@ -93,6 +95,7 @@ class AppColorsTheme extends ThemeExtension { pink: const Color(0xFFED4CCE), yellow: const Color(0xFFFFE91F), background: const Color(0xFF0B0F14), + background2: const Color(0xFF0C1014), surface: const Color(0xFF000000), surfaceActive: const Color(0xFFF4F6F9), error: const Color(0xFFFF2D54), @@ -134,6 +137,7 @@ class AppColorsTheme extends ThemeExtension { pink: const Color(0xFFED4CCE), yellow: const Color(0xFFFFE91F), background: const Color(0xFF0B0F14), + background2: const Color(0xFF0C1014), surface: const Color(0xFF000000), surfaceActive: const Color(0xFFF4F6F9), error: const Color(0xFFFF2D54), @@ -174,6 +178,7 @@ class AppColorsTheme extends ThemeExtension { Color? pink, Color? yellow, Color? background, + Color? background2, Color? surface, Color? surfaceActive, Color? error, @@ -213,6 +218,7 @@ class AppColorsTheme extends ThemeExtension { pink: pink ?? this.pink, yellow: yellow ?? this.yellow, background: background ?? this.background, + background2: background2 ?? this.background2, surface: surface ?? this.surface, surfaceActive: surfaceActive ?? this.surfaceActive, error: error ?? this.error, @@ -256,6 +262,7 @@ class AppColorsTheme extends ThemeExtension { pink: Color.lerp(pink, other.pink, t) ?? pink, yellow: Color.lerp(yellow, other.yellow, t) ?? yellow, background: Color.lerp(background, other.background, t) ?? background, + background2: Color.lerp(background2, other.background2, t) ?? background2, surface: Color.lerp(surface, other.surface, t) ?? surface, surfaceActive: Color.lerp(surfaceActive, other.surfaceActive, t) ?? surfaceActive, error: Color.lerp(error, other.error, t) ?? error, diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index b9bb1923..8db77fe0 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -100,6 +100,7 @@ flutter: - assets/high_security/step_indicator_icon.svg - assets/high_security/big_red_button_icon.png - assets/high_security/intercept_icon.svg + - assets/quests/quests_top_logo.png fonts: - family: Fira Code From da9abb2956b2fd42936d646602ba4ca8b5522bdb Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 16:43:53 +0800 Subject: [PATCH 02/23] referrals screen mark 1 --- .../components/inner_shadow_container.dart | 57 +++ .../lib/features/main/screens/navbar.dart | 10 +- .../main/screens/quests/quests_screen.dart | 157 +------ .../quests/referrals_quest_screen.dart | 424 +++++++++++------- 4 files changed, 329 insertions(+), 319 deletions(-) create mode 100644 mobile-app/lib/features/components/inner_shadow_container.dart diff --git a/mobile-app/lib/features/components/inner_shadow_container.dart b/mobile-app/lib/features/components/inner_shadow_container.dart new file mode 100644 index 00000000..801cf7ce --- /dev/null +++ b/mobile-app/lib/features/components/inner_shadow_container.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; + +class InnerShadowContainer extends StatelessWidget { + final Widget child; + final List shadows; + final BorderRadius borderRadius; + + const InnerShadowContainer({ + super.key, + required this.child, + required this.shadows, + this.borderRadius = BorderRadius.zero, + }); + + @override + Widget build(BuildContext context) { + return CustomPaint( + foregroundPainter: _InnerShadowPainter(shadows, borderRadius), + child: child, + ); + } +} + +class _InnerShadowPainter extends CustomPainter { + final List shadows; + final BorderRadius borderRadius; + + _InnerShadowPainter(this.shadows, this.borderRadius); + + @override + void paint(Canvas canvas, Size size) { + final rect = Rect.fromLTWH(0, 0, size.width, size.height); + final rrect = borderRadius.toRRect(rect); + + canvas.clipRRect(rrect); + + for (final shadow in shadows) { + final paint = Paint() + ..color = shadow.color + ..maskFilter = MaskFilter.blur(BlurStyle.normal, shadow.blurSigma); + + final outerRect = rect.inflate(shadow.blurRadius + shadow.spreadRadius + 20); + + final path = Path() + ..fillType = PathFillType.evenOdd + ..addRect(outerRect) + ..addRRect(rrect.shift(-shadow.offset)); + + canvas.drawPath(path, paint); + } + } + + @override + bool shouldRepaint(covariant _InnerShadowPainter oldDelegate) { + return oldDelegate.shadows != shadows || oldDelegate.borderRadius != borderRadius; + } +} diff --git a/mobile-app/lib/features/main/screens/navbar.dart b/mobile-app/lib/features/main/screens/navbar.dart index 9f4412cc..3cbb23bb 100644 --- a/mobile-app/lib/features/main/screens/navbar.dart +++ b/mobile-app/lib/features/main/screens/navbar.dart @@ -139,12 +139,8 @@ class _NavbarState extends ConsumerState { // Handle quest screen visibility if (newIndex == 3) { - // quests screen index - make video visible + // quests screen index (_questsScreenKey.currentState as dynamic)?.refreshStatsData(); - (_questsScreenKey.currentState as dynamic)?.setVideoVisibility(true); - } else if (_selectedIndex == 3) { - // leaving quests screen - hide video - (_questsScreenKey.currentState as dynamic)?.setVideoVisibility(false); } setState(() { @@ -215,15 +211,13 @@ class _NavbarState extends ConsumerState { } Widget _buildBody() { - bool playPromoVideo = _selectedIndex == 3; - return IndexedStack( index: _selectedIndex, children: [ const WalletMain(), const TransactionsScreen(), const SettingsScreen(), - QuestsScreen(key: _questsScreenKey, playPromoVideo: playPromoVideo), + QuestsScreen(key: _questsScreenKey), ], ); } diff --git a/mobile-app/lib/features/main/screens/quests/quests_screen.dart b/mobile-app/lib/features/main/screens/quests/quests_screen.dart index be4e0c79..087a5b73 100644 --- a/mobile-app/lib/features/main/screens/quests/quests_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/quests_screen.dart @@ -1,67 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/features/components/button.dart'; import 'package:resonance_network_wallet/features/components/complete_setup_action_sheet.dart'; -import 'package:resonance_network_wallet/features/components/loading_text_animation.dart'; import 'package:resonance_network_wallet/features/components/quest_card.dart'; -import 'package:resonance_network_wallet/features/components/quests_promo_video.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/features/components/sphere.dart'; -import 'package:resonance_network_wallet/features/main/screens/navbar.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/king_of_the_shill_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/referrals_quest_screen.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_stats_providers.dart'; -import 'package:resonance_network_wallet/providers/opt_in_position_providers.dart'; -import 'package:resonance_network_wallet/services/referral_service.dart'; class QuestsScreen extends ConsumerStatefulWidget { - final bool playPromoVideo; - const QuestsScreen({super.key, required this.playPromoVideo}); + const QuestsScreen({super.key}); @override ConsumerState createState() => _QuestsScreenState(); } class _QuestsScreenState extends ConsumerState { - final ReferralService _referralService = ReferralService(); final ScrollController _scrollController = ScrollController(); - bool _isRewardProgramParticipant = false; - bool _isLoadingParticipation = true; - bool _isLastPromo = false; - bool _isSubmitting = false; - bool _isVisible = true; - - Future _loadParticipationStatus() async { - try { - final isParticipant = await _referralService.getRewardProgramParticiation(); - setState(() { - _isRewardProgramParticipant = isParticipant; - _isLoadingParticipation = false; - }); - } catch (e) { - debugPrint('Error loading participation status: $e'); - setState(() { - _isLoadingParticipation = false; - }); - } - } - - @override - void initState() { - super.initState(); - _loadParticipationStatus(); - } - - @override - void dispose() { - super.dispose(); - } - void refreshStatsData() { ref.invalidate(accountsStatsProvider); } @@ -70,116 +28,8 @@ class _QuestsScreenState extends ConsumerState { ref.invalidate(accountAssociationsProvider); } - void setVideoVisibility(bool isVisible) { - if (mounted) { - setState(() { - _isVisible = isVisible; - }); - } - } - - void _setIsFinalVideo(bool isFinalVideo) { - setState(() { - _isLastPromo = isFinalVideo; - }); - } - - Future _handleOptIn(BuildContext context) async { - setState(() { - _isSubmitting = true; - }); - - try { - await _referralService.optInRewardProgram(); - ref.invalidate(optInPositionProvider); - - setState(() { - _isSubmitting = false; - }); - - if (mounted) { - Navigator.pushAndRemoveUntil( - this.context, - MaterialPageRoute( - settings: const RouteSettings(name: 'navbar'), - builder: (context) => const Navbar(initialIndex: 3), - ), - (route) => false, - ); - } - } catch (e) { - print('Failed opting in reward program: $e'); - setState(() { - _isSubmitting = false; - }); - } - } - @override Widget build(BuildContext context) { - // Show loading state for users who haven't opted in to the reward program or if the promo video is not playing yet - if (_isLoadingParticipation || !widget.playPromoVideo) { - return ScaffoldBase( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 12, - children: [ - Image.asset('assets/quests/quests_top_logo.png', height: 24), - const LoadingTextAnimation(), - ], - ), - ), - ); - } - - if (!_isRewardProgramParticipant) { - return Scaffold( - backgroundColor: context.themeColors.background2, - body: Stack( - children: [ - QuestsPromoVideo( - isSubmitting: _isSubmitting, - closeSheet: null, // No close button for inline use - setIsFinalVideo: _setIsFinalVideo, - startFromBeginning: true, - showCloseButton: false, - isVisible: _isVisible, - ), - if (_isLastPromo) - Positioned( - bottom: 100, // Move down to avoid video text overlap - left: 0, - right: 0, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.transparent, Colors.black.useOpacity(0.8)], - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Button( - label: "I'm In", - isLoading: _isSubmitting, - variant: ButtonVariant.primary, - onPressed: () { - _handleOptIn(context); - }, - ), - ], - ), - ), - ), - ], - ), - ); - } - final associationsAsync = ref.watch(accountAssociationsProvider); final hasCompletedSetup = associationsAsync.maybeWhen( data: (associations) => associations.ethAddress != null || associations.xUsername != null, @@ -193,10 +43,6 @@ class _QuestsScreenState extends ConsumerState { refreshAssociationsData(); }, scrollController: _scrollController, - decorations: [ - const Positioned(top: 180, right: -34, child: Sphere(variant: 2, size: 194)), - const Positioned(left: -60, bottom: 0, child: Sphere(variant: 7, size: 240.68)), - ], slivers: [ SliverToBoxAdapter( child: Column( @@ -265,5 +111,4 @@ class _QuestsScreenState extends ConsumerState { ], ); } - } diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 3a4dabec..a1b9a8d1 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -1,24 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/features/components/basic_card.dart'; -import 'package:resonance_network_wallet/features/components/button.dart'; import 'package:resonance_network_wallet/features/components/copy_icon.dart'; -import 'package:resonance_network_wallet/features/components/link_text.dart'; +import 'package:resonance_network_wallet/features/components/inner_shadow_container.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/features/components/skeleton.dart'; -import 'package:resonance_network_wallet/features/components/sphere.dart'; import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/account_associations_status.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/optin_position_status.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/quest_title.dart'; -import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; -import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_stats_providers.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; import 'package:share_plus/share_plus.dart'; class ReferralsQuestScreen extends ConsumerStatefulWidget { @@ -28,10 +18,8 @@ class ReferralsQuestScreen extends ConsumerStatefulWidget { ConsumerState createState() => _ReferralsQuestScreenState(); } -class _ReferralsQuestScreenState extends ConsumerState with WidgetsBindingObserver { +class _ReferralsQuestScreenState extends ConsumerState { final ReferralService _referralService = ReferralService(); - final ScrollController _scrollController = ScrollController(); - String? _referralCode; Future _loadReferralCode() async { @@ -65,181 +53,307 @@ class _ReferralsQuestScreenState extends ConsumerState wit _loadReferralCode(); } - @override - void dispose() { - super.dispose(); - } - void refreshStatsData() { ref.invalidate(accountsStatsProvider); - } - - void refreshAssociationsData() { ref.invalidate(accountAssociationsProvider); } @override Widget build(BuildContext context) { final statsAsync = ref.watch(accountsStatsProvider); + final referralsCount = statsAsync.value?.referralCount ?? 0; - return ScaffoldBase.refreshable( - appBar: WalletAppBar(title: 'Referrals Quest'), - padding: const EdgeInsetsGeometry.all(0), - onRefresh: () async { - refreshStatsData(); - refreshAssociationsData(); - }, - scrollController: _scrollController, - decorations: [ - const Positioned(top: 180, right: -34, child: Sphere(variant: 2, size: 194)), - const Positioned(left: -60, bottom: 0, child: Sphere(variant: 7, size: 240.68)), - ], - slivers: [ - SliverToBoxAdapter( - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.only(top: 11), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(right: 44), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 17, - children: [ - _buildDecoration(), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 96), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [OptinPositionStatus(), SizedBox(width: 71)], - ), - SizedBox(height: context.isSmallHeight ? 18 : 37.0), - const AccountAssociationsStatus(), - SizedBox(height: context.isSmallHeight ? 18 : 37.0), - ..._buildAccountStats(context, statsAsync), - const SizedBox(height: 16), - LinkText( - label: 'Learn more', - url: AppConstants.shillQuestsPageUrl, - textStyle: context.themeText.smallParagraph, - ), - ], - ), - ), - ], + return ScaffoldBase( + appBar: WalletAppBar( + title: 'Referrals', + actions: [ + IconButton( + icon: const Icon(Icons.info_outline, color: Colors.white), + onPressed: () { + // TODO: Show info + }, + ), + ], + ), + child: Column( + children: [ + Expanded( + child: Stack( + children: [ + // Main Card + InnerShadowContainer( + shadows: const [ + BoxShadow( + color: Color(0x19FFFFFF), + offset: Offset(-2, -2), + blurRadius: 12, + spreadRadius: 2, + ), + BoxShadow( + color: Color(0x19FFFFFF), + offset: Offset(2, 2), + blurRadius: 12, + spreadRadius: 2, + ), + ], + borderRadius: BorderRadius.circular(4), + child: Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 80), // Leave space for the button + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x7F6734BA), + ), + borderRadius: BorderRadius.circular(4), ), ), - SizedBox(height: context.isSmallHeight ? 18 : 40), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: InkWell( - onTap: _copyReferralCode, + child: Stack( + children: [ + // Gradient Blob + Positioned( + left: -156, + top: 72, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11), + width: 531, + height: 531, decoration: ShapeDecoration( - color: context.themeColors.background, + gradient: const RadialGradient( + center: Alignment.bottomCenter, + radius: 1.2, + colors: [ + Color(0xFFFFE91F), + Color(0xFFED4CCE), + Color(0xFF0000FF), + Color(0xFF0C1014), + ], + stops: [0.0, 0.2, 0.5, 1.0], + ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), - child: Row( - spacing: 12.0, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _referralCode ?? 'Loading...', - style: context.themeText.smallParagraph, - textAlign: TextAlign.center, + ), + ), + + // Content + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Section + Center( + child: Column( + children: [ + const Text( + 'REFER FRIENDS', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + 'Invite friends. Earn rewards. \nClimb the leaderboard.', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.50), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), + ), + ], ), - const CopyIcon(), + ), + + const SizedBox(height: 40), + + // Avatars Row (Placeholders) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildAvatar(const [Color(0xFF0000FF), Color(0xFF0C1014)]), + Transform.translate(offset: const Offset(-16, 0), child: _buildAvatar(const [Color(0xFF8B0000), Color(0xFFED4CCE)])), + Transform.translate(offset: const Offset(-32, 0), child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)])), ], ), + + const SizedBox(height: 40), + + // Stats Box + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: ShapeDecoration( + color: const Color(0xFF0C1014).withOpacity(0.4), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x33F4F6F9), + ), + borderRadius: BorderRadius.circular(8), + ), + shadows: const [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 4, + offset: Offset(4, 4), + spreadRadius: 0, + ) + ], + ), + child: Column( + children: [ + _buildStatRow('Referrals', '$referralsCount', Colors.white), + const SizedBox(height: 16), + _buildStatRow('Rank', '#-', const Color(0xFFED4CCE)), // Rank not available yet + ], + ), + ), + + const Spacer(), + + // Invite Code Section + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Your invite code', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: _copyReferralCode, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: ShapeDecoration( + color: const Color(0xFF6B46C1).withOpacity(0.2), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _referralCode ?? 'Loading...', + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 12, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + const CopyIcon(width: 16, color: Color(0xFFF4F6F9)), + ], + ), + ), + ), + ], + ), + ], ), ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Button( - variant: ButtonVariant.glassOutline, - label: 'Share Referral Link', - onPressed: _shareReferralLink, + ], + ), + ), + ), + + // Bottom Button + Positioned( + bottom: 24, + left: 0, + right: 0, + child: Center( + child: GestureDetector( + onTap: _shareReferralLink, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + decoration: ShapeDecoration( + color: const Color(0x33F4F6F9), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x33F4F6F9), + ), + borderRadius: BorderRadius.circular(42), + ), + ), + child: const Text( + 'Share Link', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), ), ), - ], + ), ), - ), - const QuestTitle(), - ], + ], + ), ), - ), - ], - ); - } - - List _buildAccountStats(BuildContext context, AsyncValue statsAsync) { - return statsAsync.when( - data: (stats) => [ - _buildStatCard(context, 'Referrals:', stats.referralCount), - const SizedBox(height: 9), - _buildStatCard(context, 'Sends:', stats.sendCount), - const SizedBox(height: 9), - _buildStatCard(context, 'Reversals:', stats.reversalCount), - const SizedBox(height: 9), - _buildStatCard(context, 'Mining:', stats.miningCount), - ], - loading: () => [ - _buildStatCard(context, 'Referrals:', null), - const SizedBox(height: 9), - _buildStatCard(context, 'Sends:', null), - const SizedBox(height: 9), - _buildStatCard(context, 'Reversals:', null), - const SizedBox(height: 9), - _buildStatCard(context, 'Mining:', null), - ], - error: (error, stack) => [ - Text( - 'Error fetching account stats.', - style: context.themeText.detail?.copyWith(color: context.themeColors.textError), - ), - const SizedBox(height: 12), - Button(variant: ButtonVariant.neutral, label: 'Try again', onPressed: refreshStatsData), - ], - ); - } - - Widget _buildStatCard(BuildContext context, String title, int? stat) { - final isLoading = stat == null; - - return BasicCard( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, style: context.themeText.smallTitle), - isLoading ? const Skeleton(width: 40, height: 16) : Text('$stat', style: context.themeText.smallTitle), ], ), ); } - Widget _buildDecoration() { + Widget _buildAvatar(List colors) { return Container( - width: 85, - height: context.isSmallHeight ? 415 : 480, - decoration: const ShapeDecoration( + width: 64, + height: 64, + decoration: ShapeDecoration( gradient: LinearGradient( - begin: Alignment(0.03, -1.00), - end: Alignment(-0.03, 1), - colors: [Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: colors, ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only(topRight: Radius.circular(10), bottomRight: Radius.circular(10)), + shape: const OvalBorder( + side: BorderSide( + width: 2.67, + strokeAlign: BorderSide.strokeAlignOutside, + color: Color(0xFF0C1014), + ), ), ), ); } + + Widget _buildStatRow(String label, String value, Color valueColor) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + Text( + value, + textAlign: TextAlign.center, + style: TextStyle( + color: valueColor, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + ], + ); + } } From 729bd8b82e449f3154647633ef5de6af2e0a9dc1 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 16:53:40 +0800 Subject: [PATCH 03/23] playing arodund w gradient colors --- .../features/main/screens/quests/referrals_quest_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index a1b9a8d1..17235b16 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -122,7 +122,7 @@ class _ReferralsQuestScreenState extends ConsumerState { height: 531, decoration: ShapeDecoration( gradient: const RadialGradient( - center: Alignment.bottomCenter, + center: Alignment.bottomLeft, radius: 1.2, colors: [ Color(0xFFFFE91F), @@ -130,7 +130,7 @@ class _ReferralsQuestScreenState extends ConsumerState { Color(0xFF0000FF), Color(0xFF0C1014), ], - stops: [0.0, 0.2, 0.5, 1.0], + stops: [0.1, 0.2, 0.4, 0.6], ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), From 5e59954d4a3ec65ab14a2c97bca0fe6634a0f7e1 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 17:11:19 +0800 Subject: [PATCH 04/23] minor progress --- .../components/inner_shadow_container.dart | 20 ++-- .../quests/referrals_quest_screen.dart | 99 ++++++++----------- 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/mobile-app/lib/features/components/inner_shadow_container.dart b/mobile-app/lib/features/components/inner_shadow_container.dart index 801cf7ce..779f3013 100644 --- a/mobile-app/lib/features/components/inner_shadow_container.dart +++ b/mobile-app/lib/features/components/inner_shadow_container.dart @@ -12,13 +12,21 @@ class InnerShadowContainer extends StatelessWidget { this.borderRadius = BorderRadius.zero, }); - @override - Widget build(BuildContext context) { - return CustomPaint( - foregroundPainter: _InnerShadowPainter(shadows, borderRadius), + factory InnerShadowContainer.standard({required Widget child}) { + return InnerShadowContainer( + shadows: const [ + BoxShadow(color: Color(0x19FFFFFF), offset: Offset(-2, -2), blurRadius: 12, spreadRadius: 2), + BoxShadow(color: Color(0x19FFFFFF), offset: Offset(2, 2), blurRadius: 12, spreadRadius: 2), + ], + borderRadius: BorderRadius.circular(4), child: child, ); } + + @override + Widget build(BuildContext context) { + return CustomPaint(foregroundPainter: _InnerShadowPainter(shadows, borderRadius), child: child); + } } class _InnerShadowPainter extends CustomPainter { @@ -40,12 +48,12 @@ class _InnerShadowPainter extends CustomPainter { ..maskFilter = MaskFilter.blur(BlurStyle.normal, shadow.blurSigma); final outerRect = rect.inflate(shadow.blurRadius + shadow.spreadRadius + 20); - + final path = Path() ..fillType = PathFillType.evenOdd ..addRect(outerRect) ..addRRect(rrect.shift(-shadow.offset)); - + canvas.drawPath(path, paint); } } diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 17235b16..38ab0227 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -81,36 +81,21 @@ class _ReferralsQuestScreenState extends ConsumerState { child: Stack( children: [ // Main Card - InnerShadowContainer( - shadows: const [ - BoxShadow( - color: Color(0x19FFFFFF), - offset: Offset(-2, -2), - blurRadius: 12, - spreadRadius: 2, - ), - BoxShadow( - color: Color(0x19FFFFFF), - offset: Offset(2, 2), - blurRadius: 12, - spreadRadius: 2, - ), - ], - borderRadius: BorderRadius.circular(4), - child: Container( - width: double.infinity, - margin: const EdgeInsets.only(bottom: 80), // Leave space for the button - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: const Color(0xFF0C1014), - shape: RoundedRectangleBorder( - side: const BorderSide( - width: 1, - color: Color(0x7F6734BA), - ), - borderRadius: BorderRadius.circular(4), + Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 80), // Leave space for the button + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x7F6734BA), ), + borderRadius: BorderRadius.circular(4), ), + ), + child: InnerShadowContainer.standard( child: Stack( children: [ // Gradient Blob @@ -182,9 +167,9 @@ class _ReferralsQuestScreenState extends ConsumerState { Transform.translate(offset: const Offset(-32, 0), child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)])), ], ), - + const SizedBox(height: 40), - + // Stats Box Container( width: double.infinity, @@ -215,12 +200,12 @@ class _ReferralsQuestScreenState extends ConsumerState { ], ), ), - + const Spacer(), - + // Invite Code Section Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( 'Your invite code', @@ -238,7 +223,7 @@ class _ReferralsQuestScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(14), decoration: ShapeDecoration( - color: const Color(0xFF6B46C1).withOpacity(0.2), + color: const Color(0xFF6B46C1).useOpacity(0.8), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: Row( @@ -264,38 +249,38 @@ class _ReferralsQuestScreenState extends ConsumerState { ), ), ], + ), ), - ), - ), + ), // Bottom Button Positioned( bottom: 24, left: 0, right: 0, - child: Center( - child: GestureDetector( - onTap: _shareReferralLink, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - decoration: ShapeDecoration( - color: const Color(0x33F4F6F9), - shape: RoundedRectangleBorder( - side: const BorderSide( - width: 1, - color: Color(0x33F4F6F9), - ), - borderRadius: BorderRadius.circular(42), + child: GestureDetector( + onTap: _shareReferralLink, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: ShapeDecoration( + color: const Color(0x33F4F6F9), + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: Color(0x33F4F6F9), ), + borderRadius: BorderRadius.circular(42), ), - child: const Text( - 'Share Link', - style: TextStyle( - color: Color(0xFFF4F6F9), - fontSize: 18, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), + ), + alignment: Alignment.center, + child: const Text( + 'Share Link', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, ), ), ), From 76cd29cccca90c79c4a44d1c2e483a61a5132b3b Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 17:19:20 +0800 Subject: [PATCH 05/23] sane mode --- .../quests/referrals_quest_screen.dart | 148 +++++++----------- 1 file changed, 58 insertions(+), 90 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 38ab0227..6ad089e2 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -69,36 +69,28 @@ class _ReferralsQuestScreenState extends ConsumerState { actions: [ IconButton( icon: const Icon(Icons.info_outline, color: Colors.white), - onPressed: () { - // TODO: Show info - }, + onPressed: () {}, ), ], ), - child: Column( - children: [ - Expanded( - child: Stack( - children: [ - // Main Card - Container( - width: double.infinity, - margin: const EdgeInsets.only(bottom: 80), // Leave space for the button - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: const Color(0xFF0C1014), - shape: RoundedRectangleBorder( - side: const BorderSide( - width: 1, - color: Color(0x7F6734BA), - ), - borderRadius: BorderRadius.circular(4), - ), + child: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: Container( + width: double.infinity, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x7F6734BA)), + borderRadius: BorderRadius.circular(4), ), - child: InnerShadowContainer.standard( - child: Stack( + ), + child: InnerShadowContainer.standard( + child: Stack( children: [ - // Gradient Blob Positioned( left: -156, top: 72, @@ -121,14 +113,11 @@ class _ReferralsQuestScreenState extends ConsumerState { ), ), ), - - // Content Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header Section Center( child: Column( children: [ @@ -155,32 +144,23 @@ class _ReferralsQuestScreenState extends ConsumerState { ], ), ), - const SizedBox(height: 40), - - // Avatars Row (Placeholders) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildAvatar(const [Color(0xFF0000FF), Color(0xFF0C1014)]), - Transform.translate(offset: const Offset(-16, 0), child: _buildAvatar(const [Color(0xFF8B0000), Color(0xFFED4CCE)])), - Transform.translate(offset: const Offset(-32, 0), child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)])), - ], - ), - + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildAvatar(const [Color(0xFF0000FF), Color(0xFF0C1014)]), + Transform.translate(offset: const Offset(-16, 0), child: _buildAvatar(const [Color(0xFF8B0000), Color(0xFFED4CCE)])), + Transform.translate(offset: const Offset(-32, 0), child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)])), + ], + ), const SizedBox(height: 40), - - // Stats Box Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: const Color(0xFF0C1014).withOpacity(0.4), + color: const Color(0xFF0C1014).useOpacity(0.4), shape: RoundedRectangleBorder( - side: const BorderSide( - width: 1, - color: Color(0x33F4F6F9), - ), + side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), borderRadius: BorderRadius.circular(8), ), shadows: const [ @@ -188,7 +168,6 @@ class _ReferralsQuestScreenState extends ConsumerState { color: Color(0x3F000000), blurRadius: 4, offset: Offset(4, 4), - spreadRadius: 0, ) ], ), @@ -196,14 +175,11 @@ class _ReferralsQuestScreenState extends ConsumerState { children: [ _buildStatRow('Referrals', '$referralsCount', Colors.white), const SizedBox(height: 16), - _buildStatRow('Rank', '#-', const Color(0xFFED4CCE)), // Rank not available yet + _buildStatRow('Rank', '#-', const Color(0xFFED4CCE)), ], ), ), - const Spacer(), - - // Invite Code Section Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -217,13 +193,13 @@ class _ReferralsQuestScreenState extends ConsumerState { ), ), const SizedBox(height: 12), - GestureDetector( + GestureDetector( onTap: _copyReferralCode, child: Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: ShapeDecoration( - color: const Color(0xFF6B46C1).useOpacity(0.8), + color: Colors.white.useOpacity(0.3), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: Row( @@ -249,47 +225,39 @@ class _ReferralsQuestScreenState extends ConsumerState { ), ), ], - ), ), - ), - - // Bottom Button - Positioned( - bottom: 24, - left: 0, - right: 0, - child: GestureDetector( - onTap: _shareReferralLink, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: ShapeDecoration( - color: const Color(0x33F4F6F9), - shape: RoundedRectangleBorder( - side: const BorderSide( - width: 1, - color: Color(0x33F4F6F9), - ), - borderRadius: BorderRadius.circular(42), - ), - ), - alignment: Alignment.center, - child: const Text( - 'Share Link', - style: TextStyle( - color: Color(0xFFF4F6F9), - fontSize: 18, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + ), + ), + ), + const SizedBox(height: 32), + GestureDetector( + onTap: _shareReferralLink, + child: InnerShadowContainer.standard( + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: ShapeDecoration( + color: const Color(0x33F4F6F9), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), + borderRadius: BorderRadius.circular(42), + ), + ), + alignment: Alignment.center, + child: const Text( + 'Share Link', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, ), ), ), - ], + ), ), - ), - ], + ], + ), ), ); } From 4e20023a2a291bbb57d5fb3d5c023327ad57488d Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 17:22:22 +0800 Subject: [PATCH 06/23] text more better --- mobile-app/lib/services/referral_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/lib/services/referral_service.dart b/mobile-app/lib/services/referral_service.dart index d9c6588c..46fa64b4 100644 --- a/mobile-app/lib/services/referral_service.dart +++ b/mobile-app/lib/services/referral_service.dart @@ -151,7 +151,7 @@ class ReferralService { String link = generateReferralLink(referralCode); String message = - "Most L1s aren't ready for quantum threats. This one is.\nI'm on the @QuantusNetwork testnet stacking early points for rewards.\nUse my referral link so we both earn points: $referralCode\n\nDownload the wallet & get in early\n$link"; + "Most L1s aren't ready for quantum threats. This one is.\nI'm on the @QuantusNetwork testnet stacking early points for rewards.\nUse my referral link so we both earn points:\n$referralCode\n\nDownload the wallet & get in early\n\n$link"; return ShareParams( text: message, From 8402f87f753404c9ff3ab34403e4b3380d02ee2d Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 17:36:33 +0800 Subject: [PATCH 07/23] how it works dialog --- .../quests/referrals_quest_screen.dart | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 6ad089e2..923b4aff 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -47,6 +47,92 @@ class _ReferralsQuestScreenState extends ConsumerState { } } + void _showHowItWorksDialog() { + showDialog( + context: context, + barrierColor: Colors.black.withValues(alpha: 0.7), + builder: (context) => GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Scaffold( + backgroundColor: Colors.transparent, + body: Center( + child: GestureDetector( + onTap: () {}, + child: Container( + width: 280, + padding: const EdgeInsets.all(24), + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x66F4F6F9)), + borderRadius: BorderRadius.circular(20), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'HOW IT WORKS', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: const Icon(Icons.close, color: Colors.white, size: 24), + ), + ], + ), + const SizedBox(height: 32), + _buildStep('Step 1', 'Invite users using your unique code'), + const SizedBox(height: 16), + _buildStep('Step 2', 'They must create a Quantus Wallet. Referrals without wallet creation won\'t count'), + const SizedBox(height: 16), + _buildStep('Step 3', 'Climb the leaderboard. Rank updates automatically'), + ], + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildStep(String title, String description) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + Text( + description, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.50), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), + ), + ], + ); + } + @override void initState() { super.initState(); @@ -69,7 +155,7 @@ class _ReferralsQuestScreenState extends ConsumerState { actions: [ IconButton( icon: const Icon(Icons.info_outline, color: Colors.white), - onPressed: () {}, + onPressed: _showHowItWorksDialog, ), ], ), From 862cab97e83f63f2059c93c7390cbb08a1e8c73d Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 18:26:31 +0800 Subject: [PATCH 08/23] correct color sequence --- .../screens/quests/referrals_quest_screen.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 923b4aff..8c07385f 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -177,23 +177,25 @@ class _ReferralsQuestScreenState extends ConsumerState { child: InnerShadowContainer.standard( child: Stack( children: [ - Positioned( + Positioned.fill( left: -156, top: 72, child: Container( width: 531, height: 531, + clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( gradient: const RadialGradient( - center: Alignment.bottomLeft, - radius: 1.2, + center: Alignment(0.77, -0.77), + radius: 1.8, colors: [ - Color(0xFFFFE91F), - Color(0xFFED4CCE), - Color(0xFF0000FF), Color(0xFF0C1014), + Color(0xFF0000FF), + Color(0xFFED4CCE), + Color(0xFFFFE91F), + ], - stops: [0.1, 0.2, 0.4, 0.6], + stops: [0.45, 0.53, 0.62, 0.65], ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), From 267e3f495d9bee86c3c8a8c33eb8a4ca108ad85f Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 18:37:35 +0800 Subject: [PATCH 09/23] update king of the shill --- .../quests/king_of_the_shill_screen.dart | 577 ++++++++++++------ 1 file changed, 397 insertions(+), 180 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart index 1eccde51..223eb02f 100644 --- a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart @@ -1,20 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/features/components/button.dart'; -import 'package:resonance_network_wallet/features/components/link_text.dart'; +import 'package:resonance_network_wallet/features/components/inner_shadow_container.dart'; import 'package:resonance_network_wallet/features/components/raid_submission_action_sheet.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/features/components/sphere.dart'; import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/account_associations_status.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/optin_position_status.dart'; -import 'package:resonance_network_wallet/features/main/screens/quests/quest_title.dart'; -import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; -import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/raider_quest_providers.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; -import 'package:resonance_network_wallet/shared/extensions/snackbar_extensions.dart'; +import 'package:url_launcher/url_launcher.dart'; class KingOfTheShillScreen extends ConsumerStatefulWidget { const KingOfTheShillScreen({super.key}); @@ -23,201 +15,426 @@ class KingOfTheShillScreen extends ConsumerStatefulWidget { ConsumerState createState() => _KingOfTheShillScreenState(); } -class _KingOfTheShillScreenState extends ConsumerState with WidgetsBindingObserver { - final _taskmasterService = TaskmasterService(); - final ScrollController _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); +class _KingOfTheShillScreenState extends ConsumerState { + void _showHowItWorksDialog() { + showDialog( + context: context, + barrierColor: Colors.black.withValues(alpha: 0.7), + builder: (context) => GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Scaffold( + backgroundColor: Colors.transparent, + body: Center( + child: GestureDetector( + onTap: () {}, + child: Container( + width: 280, + padding: const EdgeInsets.all(24), + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x66F4F6F9)), + borderRadius: BorderRadius.circular(20), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'HOW IT WORKS', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: const Icon(Icons.close, color: Colors.white, size: 24), + ), + ], + ), + const SizedBox(height: 32), + _buildStep('Step 1', 'Find an active raid on X (Twitter)'), + const SizedBox(height: 16), + _buildStep('Step 2', 'Reply to the raid post with your shill'), + const SizedBox(height: 16), + _buildStep('Step 3', 'Submit your reply URL here to get verified and earn rewards'), + ], + ), + ), + ), + ), + ), + ), + ); } - @override - void dispose() { - super.dispose(); + Widget _buildStep(String title, String description) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + Text( + description, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.50), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), + ), + ], + ); } void refreshRaiderSubmissions() { ref.invalidate(raiderSubmissionsProvider); } - String? extractXStatusId(String url) { - final uri = Uri.tryParse(url); - if (uri == null) return null; - - // Expected path: /{username}/status/{id} - final segments = uri.pathSegments; - - if (segments.length >= 3 && segments[1] == 'status') { - final id = segments[2]; - return RegExp(r'^\d+$').hasMatch(id) ? id : null; - } - - return null; + String _formatTimeAgo(String url) { + return ''; } - Future _handleRemoveSubmission(String id) async { - try { - await _taskmasterService.removeRaidSubmission(id); - if (mounted) { - context.showSuccessSnackbar(title: 'Success removed!', message: 'Success removing raid submission!'); - } - ref.invalidate(raiderSubmissionsProvider); - } catch (e) { - print('Failed removing raid submission: $e'); - - if (mounted) { - context.showErrorSnackbar(title: 'Failed removing!', message: e.toString()); - } + Future _openUrl(String url) async { + final uri = Uri.tryParse(url); + if (uri != null && await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); } } @override Widget build(BuildContext context) { final raiderSubmissionsAsync = ref.watch(raiderSubmissionsProvider); - final effectiveSpacing = context.isSmallHeight ? 24.0 : 36.0; - return ScaffoldBase.refreshable( - appBar: WalletAppBar(title: 'King of The Shill'), - onRefresh: () async { - refreshRaiderSubmissions(); - }, - scrollController: _scrollController, - decorations: [ - const Positioned(top: 180, right: -34, child: Sphere(variant: 2, size: 194)), - const Positioned(left: -60, bottom: 0, child: Sphere(variant: 7, size: 240.68)), - ], - slivers: [ - SliverToBoxAdapter( - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.only(top: 11), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 17, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 96), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [OptinPositionStatus()], - ), - SizedBox(height: effectiveSpacing), - const AccountAssociationsStatus(), - SizedBox(height: effectiveSpacing), - ...raiderSubmissionsAsync.when( - loading: () => [ - Center(child: CircularProgressIndicator(color: context.themeColors.circularLoader)), - ], - error: (error, stackTrace) => [ - Text( - 'Error fetching raider submissions.', - style: context.themeText.detail?.copyWith(color: context.themeColors.textError), + return ScaffoldBase( + appBar: WalletAppBar( + title: 'King of The Shill', + actions: [ + IconButton( + icon: const Icon(Icons.info_outline, color: Colors.white), + onPressed: _showHowItWorksDialog, + ), + ], + ), + child: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: Container( + width: double.infinity, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x7F6734BA)), + borderRadius: BorderRadius.circular(4), + ), + ), + child: InnerShadowContainer.standard( + child: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: const BoxDecoration( + gradient: RadialGradient( + center: Alignment(-0.8, 0.6), + radius: 1.0, + colors: [ + Color(0xFFFFE91F), + Color(0xFFED4CCE), + Color(0xFF0000FF), + Color(0xFF0C1014), + ], + stops: [0.05, 0.15, 0.35, 0.6], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Column( + children: [ + const Text( + 'KING OF THE SHILL', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), ), - const SizedBox(height: 12), - Button( - variant: ButtonVariant.neutral, - label: 'Try again', - onPressed: refreshRaiderSubmissions, + const SizedBox(height: 8), + Text( + 'Join social raids. Get rewarded for\nverified posts.', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.50), + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + ), ), ], - data: (state) { - switch (state) { - case RaiderSubmissionsOk(): - return [ - LinkText( - label: 'Learn more', - url: AppConstants.raidQuestsPageUrl, - textStyle: context.themeText.smallParagraph, - ), - SizedBox(height: effectiveSpacing), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Raid Submissions', style: context.themeText.smallTitle), - const SizedBox(width: 12), - InkWell( - child: Container( - decoration: ShapeDecoration( - color: context.themeColors.buttonNeutral, - shape: RoundedRectangleBorder( - borderRadius: BorderRadiusGeometry.circular(8), - ), - ), - child: const Icon(Icons.add, color: Colors.black), - ), - onTap: () { - showRaidSubmissionActionSheet(context); - }, - ), - ], - ), - const SizedBox(height: 8), - if (state.submissions.isNotEmpty) - Column( - spacing: 4, - children: state.submissions.asMap().entries.map((entry) { - final index = entry.key + 1; - final value = entry.value; - final label = extractXStatusId(value) ?? 'Unknown'; - - return Row( - children: [ - Text('$index. '), - LinkText( - label: label, - url: value, - textStyle: context.themeText.smallParagraph, - ), - InkWell( - child: Icon(Icons.delete, color: context.themeColors.buttonDanger), - onTap: () { - _handleRemoveSubmission(label); - }, - ), - ], - ); - }).toList(), - ) - else - Text( - "You haven't submitted anything yet", - style: context.themeText.smallParagraph, - ), - ]; - - case NoActiveRaid(): - return [ - LinkText( - label: 'Learn more', - url: AppConstants.raidQuestsPageUrl, - textStyle: context.themeText.smallParagraph, - ), - ]; - - case NoTwitterLinked(): - return [Text('Please link your X account', style: context.themeText.smallTitle)]; - } - }, ), - ], - ), + ), + const SizedBox(height: 40), + raiderSubmissionsAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: Colors.white), + ), + error: (_, __) => _buildStatsBox(0, 0), + data: (state) { + if (state is RaiderSubmissionsOk) { + return _buildStatsBox(state.submissions.length, 0); + } + return _buildStatsBox(0, 0); + }, + ), + const SizedBox(height: 24), + _buildPastSubmissionsSection(raiderSubmissionsAsync), + const Spacer(), + _buildSubmitSection(), + ], ), - ], + ), + ], + ), + ), + ), + ), + const SizedBox(height: 32), + GestureDetector( + onTap: () => showRaidSubmissionActionSheet(context), + child: InnerShadowContainer.standard( + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: ShapeDecoration( + color: const Color(0x33F4F6F9), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), + borderRadius: BorderRadius.circular(42), ), - SizedBox(height: context.isSmallHeight ? 18 : 40), - ], + ), + alignment: Alignment.center, + child: const Text( + 'Add Raid Submission', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildStatsBox(int submissions, int verifiedPosts) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: ShapeDecoration( + color: const Color(0xFF0C1014).withOpacity(0.4), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), + borderRadius: BorderRadius.circular(8), + ), + shadows: const [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 4, + offset: Offset(4, 4), + ) + ], + ), + child: Column( + children: [ + _buildStatRow('Submissions', '$submissions', Colors.white), + const SizedBox(height: 16), + _buildStatRow('Verified posts', verifiedPosts.toString().padLeft(2, '0'), Colors.white), + const SizedBox(height: 16), + _buildStatRow('Rank', '#-', const Color(0xFFED4CCE)), + ], + ), + ); + } + + Widget _buildStatRow(String label, String value, Color valueColor) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + Text( + value, + style: TextStyle( + color: valueColor, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + ), + ), + ], + ); + } + + Widget _buildPastSubmissionsSection(AsyncValue raiderSubmissionsAsync) { + return Column( + children: [ + const Text( + 'Past Submissions', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), + borderRadius: BorderRadius.circular(8), + ), + ), + child: raiderSubmissionsAsync.when( + loading: () => const Center( + child: SizedBox( + height: 80, + child: CircularProgressIndicator(color: Colors.white), + ), + ), + error: (_, __) => Text( + 'Failed to load submissions', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.5), + fontSize: 12, + fontFamily: 'Inter', + ), + ), + data: (state) { + if (state is RaiderSubmissionsOk && state.submissions.isNotEmpty) { + return Column( + children: state.submissions.take(4).map((url) => _buildSubmissionRow(url)).toList(), + ); + } + return Text( + 'No submissions yet', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.5), + fontSize: 12, + fontFamily: 'Inter', ), + ); + }, + ), + ), + ], + ); + } + + Widget _buildSubmissionRow(String url) { + final displayUrl = url.length > 30 ? '${url.substring(0, 30)}...' : url; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => _openUrl(url), + child: Text( + displayUrl, + style: const TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 12, + fontFamily: 'Inter', + fontWeight: FontWeight.w400, + decoration: TextDecoration.underline, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ), + Text( + _formatTimeAgo(url), + style: TextStyle( + color: Colors.white.withValues(alpha: 0.5), + fontSize: 10, + fontFamily: 'Fira Code', + ), + ), + ], + ), + ); + } + + Widget _buildSubmitSection() { + return Column( + children: [ + const Text( + 'Submit Your Reply', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () => showRaidSubmissionActionSheet(context), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: ShapeDecoration( + color: Colors.white.withValues(alpha: 0.24), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: Text( + '|https://x.com/....', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.5), + fontSize: 12, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, ), - const QuestTitle(), - ], + ), ), ), ], From 918a1a3fbfe2dc586cbcd0d1fe8d145fcfa59578 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 18:57:41 +0800 Subject: [PATCH 10/23] gradient fixed --- .../quests/king_of_the_shill_screen.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart index 223eb02f..9182ab2f 100644 --- a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; @@ -150,18 +152,16 @@ class _KingOfTheShillScreenState extends ConsumerState { child: Stack( children: [ Positioned.fill( - child: Container( - decoration: const BoxDecoration( - gradient: RadialGradient( - center: Alignment(-0.8, 0.6), - radius: 1.0, - colors: [ - Color(0xFFFFE91F), - Color(0xFFED4CCE), - Color(0xFF0000FF), - Color(0xFF0C1014), - ], - stops: [0.05, 0.15, 0.35, 0.6], + child: ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 52, sigmaY: 52), + child: Container( + decoration: const BoxDecoration( + gradient: RadialGradient( + center: Alignment(0.77, -0.56), + radius: 2.8, + colors: [Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F)], + stops: [0.45, 0.54, 0.57], + ), ), ), ), From 75028794358c43c1999f21d25b993d079d5178c6 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 19:05:06 +0800 Subject: [PATCH 11/23] better error message, better loading indicators --- .../raid_submission_action_sheet.dart | 7 ++++++- .../quests/king_of_the_shill_screen.dart | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/mobile-app/lib/features/components/raid_submission_action_sheet.dart b/mobile-app/lib/features/components/raid_submission_action_sheet.dart index 70c4aacb..0f7554f5 100644 --- a/mobile-app/lib/features/components/raid_submission_action_sheet.dart +++ b/mobile-app/lib/features/components/raid_submission_action_sheet.dart @@ -75,9 +75,14 @@ class _RaidSubmissionActionSheetState extends ConsumerState { const SizedBox(height: 40), raiderSubmissionsAsync.when( loading: () => const Center( - child: CircularProgressIndicator(color: Colors.white), + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), + ), ), error: (_, __) => _buildStatsBox(0, 0), data: (state) { @@ -334,10 +338,14 @@ class _KingOfTheShillScreenState extends ConsumerState { ), ), child: raiderSubmissionsAsync.when( - loading: () => const Center( - child: SizedBox( - height: 80, - child: CircularProgressIndicator(color: Colors.white), + loading: () => const SizedBox( + height: 80, + child: Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), + ), ), ), error: (_, __) => Text( From 9da2768e11c463e06abc25c7bc42e74f495853e1 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 20:03:09 +0800 Subject: [PATCH 12/23] initial account setup --- .../complete_setup_action_sheet.dart | 70 +++++++++----- .../main/screens/quests/quests_screen.dart | 95 +++++++++++++++---- 2 files changed, 120 insertions(+), 45 deletions(-) diff --git a/mobile-app/lib/features/components/complete_setup_action_sheet.dart b/mobile-app/lib/features/components/complete_setup_action_sheet.dart index d6d3bf5c..e399758b 100644 --- a/mobile-app/lib/features/components/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/components/complete_setup_action_sheet.dart @@ -30,8 +30,10 @@ class _CompleteSetupActionSheetState extends ConsumerState _bioController.text.contains('@QuantusNetwork'); + bool get _isBioValid => _bioController.text.trim().toLowerCase().contains('@quantusnetwork'); @override void dispose() { @@ -44,13 +46,15 @@ class _CompleteSetupActionSheetState extends ConsumerState _isSubmitting = true); try { - // 1. Opt-in to reward program (idempotent-ish, or safe to call) - // Requirement: "If it is a new user we set their opt in to true" - await _taskmasterService.optInRewardProgram(); + final optInStatus = ref.read(optInPositionProvider); + final isAlreadyOptedIn = optInStatus.maybeWhen( + data: (position) => position.position > 0, + orElse: () => false, + ); + + if (!isAlreadyOptedIn) { + await _taskmasterService.optInRewardProgram(); + } - // 2. Link ETH Address - if (ethAddress.isNotEmpty) { + if (ethAddress.isNotEmpty && ethAddress != _originalEthAddress) { await _taskmasterService.associateEthAddress(ethAddress); } - // 3. Link X Handle - if (xHandle.isNotEmpty) { - final handle = xHandle.startsWith('@') ? xHandle.substring(1) : xHandle; - await _taskmasterService.associateXHandle(handle); + final normalizedHandle = xHandle.startsWith('@') ? xHandle.substring(1) : xHandle; + if (normalizedHandle.isNotEmpty && normalizedHandle != _originalXUsername) { + await _taskmasterService.associateXHandle(normalizedHandle); } - // 4. Refresh data ref.invalidate(accountAssociationsProvider); ref.invalidate(optInPositionProvider); @@ -111,7 +118,15 @@ class _CompleteSetupActionSheetState extends ConsumerState setState(() {}), + autocorrect: false, + textCapitalization: TextCapitalization.none, style: const TextStyle( color: Color(0xFFF4F6F9), fontSize: 14, diff --git a/mobile-app/lib/features/main/screens/quests/quests_screen.dart b/mobile-app/lib/features/main/screens/quests/quests_screen.dart index 087a5b73..288216c8 100644 --- a/mobile-app/lib/features/main/screens/quests/quests_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/quests_screen.dart @@ -19,6 +19,7 @@ class QuestsScreen extends ConsumerStatefulWidget { class _QuestsScreenState extends ConsumerState { final ScrollController _scrollController = ScrollController(); + bool _showSetupTooltip = true; void refreshStatsData() { ref.invalidate(accountsStatsProvider); @@ -28,6 +29,12 @@ class _QuestsScreenState extends ConsumerState { ref.invalidate(accountAssociationsProvider); } + void _dismissTooltip() { + setState(() { + _showSetupTooltip = false; + }); + } + @override Widget build(BuildContext context) { final associationsAsync = ref.watch(accountAssociationsProvider); @@ -36,6 +43,8 @@ class _QuestsScreenState extends ConsumerState { orElse: () => false, ); + final showTooltip = !hasCompletedSetup && _showSetupTooltip; + return ScaffoldBase.refreshable( backgroundColor: const Color(0xFF0C1014), onRefresh: () async { @@ -49,7 +58,7 @@ class _QuestsScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 24), - _buildHeader(context, hasCompletedSetup), + _buildHeader(context, hasCompletedSetup, showTooltip), const SizedBox(height: 48), _buildQuestCards(context, hasCompletedSetup), ], @@ -59,33 +68,79 @@ class _QuestsScreenState extends ConsumerState { ); } - Widget _buildHeader(BuildContext context, bool hasCompletedSetup) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Widget _buildHeader(BuildContext context, bool hasCompletedSetup, bool showTooltip) { + return Column( + crossAxisAlignment: CrossAxisAlignment.end, children: [ - Image.asset( - 'assets/quests/quests_top_logo.png', - height: 24, - fit: BoxFit.contain, - ), - GestureDetector( - onTap: () => showCompleteSetupActionSheet(context), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - decoration: ShapeDecoration( - color: context.themeColors.settingCard, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset( + 'assets/quests/quests_top_logo.png', + height: 24, + fit: BoxFit.contain, ), - child: Text( - hasCompletedSetup ? 'Setup' : 'Complete Setup', - style: context.themeText.smallParagraph, + GestureDetector( + onTap: () => showCompleteSetupActionSheet(context), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: ShapeDecoration( + color: context.themeColors.settingCard, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + child: Text( + hasCompletedSetup ? 'Setup' : 'Complete Setup', + style: context.themeText.smallParagraph, + ), + ), ), - ), + ], ), + if (showTooltip) ...[ + const SizedBox(height: 8), + _buildSetupTooltip(), + ], ], ); } + Widget _buildSetupTooltip() { + return Container( + padding: const EdgeInsets.all(14), + decoration: ShapeDecoration( + color: const Color(0xFF0C1014), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Color(0x66F4F6F9)), + borderRadius: BorderRadius.circular(4), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 149, + child: Text( + 'Complete setup to unlock quests.', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w400, + height: 1.35, + ), + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: _dismissTooltip, + child: const Icon(Icons.close, color: Colors.white, size: 14), + ), + ], + ), + ); + } + Widget _buildQuestCards(BuildContext context, bool hasCompletedSetup) { return Column( children: [ From f24f4bf9986104db1a0f80ce4f4674fd4124a549 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 21:08:13 +0800 Subject: [PATCH 13/23] add disconnect buttons --- .../complete_setup_action_sheet.dart | 226 ++++++++++++++++-- .../lib/features/components/quest_card.dart | 17 +- .../main/screens/quests/quests_screen.dart | 28 ++- 3 files changed, 235 insertions(+), 36 deletions(-) diff --git a/mobile-app/lib/features/components/complete_setup_action_sheet.dart b/mobile-app/lib/features/components/complete_setup_action_sheet.dart index e399758b..f179c952 100644 --- a/mobile-app/lib/features/components/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/components/complete_setup_action_sheet.dart @@ -173,7 +173,7 @@ class _CompleteSetupActionSheetState extends ConsumerState Function() onConfirm, + }) { + showModalBottomSheet( + context: context, + isDismissible: true, + backgroundColor: const Color(0xFF0C1014), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (sheetContext) => _UnlinkConfirmationSheet( + title: title, + onConfirm: onConfirm, + dangerColor: context.themeColors.buttonDanger, + ), + ); + } + + Future _handleUnlinkEth() async { + await _taskmasterService.dissociateEthAddress(); + _ethAddressController.clear(); + _originalEthAddress = null; + ref.invalidate(accountAssociationsProvider); + if (mounted) { + context.showSuccessSnackbar(title: 'Success', message: 'ETH address unlinked'); + setState(() {}); + } + } + + Future _handleUnlinkX() async { + await _taskmasterService.dissociateXAccount(); + _xHandleController.clear(); + _originalXUsername = null; + ref.invalidate(accountAssociationsProvider); + if (mounted) { + context.showSuccessSnackbar(title: 'Success', message: 'X account unlinked'); + setState(() {}); + } + } + Widget _buildForm() { return Column( mainAxisSize: MainAxisSize.min, @@ -218,12 +259,22 @@ class _CompleteSetupActionSheetState extends ConsumerState _showUnlinkConfirmation( + title: 'ETH Address', + onConfirm: _handleUnlinkEth, + ), ), const SizedBox(height: 32), _buildInputSection( title: 'Connect your X handle', controller: _xHandleController, hintText: 'Enter username', + canUnlink: _originalXUsername != null, + onUnlink: () => _showUnlinkConfirmation( + title: 'X Account', + onConfirm: _handleUnlinkX, + ), ), const SizedBox(height: 32), _buildBioSection(), @@ -235,6 +286,8 @@ class _CompleteSetupActionSheetState extends ConsumerState Function() onConfirm; + final Color dangerColor; + + const _UnlinkConfirmationSheet({ + required this.title, + required this.onConfirm, + required this.dangerColor, + }); + + @override + State<_UnlinkConfirmationSheet> createState() => _UnlinkConfirmationSheetState(); +} + +class _UnlinkConfirmationSheetState extends State<_UnlinkConfirmationSheet> { + bool _isUnlinking = false; + + Future _handleUnlink() async { + setState(() => _isUnlinking = true); + try { + await widget.onConfirm(); + if (mounted) Navigator.pop(context); + } catch (e) { + if (mounted) { + setState(() => _isUnlinking = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e'), backgroundColor: widget.dangerColor), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Unlink ${widget.title}?', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + 'Are you sure you want to unlink your ${widget.title}?', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.5), + fontSize: 14, + fontFamily: 'Inter', + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + GestureDetector( + onTap: _isUnlinking ? null : _handleUnlink, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: ShapeDecoration( + color: widget.dangerColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(42)), + ), + alignment: Alignment.center, + child: _isUnlinking + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), + ) + : const Text( + 'Unlink', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: _isUnlinking ? null : () => Navigator.pop(context), + child: Opacity( + opacity: _isUnlinking ? 0.5 : 1.0, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 14), + decoration: ShapeDecoration( + color: const Color(0x33F4F6F9), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(42)), + ), + alignment: Alignment.center, + child: const Text( + 'Cancel', + style: TextStyle( + color: Color(0xFFF4F6F9), + fontSize: 16, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app/lib/features/components/quest_card.dart b/mobile-app/lib/features/components/quest_card.dart index 07a43c44..ce0d1e61 100644 --- a/mobile-app/lib/features/components/quest_card.dart +++ b/mobile-app/lib/features/components/quest_card.dart @@ -7,13 +7,13 @@ class QuestCard extends StatelessWidget { final String description; final String actionLabel; final VoidCallback? onTap; + final VoidCallback? onDisabledTap; final List gradientColors; final List? gradientStops; final AlignmentGeometry gradientCenter; final Color borderColor; final bool isDisabled; - // Background rectangle positioning final double bgRectLeft; final double bgRectTop; final double bgRectWidth; @@ -25,6 +25,7 @@ class QuestCard extends StatelessWidget { required this.description, required this.actionLabel, required this.onTap, + this.onDisabledTap, required this.gradientColors, this.gradientStops, required this.gradientCenter, @@ -38,6 +39,7 @@ class QuestCard extends StatelessWidget { factory QuestCard.referFriends({ required VoidCallback? onTap, + VoidCallback? onDisabledTap, bool isDisabled = false, }) { return QuestCard( @@ -45,18 +47,17 @@ class QuestCard extends StatelessWidget { description: 'Earn for every friend who joins\nQuantus using your link.', actionLabel: 'View Referrals', onTap: onTap, + onDisabledTap: onDisabledTap, gradientColors: const [ Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F), ], - // Exact stops from Figma: 45%, 53%, 62%, 65% gradientStops: const [0.45, 0.53, 0.62, 0.65], gradientCenter: const Alignment(0.77, 0.22), borderColor: const Color(0x7F6734BA), isDisabled: isDisabled, - // Default positioning for Refer Friends bgRectLeft: -127, bgRectTop: -198, ); @@ -64,24 +65,26 @@ class QuestCard extends StatelessWidget { factory QuestCard.kingOfTheShill({ required VoidCallback? onTap, + VoidCallback? onDisabledTap, bool isDisabled = false, }) { return QuestCard( title: 'KING OF THE SHILL', - description: 'Participate in social raids and earn rewards for verified posts.', + description: isDisabled + ? 'Link your X account to participate!' + : 'Participate in social raids and earn rewards for verified posts.', actionLabel: 'View Raids', onTap: onTap, + onDisabledTap: onDisabledTap, gradientColors: const [ Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F), ], - // Exact stops from Figma: 45%, 54%, 57% gradientStops: const [0.45, 0.54, 0.57], gradientCenter: const Alignment(0.77, 0.22), borderColor: const Color(0x7F773F56), isDisabled: isDisabled, - // Same positioning for now, but exposed for adjustment bgRectLeft: -127, bgRectTop: -198, ); @@ -90,7 +93,7 @@ class QuestCard extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: isDisabled ? null : onTap, + onTap: isDisabled ? onDisabledTap : onTap, child: Opacity( opacity: isDisabled ? 0.5 : 1.0, child: Container( diff --git a/mobile-app/lib/features/main/screens/quests/quests_screen.dart b/mobile-app/lib/features/main/screens/quests/quests_screen.dart index 288216c8..5ad7586e 100644 --- a/mobile-app/lib/features/main/screens/quests/quests_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/quests_screen.dart @@ -38,12 +38,18 @@ class _QuestsScreenState extends ConsumerState { @override Widget build(BuildContext context) { final associationsAsync = ref.watch(accountAssociationsProvider); - final hasCompletedSetup = associationsAsync.maybeWhen( - data: (associations) => associations.ethAddress != null || associations.xUsername != null, + + final hasEthAddress = associationsAsync.maybeWhen( + data: (associations) => associations.ethAddress != null, + orElse: () => false, + ); + + final hasXUsername = associationsAsync.maybeWhen( + data: (associations) => associations.xUsername != null, orElse: () => false, ); - final showTooltip = !hasCompletedSetup && _showSetupTooltip; + final showTooltip = !hasEthAddress && _showSetupTooltip; return ScaffoldBase.refreshable( backgroundColor: const Color(0xFF0C1014), @@ -58,9 +64,9 @@ class _QuestsScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 24), - _buildHeader(context, hasCompletedSetup, showTooltip), + _buildHeader(context, hasEthAddress, showTooltip), const SizedBox(height: 48), - _buildQuestCards(context, hasCompletedSetup), + _buildQuestCards(context, hasEthAddress, hasXUsername), ], ), ), @@ -68,7 +74,7 @@ class _QuestsScreenState extends ConsumerState { ); } - Widget _buildHeader(BuildContext context, bool hasCompletedSetup, bool showTooltip) { + Widget _buildHeader(BuildContext context, bool hasEthAddress, bool showTooltip) { return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -89,7 +95,7 @@ class _QuestsScreenState extends ConsumerState { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), child: Text( - hasCompletedSetup ? 'Setup' : 'Complete Setup', + hasEthAddress ? 'Setup' : 'Complete Setup', style: context.themeText.smallParagraph, ), ), @@ -141,11 +147,12 @@ class _QuestsScreenState extends ConsumerState { ); } - Widget _buildQuestCards(BuildContext context, bool hasCompletedSetup) { + Widget _buildQuestCards(BuildContext context, bool hasEthAddress, bool hasXUsername) { return Column( children: [ QuestCard.referFriends( - isDisabled: !hasCompletedSetup, + isDisabled: !hasEthAddress, + onDisabledTap: () => showCompleteSetupActionSheet(context), onTap: () { Navigator.push( context, @@ -155,7 +162,8 @@ class _QuestsScreenState extends ConsumerState { ), const SizedBox(height: 40), QuestCard.kingOfTheShill( - isDisabled: !hasCompletedSetup, + isDisabled: !hasXUsername, + onDisabledTap: () => showCompleteSetupActionSheet(context), onTap: () { Navigator.push( context, From b873dec9f334b84c6efda188a92896202d8971b1 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 21:08:35 +0800 Subject: [PATCH 14/23] format --- .../complete_setup_action_sheet.dart | 109 ++++-------------- .../lib/features/components/quest_card.dart | 34 ++---- .../quests/king_of_the_shill_screen.dart | 51 ++------ .../main/screens/quests/quests_screen.dart | 26 +---- .../quests/referrals_quest_screen.dart | 50 +++----- 5 files changed, 62 insertions(+), 208 deletions(-) diff --git a/mobile-app/lib/features/components/complete_setup_action_sheet.dart b/mobile-app/lib/features/components/complete_setup_action_sheet.dart index f179c952..a7571dee 100644 --- a/mobile-app/lib/features/components/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/components/complete_setup_action_sheet.dart @@ -67,10 +67,7 @@ class _CompleteSetupActionSheetState extends ConsumerState position.position > 0, - orElse: () => false, - ); + final isAlreadyOptedIn = optInStatus.maybeWhen(data: (position) => position.position > 0, orElse: () => false); if (!isAlreadyOptedIn) { await _taskmasterService.optInRewardProgram(); @@ -89,18 +86,12 @@ class _CompleteSetupActionSheetState extends ConsumerState Navigator.pop(context), child: Icon(Icons.close, color: context.themeColors.textPrimary, size: 24), ), @@ -208,22 +185,14 @@ class _CompleteSetupActionSheetState extends ConsumerState Function() onConfirm, - }) { + void _showUnlinkConfirmation({required String title, required Future Function() onConfirm}) { showModalBottomSheet( context: context, isDismissible: true, backgroundColor: const Color(0xFF0C1014), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - builder: (sheetContext) => _UnlinkConfirmationSheet( - title: title, - onConfirm: onConfirm, - dangerColor: context.themeColors.buttonDanger, - ), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), + builder: (sheetContext) => + _UnlinkConfirmationSheet(title: title, onConfirm: onConfirm, dangerColor: context.themeColors.buttonDanger), ); } @@ -260,10 +229,7 @@ class _CompleteSetupActionSheetState extends ConsumerState _showUnlinkConfirmation( - title: 'ETH Address', - onConfirm: _handleUnlinkEth, - ), + onUnlink: () => _showUnlinkConfirmation(title: 'ETH Address', onConfirm: _handleUnlinkEth), ), const SizedBox(height: 32), _buildInputSection( @@ -271,10 +237,7 @@ class _CompleteSetupActionSheetState extends ConsumerState _showUnlinkConfirmation( - title: 'X Account', - onConfirm: _handleUnlinkX, - ), + onUnlink: () => _showUnlinkConfirmation(title: 'X Account', onConfirm: _handleUnlinkX), ), const SizedBox(height: 32), _buildBioSection(), @@ -315,10 +278,7 @@ class _CompleteSetupActionSheetState extends ConsumerState Function() onConfirm; final Color dangerColor; - const _UnlinkConfirmationSheet({ - required this.title, - required this.onConfirm, - required this.dangerColor, - }); + const _UnlinkConfirmationSheet({required this.title, required this.onConfirm, required this.dangerColor}); @override State<_UnlinkConfirmationSheet> createState() => _UnlinkConfirmationSheetState(); @@ -523,9 +466,9 @@ class _UnlinkConfirmationSheetState extends State<_UnlinkConfirmationSheet> { } catch (e) { if (mounted) { setState(() => _isUnlinking = false); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error: $e'), backgroundColor: widget.dangerColor), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error: $e'), backgroundColor: widget.dangerColor)); } } } @@ -550,11 +493,7 @@ class _UnlinkConfirmationSheetState extends State<_UnlinkConfirmationSheet> { const SizedBox(height: 8), Text( 'Are you sure you want to unlink your ${widget.title}?', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.5), - fontSize: 14, - fontFamily: 'Inter', - ), + style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 14, fontFamily: 'Inter'), textAlign: TextAlign.center, ), const SizedBox(height: 24), diff --git a/mobile-app/lib/features/components/quest_card.dart b/mobile-app/lib/features/components/quest_card.dart index ce0d1e61..6d0fb83d 100644 --- a/mobile-app/lib/features/components/quest_card.dart +++ b/mobile-app/lib/features/components/quest_card.dart @@ -37,23 +37,14 @@ class QuestCard extends StatelessWidget { this.bgRectHeight = 531, }); - factory QuestCard.referFriends({ - required VoidCallback? onTap, - VoidCallback? onDisabledTap, - bool isDisabled = false, - }) { + factory QuestCard.referFriends({required VoidCallback? onTap, VoidCallback? onDisabledTap, bool isDisabled = false}) { return QuestCard( title: 'REFER FRIENDS', description: 'Earn for every friend who joins\nQuantus using your link.', actionLabel: 'View Referrals', onTap: onTap, onDisabledTap: onDisabledTap, - gradientColors: const [ - Color(0xFF0C1014), - Color(0xFF0000FF), - Color(0xFFED4CCE), - Color(0xFFFFE91F), - ], + gradientColors: const [Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)], gradientStops: const [0.45, 0.53, 0.62, 0.65], gradientCenter: const Alignment(0.77, 0.22), borderColor: const Color(0x7F6734BA), @@ -70,17 +61,13 @@ class QuestCard extends StatelessWidget { }) { return QuestCard( title: 'KING OF THE SHILL', - description: isDisabled - ? 'Link your X account to participate!' + description: isDisabled + ? 'Link your X account to participate!' : 'Participate in social raids and earn rewards for verified posts.', actionLabel: 'View Raids', onTap: onTap, onDisabledTap: onDisabledTap, - gradientColors: const [ - Color(0xFF0C1014), - Color(0xFFED4CCE), - Color(0xFFFFE91F), - ], + gradientColors: const [Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F)], gradientStops: const [0.45, 0.54, 0.57], gradientCenter: const Alignment(0.77, 0.22), borderColor: const Color(0x7F773F56), @@ -159,16 +146,9 @@ class QuestCard extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - actionLabel, - style: context.themeText.smallTitle, - ), + Text(actionLabel, style: context.themeText.smallTitle), const SizedBox(width: 8), - Icon( - Icons.arrow_forward, - size: 18, - color: context.themeColors.textPrimary, - ), + Icon(Icons.arrow_forward, size: 18, color: context.themeColors.textPrimary), ], ), ], diff --git a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart index b25d8528..95f59f1b 100644 --- a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart @@ -269,13 +269,7 @@ class _KingOfTheShillScreenState extends ConsumerState { side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), borderRadius: BorderRadius.circular(8), ), - shadows: const [ - BoxShadow( - color: Color(0x3F000000), - blurRadius: 4, - offset: Offset(4, 4), - ) - ], + shadows: const [BoxShadow(color: Color(0x3F000000), blurRadius: 4, offset: Offset(4, 4))], ), child: Column( children: [ @@ -304,12 +298,7 @@ class _KingOfTheShillScreenState extends ConsumerState { ), Text( value, - style: TextStyle( - color: valueColor, - fontSize: 14, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), + style: TextStyle(color: valueColor, fontSize: 14, fontFamily: 'Fira Code', fontWeight: FontWeight.w400), ), ], ); @@ -320,12 +309,7 @@ class _KingOfTheShillScreenState extends ConsumerState { children: [ const Text( 'Past Submissions', - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), + style: TextStyle(color: Colors.white, fontSize: 14, fontFamily: 'Fira Code', fontWeight: FontWeight.w500), ), const SizedBox(height: 12), Container( @@ -350,25 +334,15 @@ class _KingOfTheShillScreenState extends ConsumerState { ), error: (_, __) => Text( 'Failed to load submissions', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.5), - fontSize: 12, - fontFamily: 'Inter', - ), + style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12, fontFamily: 'Inter'), ), data: (state) { if (state is RaiderSubmissionsOk && state.submissions.isNotEmpty) { - return Column( - children: state.submissions.take(4).map((url) => _buildSubmissionRow(url)).toList(), - ); + return Column(children: state.submissions.take(4).map((url) => _buildSubmissionRow(url)).toList()); } return Text( 'No submissions yet', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.5), - fontSize: 12, - fontFamily: 'Inter', - ), + style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12, fontFamily: 'Inter'), ); }, ), @@ -401,11 +375,7 @@ class _KingOfTheShillScreenState extends ConsumerState { ), Text( _formatTimeAgo(url), - style: TextStyle( - color: Colors.white.withValues(alpha: 0.5), - fontSize: 10, - fontFamily: 'Fira Code', - ), + style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 10, fontFamily: 'Fira Code'), ), ], ), @@ -417,12 +387,7 @@ class _KingOfTheShillScreenState extends ConsumerState { children: [ const Text( 'Submit Your Reply', - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), + style: TextStyle(color: Colors.white, fontSize: 14, fontFamily: 'Fira Code', fontWeight: FontWeight.w500), ), const SizedBox(height: 12), GestureDetector( diff --git a/mobile-app/lib/features/main/screens/quests/quests_screen.dart b/mobile-app/lib/features/main/screens/quests/quests_screen.dart index 5ad7586e..f13de530 100644 --- a/mobile-app/lib/features/main/screens/quests/quests_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/quests_screen.dart @@ -81,11 +81,7 @@ class _QuestsScreenState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Image.asset( - 'assets/quests/quests_top_logo.png', - height: 24, - fit: BoxFit.contain, - ), + Image.asset('assets/quests/quests_top_logo.png', height: 24, fit: BoxFit.contain), GestureDetector( onTap: () => showCompleteSetupActionSheet(context), child: Container( @@ -94,18 +90,12 @@ class _QuestsScreenState extends ConsumerState { color: context.themeColors.settingCard, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), - child: Text( - hasEthAddress ? 'Setup' : 'Complete Setup', - style: context.themeText.smallParagraph, - ), + child: Text(hasEthAddress ? 'Setup' : 'Complete Setup', style: context.themeText.smallParagraph), ), ), ], ), - if (showTooltip) ...[ - const SizedBox(height: 8), - _buildSetupTooltip(), - ], + if (showTooltip) ...[const SizedBox(height: 8), _buildSetupTooltip()], ], ); } @@ -154,10 +144,7 @@ class _QuestsScreenState extends ConsumerState { isDisabled: !hasEthAddress, onDisabledTap: () => showCompleteSetupActionSheet(context), onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const ReferralsQuestScreen()), - ); + Navigator.push(context, MaterialPageRoute(builder: (context) => const ReferralsQuestScreen())); }, ), const SizedBox(height: 40), @@ -165,10 +152,7 @@ class _QuestsScreenState extends ConsumerState { isDisabled: !hasXUsername, onDisabledTap: () => showCompleteSetupActionSheet(context), onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const KingOfTheShillScreen()), - ); + Navigator.push(context, MaterialPageRoute(builder: (context) => const KingOfTheShillScreen())); }, ), ], diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 8c07385f..05dbe3b7 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -93,7 +93,10 @@ class _ReferralsQuestScreenState extends ConsumerState { const SizedBox(height: 32), _buildStep('Step 1', 'Invite users using your unique code'), const SizedBox(height: 16), - _buildStep('Step 2', 'They must create a Quantus Wallet. Referrals without wallet creation won\'t count'), + _buildStep( + 'Step 2', + 'They must create a Quantus Wallet. Referrals without wallet creation won\'t count', + ), const SizedBox(height: 16), _buildStep('Step 3', 'Climb the leaderboard. Rank updates automatically'), ], @@ -188,13 +191,7 @@ class _ReferralsQuestScreenState extends ConsumerState { gradient: const RadialGradient( center: Alignment(0.77, -0.77), radius: 1.8, - colors: [ - Color(0xFF0C1014), - Color(0xFF0000FF), - Color(0xFFED4CCE), - Color(0xFFFFE91F), - - ], + colors: [Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)], stops: [0.45, 0.53, 0.62, 0.65], ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), @@ -237,8 +234,14 @@ class _ReferralsQuestScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.center, children: [ _buildAvatar(const [Color(0xFF0000FF), Color(0xFF0C1014)]), - Transform.translate(offset: const Offset(-16, 0), child: _buildAvatar(const [Color(0xFF8B0000), Color(0xFFED4CCE)])), - Transform.translate(offset: const Offset(-32, 0), child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)])), + Transform.translate( + offset: const Offset(-16, 0), + child: _buildAvatar(const [Color(0xFF8B0000), Color(0xFFED4CCE)]), + ), + Transform.translate( + offset: const Offset(-32, 0), + child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)]), + ), ], ), const SizedBox(height: 40), @@ -252,11 +255,7 @@ class _ReferralsQuestScreenState extends ConsumerState { borderRadius: BorderRadius.circular(8), ), shadows: const [ - BoxShadow( - color: Color(0x3F000000), - blurRadius: 4, - offset: Offset(4, 4), - ) + BoxShadow(color: Color(0x3F000000), blurRadius: 4, offset: Offset(4, 4)), ], ), child: Column( @@ -287,7 +286,7 @@ class _ReferralsQuestScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(14), decoration: ShapeDecoration( - color: Colors.white.useOpacity(0.3), + color: Colors.white.useOpacity(0.3), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: Row( @@ -355,17 +354,9 @@ class _ReferralsQuestScreenState extends ConsumerState { width: 64, height: 64, decoration: ShapeDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: colors, - ), + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: colors), shape: const OvalBorder( - side: BorderSide( - width: 2.67, - strokeAlign: BorderSide.strokeAlignOutside, - color: Color(0xFF0C1014), - ), + side: BorderSide(width: 2.67, strokeAlign: BorderSide.strokeAlignOutside, color: Color(0xFF0C1014)), ), ), ); @@ -387,12 +378,7 @@ class _ReferralsQuestScreenState extends ConsumerState { Text( value, textAlign: TextAlign.center, - style: TextStyle( - color: valueColor, - fontSize: 14, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), + style: TextStyle(color: valueColor, fontSize: 14, fontFamily: 'Fira Code', fontWeight: FontWeight.w400), ), ], ); From cc83f43c46c9b1717c6828546051e46eacf24c50 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 21:16:03 +0800 Subject: [PATCH 15/23] linter fixes --- .../components/complete_setup_action_sheet.dart | 12 ++++++------ .../screens/quests/king_of_the_shill_screen.dart | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mobile-app/lib/features/components/complete_setup_action_sheet.dart b/mobile-app/lib/features/components/complete_setup_action_sheet.dart index a7571dee..6268ad90 100644 --- a/mobile-app/lib/features/components/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/components/complete_setup_action_sheet.dart @@ -252,7 +252,7 @@ class _CompleteSetupActionSheetState extends ConsumerState { child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), ), ), - error: (_, __) => _buildStatsBox(0, 0), + error: (_, _) => _buildStatsBox(0, 0), data: (state) { if (state is RaiderSubmissionsOk) { return _buildStatsBox(state.submissions.length, 0); @@ -264,7 +264,7 @@ class _KingOfTheShillScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: const Color(0xFF0C1014).withOpacity(0.4), + color: const Color(0xFF0C1014).useOpacity(0.4), shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), borderRadius: BorderRadius.circular(8), @@ -332,7 +332,7 @@ class _KingOfTheShillScreenState extends ConsumerState { ), ), ), - error: (_, __) => Text( + error: (_, _) => Text( 'Failed to load submissions', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12, fontFamily: 'Inter'), ), From 5342f310d88f671ac5da7e2fe811907ac10d9739 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 23:04:48 +0800 Subject: [PATCH 16/23] lightning icon for quests added --- mobile-app/assets/navbar/Lightning.svg | 3 +++ mobile-app/assets/navbar/lightning_icon_off.svg | 5 +++++ mobile-app/assets/navbar/lightning_icon_on.svg | 4 ++++ mobile-app/lib/features/main/screens/navbar.dart | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 mobile-app/assets/navbar/Lightning.svg create mode 100644 mobile-app/assets/navbar/lightning_icon_off.svg create mode 100644 mobile-app/assets/navbar/lightning_icon_on.svg diff --git a/mobile-app/assets/navbar/Lightning.svg b/mobile-app/assets/navbar/Lightning.svg new file mode 100644 index 00000000..174b263c --- /dev/null +++ b/mobile-app/assets/navbar/Lightning.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile-app/assets/navbar/lightning_icon_off.svg b/mobile-app/assets/navbar/lightning_icon_off.svg new file mode 100644 index 00000000..8d9bf394 --- /dev/null +++ b/mobile-app/assets/navbar/lightning_icon_off.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/mobile-app/assets/navbar/lightning_icon_on.svg b/mobile-app/assets/navbar/lightning_icon_on.svg new file mode 100644 index 00000000..6eb149cf --- /dev/null +++ b/mobile-app/assets/navbar/lightning_icon_on.svg @@ -0,0 +1,4 @@ + + + + diff --git a/mobile-app/lib/features/main/screens/navbar.dart b/mobile-app/lib/features/main/screens/navbar.dart index 3cbb23bb..1dc0ce2e 100644 --- a/mobile-app/lib/features/main/screens/navbar.dart +++ b/mobile-app/lib/features/main/screens/navbar.dart @@ -107,7 +107,7 @@ class _NavbarState extends ConsumerState { NavItem('assets/navbar/history_icon_off.svg', 'assets/navbar/history_icon_on.svg', 'History'), NavItem('assets/navbar/floating_button.svg', 'assets/navbar/floating_button.svg', 'Send'), NavItem('assets/navbar/settings_icon_off.svg', 'assets/navbar/settings_icon_on.svg', 'Settings'), - NavItem('assets/navbar/qcat_navbar_icon.png', 'assets/navbar/qcat_navbar_icon.png', 'Quests'), + NavItem('assets/navbar/lightning_icon_off.svg', 'assets/navbar/lightning_icon_on.svg', 'Quests'), ]; @override From 0dbfec002231b8f04c605bc3faab3e25d45c49a6 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 30 Jan 2026 23:17:28 +0800 Subject: [PATCH 17/23] fixing gradients --- .../lib/features/components/quest_card.dart | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/mobile-app/lib/features/components/quest_card.dart b/mobile-app/lib/features/components/quest_card.dart index 6d0fb83d..7df6e87f 100644 --- a/mobile-app/lib/features/components/quest_card.dart +++ b/mobile-app/lib/features/components/quest_card.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; @@ -11,6 +13,7 @@ class QuestCard extends StatelessWidget { final List gradientColors; final List? gradientStops; final AlignmentGeometry gradientCenter; + final double gradientRadius; final Color borderColor; final bool isDisabled; @@ -29,6 +32,7 @@ class QuestCard extends StatelessWidget { required this.gradientColors, this.gradientStops, required this.gradientCenter, + required this.gradientRadius, required this.borderColor, this.isDisabled = false, this.bgRectLeft = -127, @@ -45,8 +49,9 @@ class QuestCard extends StatelessWidget { onTap: onTap, onDisabledTap: onDisabledTap, gradientColors: const [Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)], - gradientStops: const [0.45, 0.53, 0.62, 0.65], - gradientCenter: const Alignment(0.77, 0.22), + gradientStops: const [0.55, 0.62, 0.68, 0.72], + gradientCenter: const Alignment(0.6, -0.7), + gradientRadius: 1.29, borderColor: const Color(0x7F6734BA), isDisabled: isDisabled, bgRectLeft: -127, @@ -68,8 +73,9 @@ class QuestCard extends StatelessWidget { onTap: onTap, onDisabledTap: onDisabledTap, gradientColors: const [Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F)], - gradientStops: const [0.45, 0.54, 0.57], - gradientCenter: const Alignment(0.77, 0.22), + gradientStops: const [0.55, 0.64, 0.68], + gradientCenter: const Alignment(0.7, -0.7), + gradientRadius: 1.3, borderColor: const Color(0x7F773F56), isDisabled: isDisabled, bgRectLeft: -127, @@ -99,17 +105,20 @@ class QuestCard extends StatelessWidget { Positioned( left: bgRectLeft, top: bgRectTop, - child: Container( - width: bgRectWidth, - height: bgRectHeight, - decoration: ShapeDecoration( - gradient: RadialGradient( - center: gradientCenter, - radius: 1.39, - colors: gradientColors, - stops: gradientStops, + child: ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 52, sigmaY: 52), + child: Container( + width: bgRectWidth, + height: bgRectHeight, + decoration: ShapeDecoration( + gradient: RadialGradient( + center: gradientCenter, + radius: gradientRadius, + colors: gradientColors, + stops: gradientStops, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), ), ), From 17641ca94289d28882fcf58c9dd77f253569cd58 Mon Sep 17 00:00:00 2001 From: NIK Date: Mon, 2 Feb 2026 10:21:46 +0800 Subject: [PATCH 18/23] moved quests files to quests, changed colors with app colors --- .../quests}/complete_setup_action_sheet.dart | 8 +++--- .../quests/king_of_the_shill_screen.dart | 14 +++++----- .../screens/quests}/quest_card.dart | 7 ++--- .../main/screens/quests/quest_constants.dart | 4 +++ .../screens/quests}/quests_promo_video.dart | 0 .../main/screens/quests/quests_screen.dart | 8 +++--- .../quests}/raid_submission_action_sheet.dart | 0 .../quests/referrals_quest_screen.dart | 27 ++++++++++++------- 8 files changed, 41 insertions(+), 27 deletions(-) rename mobile-app/lib/features/{components => main/screens/quests}/complete_setup_action_sheet.dart (99%) rename mobile-app/lib/features/{components => main/screens/quests}/quest_card.dart (95%) create mode 100644 mobile-app/lib/features/main/screens/quests/quest_constants.dart rename mobile-app/lib/features/{components => main/screens/quests}/quests_promo_video.dart (100%) rename mobile-app/lib/features/{components => main/screens/quests}/raid_submission_action_sheet.dart (100%) diff --git a/mobile-app/lib/features/components/complete_setup_action_sheet.dart b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart similarity index 99% rename from mobile-app/lib/features/components/complete_setup_action_sheet.dart rename to mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart index 6268ad90..d2aa104d 100644 --- a/mobile-app/lib/features/components/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart @@ -111,9 +111,9 @@ class _CompleteSetupActionSheetState extends ConsumerState _UnlinkConfirmationSheet(title: title, onConfirm: onConfirm, dangerColor: context.themeColors.buttonDanger), diff --git a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart index d05c8751..7d784f62 100644 --- a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart @@ -4,9 +4,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/inner_shadow_container.dart'; -import 'package:resonance_network_wallet/features/components/raid_submission_action_sheet.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; +import 'package:resonance_network_wallet/features/main/screens/quests/quest_constants.dart'; +import 'package:resonance_network_wallet/features/main/screens/quests/raid_submission_action_sheet.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/providers/raider_quest_providers.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -33,7 +35,7 @@ class _KingOfTheShillScreenState extends ConsumerState { width: 280, padding: const EdgeInsets.all(24), decoration: ShapeDecoration( - color: const Color(0xFF0C1014), + color: context.themeColors.background2, shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x66F4F6F9)), borderRadius: BorderRadius.circular(20), @@ -142,7 +144,7 @@ class _KingOfTheShillScreenState extends ConsumerState { width: double.infinity, clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: const Color(0xFF0C1014), + color: context.themeColors.background2, shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x7F6734BA)), borderRadius: BorderRadius.circular(4), @@ -159,7 +161,7 @@ class _KingOfTheShillScreenState extends ConsumerState { gradient: RadialGradient( center: Alignment(0.77, -0.56), radius: 2.8, - colors: [Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F)], + colors: questKingOfTheShillGradient, stops: [0.45, 0.54, 0.57], ), ), @@ -264,7 +266,7 @@ class _KingOfTheShillScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: const Color(0xFF0C1014).useOpacity(0.4), + color: context.themeColors.background2.useOpacity(0.4), shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), borderRadius: BorderRadius.circular(8), @@ -277,7 +279,7 @@ class _KingOfTheShillScreenState extends ConsumerState { const SizedBox(height: 16), _buildStatRow('Verified posts', verifiedPosts.toString().padLeft(2, '0'), Colors.white), const SizedBox(height: 16), - _buildStatRow('Rank', '#-', const Color(0xFFED4CCE)), + _buildStatRow('Rank', '#-', context.themeColors.pink), ], ), ); diff --git a/mobile-app/lib/features/components/quest_card.dart b/mobile-app/lib/features/main/screens/quests/quest_card.dart similarity index 95% rename from mobile-app/lib/features/components/quest_card.dart rename to mobile-app/lib/features/main/screens/quests/quest_card.dart index 7df6e87f..8769bf9d 100644 --- a/mobile-app/lib/features/components/quest_card.dart +++ b/mobile-app/lib/features/main/screens/quests/quest_card.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/features/main/screens/quests/quest_constants.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; @@ -48,7 +49,7 @@ class QuestCard extends StatelessWidget { actionLabel: 'View Referrals', onTap: onTap, onDisabledTap: onDisabledTap, - gradientColors: const [Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)], + gradientColors: questReferFriendsGradient, gradientStops: const [0.55, 0.62, 0.68, 0.72], gradientCenter: const Alignment(0.6, -0.7), gradientRadius: 1.29, @@ -72,7 +73,7 @@ class QuestCard extends StatelessWidget { actionLabel: 'View Raids', onTap: onTap, onDisabledTap: onDisabledTap, - gradientColors: const [Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F)], + gradientColors: questKingOfTheShillGradient, gradientStops: const [0.55, 0.64, 0.68], gradientCenter: const Alignment(0.7, -0.7), gradientRadius: 1.3, @@ -94,7 +95,7 @@ class QuestCard extends StatelessWidget { height: 246, clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: const Color(0xFF0C1014), + color: context.themeColors.background2, shape: RoundedRectangleBorder( side: BorderSide(width: 1, color: borderColor), borderRadius: BorderRadius.circular(4), diff --git a/mobile-app/lib/features/main/screens/quests/quest_constants.dart b/mobile-app/lib/features/main/screens/quests/quest_constants.dart new file mode 100644 index 00000000..c883c132 --- /dev/null +++ b/mobile-app/lib/features/main/screens/quests/quest_constants.dart @@ -0,0 +1,4 @@ +import 'package:flutter/material.dart'; + +const questReferFriendsGradient = [Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)]; +const questKingOfTheShillGradient = [Color(0xFF0C1014), Color(0xFFED4CCE), Color(0xFFFFE91F)]; diff --git a/mobile-app/lib/features/components/quests_promo_video.dart b/mobile-app/lib/features/main/screens/quests/quests_promo_video.dart similarity index 100% rename from mobile-app/lib/features/components/quests_promo_video.dart rename to mobile-app/lib/features/main/screens/quests/quests_promo_video.dart diff --git a/mobile-app/lib/features/main/screens/quests/quests_screen.dart b/mobile-app/lib/features/main/screens/quests/quests_screen.dart index f13de530..4554eaf2 100644 --- a/mobile-app/lib/features/main/screens/quests/quests_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/quests_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:resonance_network_wallet/features/components/complete_setup_action_sheet.dart'; -import 'package:resonance_network_wallet/features/components/quest_card.dart'; +import 'package:resonance_network_wallet/features/main/screens/quests/complete_setup_action_sheet.dart'; +import 'package:resonance_network_wallet/features/main/screens/quests/quest_card.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/king_of_the_shill_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/referrals_quest_screen.dart'; @@ -52,7 +52,7 @@ class _QuestsScreenState extends ConsumerState { final showTooltip = !hasEthAddress && _showSetupTooltip; return ScaffoldBase.refreshable( - backgroundColor: const Color(0xFF0C1014), + backgroundColor: context.themeColors.background2, onRefresh: () async { refreshStatsData(); refreshAssociationsData(); @@ -104,7 +104,7 @@ class _QuestsScreenState extends ConsumerState { return Container( padding: const EdgeInsets.all(14), decoration: ShapeDecoration( - color: const Color(0xFF0C1014), + color: context.themeColors.background2, shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x66F4F6F9)), borderRadius: BorderRadius.circular(4), diff --git a/mobile-app/lib/features/components/raid_submission_action_sheet.dart b/mobile-app/lib/features/main/screens/quests/raid_submission_action_sheet.dart similarity index 100% rename from mobile-app/lib/features/components/raid_submission_action_sheet.dart rename to mobile-app/lib/features/main/screens/quests/raid_submission_action_sheet.dart diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 05dbe3b7..6eb5a635 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -5,6 +5,8 @@ import 'package:resonance_network_wallet/features/components/copy_icon.dart'; import 'package:resonance_network_wallet/features/components/inner_shadow_container.dart'; import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; +import 'package:resonance_network_wallet/features/main/screens/quests/quest_constants.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_stats_providers.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; @@ -62,7 +64,7 @@ class _ReferralsQuestScreenState extends ConsumerState { width: 280, padding: const EdgeInsets.all(24), decoration: ShapeDecoration( - color: const Color(0xFF0C1014), + color: context.themeColors.background2, shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x66F4F6F9)), borderRadius: BorderRadius.circular(20), @@ -171,7 +173,7 @@ class _ReferralsQuestScreenState extends ConsumerState { width: double.infinity, clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: const Color(0xFF0C1014), + color: context.themeColors.background2, shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x7F6734BA)), borderRadius: BorderRadius.circular(4), @@ -191,7 +193,7 @@ class _ReferralsQuestScreenState extends ConsumerState { gradient: const RadialGradient( center: Alignment(0.77, -0.77), radius: 1.8, - colors: [Color(0xFF0C1014), Color(0xFF0000FF), Color(0xFFED4CCE), Color(0xFFFFE91F)], + colors: questReferFriendsGradient, stops: [0.45, 0.53, 0.62, 0.65], ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), @@ -233,14 +235,15 @@ class _ReferralsQuestScreenState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildAvatar(const [Color(0xFF0000FF), Color(0xFF0C1014)]), + // these colors aren't really great anyway..should be spheres for accounts.. + _buildAvatar([const Color(0xFF0000FF), context.themeColors.background2]), Transform.translate( offset: const Offset(-16, 0), - child: _buildAvatar(const [Color(0xFF8B0000), Color(0xFFED4CCE)]), + child: _buildAvatar([const Color(0xFF8B0000), context.themeColors.pink]), ), Transform.translate( offset: const Offset(-32, 0), - child: _buildAvatar(const [Color(0xFFFFD700), Color(0xFFFFE91F)]), + child: _buildAvatar([const Color(0xFFFFD700), context.themeColors.yellow]), ), ], ), @@ -249,7 +252,7 @@ class _ReferralsQuestScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(16), decoration: ShapeDecoration( - color: const Color(0xFF0C1014).useOpacity(0.4), + color: context.themeColors.background2.useOpacity(0.4), shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0x33F4F6F9)), borderRadius: BorderRadius.circular(8), @@ -262,7 +265,7 @@ class _ReferralsQuestScreenState extends ConsumerState { children: [ _buildStatRow('Referrals', '$referralsCount', Colors.white), const SizedBox(height: 16), - _buildStatRow('Rank', '#-', const Color(0xFFED4CCE)), + _buildStatRow('Rank', '#-', context.themeColors.pink), ], ), ), @@ -355,8 +358,12 @@ class _ReferralsQuestScreenState extends ConsumerState { height: 64, decoration: ShapeDecoration( gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: colors), - shape: const OvalBorder( - side: BorderSide(width: 2.67, strokeAlign: BorderSide.strokeAlignOutside, color: Color(0xFF0C1014)), + shape: OvalBorder( + side: BorderSide( + width: 2.67, + strokeAlign: BorderSide.strokeAlignOutside, + color: context.themeColors.background2, + ), ), ), ); From 6d82da02246308b293ccbc5a13660fefbf0bcb50 Mon Sep 17 00:00:00 2001 From: NIK Date: Mon, 2 Feb 2026 11:08:20 +0800 Subject: [PATCH 19/23] use text theme --- .../setup/rewards_address_setup_screen.dart | 8 ------- .../quests/complete_setup_action_sheet.dart | 11 ++-------- .../quests/king_of_the_shill_screen.dart | 21 +++---------------- .../main/screens/quests/quest_card.dart | 10 +-------- .../quests/referrals_quest_screen.dart | 21 +++---------------- 5 files changed, 9 insertions(+), 62 deletions(-) diff --git a/miner-app/lib/features/setup/rewards_address_setup_screen.dart b/miner-app/lib/features/setup/rewards_address_setup_screen.dart index 1bbcc6ad..64ba8179 100644 --- a/miner-app/lib/features/setup/rewards_address_setup_screen.dart +++ b/miner-app/lib/features/setup/rewards_address_setup_screen.dart @@ -131,14 +131,6 @@ class _RewardsAddressSetupScreenState extends State { Container( width: 40, // spacer for alignment ), - // const Text( - // 'Scan QR Code', - // style: TextStyle( - // color: Colors.white, - // fontSize: 20, - // fontWeight: FontWeight.bold, - // ), - // ), Positioned( right: 0, top: 0, diff --git a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart index d2aa104d..88bd03a0 100644 --- a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/opt_in_position_providers.dart'; import 'package:resonance_network_wallet/shared/extensions/snackbar_extensions.dart'; @@ -149,15 +150,7 @@ class _CompleteSetupActionSheetState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - 'HOW IT WORKS', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + Text('HOW IT WORKS', style: context.themeText.paragraph), GestureDetector( onTap: () => Navigator.of(context).pop(), child: const Icon(Icons.close, color: Colors.white, size: 24), @@ -176,15 +169,7 @@ class _KingOfTheShillScreenState extends ConsumerState { Center( child: Column( children: [ - const Text( - 'KING OF THE SHILL', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + Text('KING OF THE SHILL', style: context.themeText.paragraph), const SizedBox(height: 8), Text( 'Join social raids. Get rewarded for\nverified posts.', diff --git a/mobile-app/lib/features/main/screens/quests/quest_card.dart b/mobile-app/lib/features/main/screens/quests/quest_card.dart index 8769bf9d..a47db688 100644 --- a/mobile-app/lib/features/main/screens/quests/quest_card.dart +++ b/mobile-app/lib/features/main/screens/quests/quest_card.dart @@ -132,15 +132,7 @@ class QuestCard extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + Text(title, style: context.themeText.paragraph), const SizedBox(height: 8), Text( description, diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 6eb5a635..b35bef0f 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -7,6 +7,7 @@ import 'package:resonance_network_wallet/features/components/scaffold_base.dart' import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; import 'package:resonance_network_wallet/features/main/screens/quests/quest_constants.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_stats_providers.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; @@ -77,15 +78,7 @@ class _ReferralsQuestScreenState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - 'HOW IT WORKS', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + Text('HOW IT WORKS', style: context.themeText.paragraph), GestureDetector( onTap: () => Navigator.of(context).pop(), child: const Icon(Icons.close, color: Colors.white, size: 24), @@ -208,15 +201,7 @@ class _ReferralsQuestScreenState extends ConsumerState { Center( child: Column( children: [ - const Text( - 'REFER FRIENDS', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + Text('REFER FRIENDS', style: context.themeText.paragraph), const SizedBox(height: 8), Text( 'Invite friends. Earn rewards. \nClimb the leaderboard.', From 890a5384d76715ac032ecb7a7ca44e4985df940f Mon Sep 17 00:00:00 2001 From: NIK Date: Mon, 2 Feb 2026 11:25:33 +0800 Subject: [PATCH 20/23] text themes --- .../quests/complete_setup_action_sheet.dart | 43 +++---------------- .../quests/king_of_the_shill_screen.dart | 9 +--- .../quests/referrals_quest_screen.dart | 9 +--- 3 files changed, 11 insertions(+), 50 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart index 88bd03a0..9798a485 100644 --- a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart @@ -254,15 +254,7 @@ class _CompleteSetupActionSheetState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( - 'Unlink ${widget.title}?', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), - ), + Text('Unlink ${widget.title}?', style: context.themeText.smallTitle?.copyWith(color: Colors.white)), const SizedBox(height: 8), Text( 'Are you sure you want to unlink your ${widget.title}?', diff --git a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart index 7c0339ed..949db9e9 100644 --- a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart @@ -228,14 +228,9 @@ class _KingOfTheShillScreenState extends ConsumerState { ), ), alignment: Alignment.center, - child: const Text( + child: Text( 'Add Raid Submission', - style: TextStyle( - color: Color(0xFFF4F6F9), - fontSize: 18, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), + style: context.themeText.smallTitle?.copyWith(color: const Color(0xFFF4F6F9)), ), ), ), diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index b35bef0f..420ede9a 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -319,14 +319,9 @@ class _ReferralsQuestScreenState extends ConsumerState { ), ), alignment: Alignment.center, - child: const Text( + child: Text( 'Share Link', - style: TextStyle( - color: Color(0xFFF4F6F9), - fontSize: 18, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w500, - ), + style: context.themeText.smallTitle?.copyWith(color: const Color(0xFFF4F6F9)), ), ), ), From dac5c206709fa204cfe3b5cf9ed14bbcacb0152d Mon Sep 17 00:00:00 2001 From: NIK Date: Mon, 2 Feb 2026 11:26:51 +0800 Subject: [PATCH 21/23] more colors --- .../main/screens/quests/complete_setup_action_sheet.dart | 9 ++++++--- .../main/screens/quests/king_of_the_shill_screen.dart | 2 +- .../main/screens/quests/referrals_quest_screen.dart | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart index 9798a485..777002af 100644 --- a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart @@ -254,7 +254,7 @@ class _CompleteSetupActionSheetState extends ConsumerState { alignment: Alignment.center, child: Text( 'Add Raid Submission', - style: context.themeText.smallTitle?.copyWith(color: const Color(0xFFF4F6F9)), + style: context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary), ), ), ), diff --git a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart index 420ede9a..849421a0 100644 --- a/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/referrals_quest_screen.dart @@ -321,7 +321,7 @@ class _ReferralsQuestScreenState extends ConsumerState { alignment: Alignment.center, child: Text( 'Share Link', - style: context.themeText.smallTitle?.copyWith(color: const Color(0xFFF4F6F9)), + style: context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary), ), ), ), From 3439aeffa13ee94cbd639bf39e01867bf7bdd0c3 Mon Sep 17 00:00:00 2001 From: NIK Date: Mon, 2 Feb 2026 11:35:26 +0800 Subject: [PATCH 22/23] more small fixes --- .../lib/features/setup/rewards_address_setup_screen.dart | 5 ++--- .../main/screens/quests/complete_setup_action_sheet.dart | 4 +--- .../main/screens/quests/king_of_the_shill_screen.dart | 8 -------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/miner-app/lib/features/setup/rewards_address_setup_screen.dart b/miner-app/lib/features/setup/rewards_address_setup_screen.dart index 64ba8179..5c60d10f 100644 --- a/miner-app/lib/features/setup/rewards_address_setup_screen.dart +++ b/miner-app/lib/features/setup/rewards_address_setup_screen.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flash/flash_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -82,9 +83,7 @@ class _RewardsAddressSetupScreenState extends State { print('Rewards address saved: $address'); if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Rewards address saved successfully!'))); + context.showSuccessBar(content: Text('Rewards address saved successfully!')); // Navigate to the main mining screen context.go('/miner_dashboard'); } diff --git a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart index 777002af..ec0486cf 100644 --- a/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart +++ b/mobile-app/lib/features/main/screens/quests/complete_setup_action_sheet.dart @@ -441,9 +441,7 @@ class _UnlinkConfirmationSheetState extends State<_UnlinkConfirmationSheet> { } catch (e) { if (mounted) { setState(() => _isUnlinking = false); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error: $e'), backgroundColor: widget.dangerColor)); + context.showErrorSnackbar(title: 'Error', message: 'Error: $e'); } } } diff --git a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart index d94ee5fd..f57ec327 100644 --- a/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart +++ b/mobile-app/lib/features/main/screens/quests/king_of_the_shill_screen.dart @@ -103,10 +103,6 @@ class _KingOfTheShillScreenState extends ConsumerState { ref.invalidate(raiderSubmissionsProvider); } - String _formatTimeAgo(String url) { - return ''; - } - Future _openUrl(String url) async { final uri = Uri.tryParse(url); if (uri != null && await canLaunchUrl(uri)) { @@ -355,10 +351,6 @@ class _KingOfTheShillScreenState extends ConsumerState { ), ), ), - Text( - _formatTimeAgo(url), - style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 10, fontFamily: 'Fira Code'), - ), ], ), ); From 8fd76f8fa37c287c6e09366fe317a280b0beed77 Mon Sep 17 00:00:00 2001 From: NIK Date: Mon, 2 Feb 2026 11:53:09 +0800 Subject: [PATCH 23/23] fix linter error that only happens on github, not locally maybe different versions of flutter --- miner-app/lib/src/services/chain_rpc_client.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/miner-app/lib/src/services/chain_rpc_client.dart b/miner-app/lib/src/services/chain_rpc_client.dart index d5ee415d..54c23655 100644 --- a/miner-app/lib/src/services/chain_rpc_client.dart +++ b/miner-app/lib/src/services/chain_rpc_client.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; + import 'package:http/http.dart' as http; class ChainInfo { @@ -196,7 +197,8 @@ class ChainRpcClient { /// Execute a JSON-RPC call Future _rpcCall(String method, [List? params]) async { - final request = {'jsonrpc': '2.0', 'id': _requestId++, 'method': method, if (params != null) 'params': params}; + final request = {'jsonrpc': '2.0', 'id': _requestId++, 'method': method}; + if (params != null) request['params'] = params; // Only print RPC calls when debugging connection issues // print('DEBUG: Making RPC call: $method with request: ${json.encode(request)}');