From 5c88248cf59180c1f41969a92aada45a80dc6409 Mon Sep 17 00:00:00 2001 From: kolo Date: Sat, 21 Mar 2026 11:20:40 +0300 Subject: [PATCH] update --- lib/core/services/card_color_service.dart | 2 +- .../dashboard/widgets/balance_card.dart | 7 +- .../widgets/color_editor_overlay.dart | 753 +++++++++++------- lib/features/settings/screen.dart | 60 +- 4 files changed, 497 insertions(+), 325 deletions(-) diff --git a/lib/core/services/card_color_service.dart b/lib/core/services/card_color_service.dart index fa16693..6886f07 100644 --- a/lib/core/services/card_color_service.dart +++ b/lib/core/services/card_color_service.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; -enum GradientType { linear, radial, sweep, linearReverse } +enum GradientType { linear, linearReverse, radial, sweep, solid } class CardColorService { static const _key1 = 'card_color_primary'; diff --git a/lib/features/dashboard/widgets/balance_card.dart b/lib/features/dashboard/widgets/balance_card.dart index 77a3be8..a89eb0d 100644 --- a/lib/features/dashboard/widgets/balance_card.dart +++ b/lib/features/dashboard/widgets/balance_card.dart @@ -119,6 +119,11 @@ class BalanceCardState extends ConsumerState ], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], ); + case GradientType.solid: + return LinearGradient( + colors: [primary, primary, primary], + stops: const [0.0, 0.5, 1.0], + ); } } @@ -159,7 +164,7 @@ class BalanceCardState extends ConsumerState ..rotateY(_tiltY * 0.42), child: Container( width: double.infinity, - height: 180, + height: 220, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: _buildGradient(primary, secondary, gradientType), diff --git a/lib/features/dashboard/widgets/color_editor_overlay.dart b/lib/features/dashboard/widgets/color_editor_overlay.dart index 886faa6..f38415e 100644 --- a/lib/features/dashboard/widgets/color_editor_overlay.dart +++ b/lib/features/dashboard/widgets/color_editor_overlay.dart @@ -29,9 +29,11 @@ class _FullScreenBlurOverlayState extends State { Widget build(BuildContext context) { final mq = MediaQuery.of(widget.context); final cardTop = mq.padding.top + kToolbarHeight + 16; - final cardHeight = 180.0; - final panelTop = cardTop + cardHeight + 50; - final panelBottom = mq.padding.bottom + 16; + const cardHeight = 220.0; + final panelTop = cardTop + cardHeight + 16; + + // Fixed panel height — smaller than before, no bottom stretching + const panelHeight = 310.0; return Material( color: Colors.transparent, @@ -40,16 +42,12 @@ class _FullScreenBlurOverlayState extends State { Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - child: Container( - color: Colors.black.withOpacity(0.6), - ), + child: Container(color: Colors.black.withOpacity(0.6)), ), ), Positioned.fill( child: GestureDetector( - onTap: () { - dash.closeOverlay(apply: false); - }, + onTap: () => dash.closeOverlay(apply: false), behavior: HitTestBehavior.translucent, child: const SizedBox.expand(), ), @@ -73,11 +71,11 @@ class _FullScreenBlurOverlayState extends State { top: panelTop, left: 20, right: 20, - bottom: panelBottom, + // No bottom — height is explicit, panel won't stretch to screen bottom child: GestureDetector( onTap: () {}, behavior: HitTestBehavior.opaque, - child: _buildPanel(context, mq), + child: _buildPanel(panelHeight), ), ), ], @@ -85,320 +83,469 @@ class _FullScreenBlurOverlayState extends State { ); } - Widget _buildPanel(BuildContext context, MediaQueryData mq) { - return SingleChildScrollView( - physics: const ClampingScrollPhysics(), - child: Container( - padding: const EdgeInsets.fromLTRB(20, 16, 20, 20), - decoration: BoxDecoration( - color: Theme.of(widget.context).colorScheme.surface, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 24, - offset: const Offset(0, 8), - ), - ], - ), - child: StatefulBuilder( - builder: (ctx, setPanelState) { - final isDark = Theme.of(widget.context).brightness == Brightness.dark; + Widget _buildPanel(double panelHeight) { + return Container( + height: panelHeight, + decoration: BoxDecoration( + color: Theme.of(widget.context).colorScheme.surface, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 24, + offset: const Offset(0, 8), + ), + ], + ), + child: StatefulBuilder( + builder: (ctx, setPanelState) { + void onHSVChanged(HSVColor hsv) { + setPanelState(() {}); + dash.setState(() { + if (dash.editingPrimary) { + dash.tempPrimaryHSV = hsv; + dash.tempPrimary = hsv.toColor(); + } else { + dash.tempSecondaryHSV = hsv; + dash.tempSecondary = hsv.toColor(); + } + }); + dash.overlayEntry?.markNeedsBuild(); + } - void onHSVChanged(HSVColor hsv) { - setPanelState(() {}); - dash.setState(() { - if (dash.editingPrimary) { - dash.tempPrimaryHSV = hsv; - dash.tempPrimary = hsv.toColor(); - } else { - dash.tempSecondaryHSV = hsv; - dash.tempSecondary = hsv.toColor(); - } - }); - dash.overlayEntry?.markNeedsBuild(); - } + // In solid mode, editing is always "primary" (the solid color) + final currentHSV = dash.editingPrimary + ? dash.tempPrimaryHSV + : dash.tempSecondaryHSV; + final isSolid = dash.tempGradientType == GradientType.solid; - final currentHSV = dash.editingPrimary - ? dash.tempPrimaryHSV - : dash.tempSecondaryHSV; - - return Column( - mainAxisSize: MainAxisSize.min, + return Padding( + padding: const EdgeInsets.fromLTRB(16, 14, 16, 14), + child: Column( + mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ + + // ── Row 1: [──Primary──] [──Secondary──] | [Solid] [X] ── Row( children: [ - PanelTab( - label: 'Primary', - isSelected: dash.editingPrimary, - color: dash.tempPrimary, + // Expanded tabs fill available width equally + Expanded( + child: PanelTab( + label: 'Primary', + isSelected: dash.editingPrimary && !isSolid, + color: dash.tempPrimary, + onTap: isSolid ? null : () { + dash.setState(() => dash.editingPrimary = true); + setPanelState(() {}); + dash.overlayEntry?.markNeedsBuild(); + }, + ), + ), + const SizedBox(width: 6), + Expanded( + child: PanelTab( + label: 'Secondary', + isSelected: !dash.editingPrimary && !isSolid, + color: dash.tempSecondary, + onTap: isSolid ? null : () { + dash.setState(() => dash.editingPrimary = false); + setPanelState(() {}); + dash.overlayEntry?.markNeedsBuild(); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Container( + width: 1, + height: 20, + color: Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.15), + ), + ), + // Solid toggle + GestureDetector( onTap: () { - dash.setState(() => dash.editingPrimary = true); + dash.setState(() { + dash.tempGradientType = isSolid + ? GradientType.linear + : GradientType.solid; + }); setPanelState(() {}); dash.overlayEntry?.markNeedsBuild(); }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: isSolid + ? const Color(0xFF7C6DED).withOpacity(0.15) + : Colors.transparent, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: isSolid + ? const Color(0xFF7C6DED) + : Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.2), + width: 1.5, + ), + ), + child: Text( + 'Solid', + style: TextStyle( + fontSize: 11, + fontWeight: isSolid + ? FontWeight.w600 + : FontWeight.normal, + color: isSolid + ? const Color(0xFF7C6DED) + : Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.5), + ), + ), + ), ), - const SizedBox(width: 10), - PanelTab( - label: 'Secondary', - isSelected: !dash.editingPrimary, - color: dash.tempSecondary, - onTap: () { - dash.setState(() => dash.editingPrimary = false); - setPanelState(() {}); - dash.overlayEntry?.markNeedsBuild(); - }, - ), - const Spacer(), + const SizedBox(width: 8), + // Close button GestureDetector( onTap: () => dash.closeOverlay(apply: false), child: Container( - width: 32, - height: 32, + width: 30, + height: 30, decoration: BoxDecoration( color: const Color(0xFFE05C6B).withOpacity(0.15), shape: BoxShape.circle, ), - child: const Icon( - Icons.close_rounded, - size: 18, - color: Color(0xFFE05C6B), - ), + child: const Icon(Icons.close_rounded, + size: 16, color: Color(0xFFE05C6B)), ), ), ], ), - const SizedBox(height: 16), - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: SizedBox( - height: 160, - width: double.infinity, - child: ColorPickerArea( - currentHSV, - onHSVChanged, - PaletteType.hsvWithHue, - ), - ), - ), - const SizedBox(height: 12), - ClipRRect( - borderRadius: BorderRadius.circular(6), - child: SizedBox( - height: 22, - child: ColorPickerSlider( - TrackType.hue, - currentHSV, - onHSVChanged, - displayThumbColor: true, - ), - ), - ), - const SizedBox(height: 12), - Row( - children: [ - GestureDetector( - onTap: () { - dash.setState(() => dash.editingPrimary = true); - setPanelState(() {}); - dash.overlayEntry?.markNeedsBuild(); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 28, - height: 28, - decoration: BoxDecoration( - color: dash.tempPrimary, - borderRadius: BorderRadius.circular(8), - border: dash.editingPrimary - ? Border.all(color: Colors.white, width: 2) - : Border.all( - color: Colors.transparent, width: 2), - ), - ), - const SizedBox(width: 8), - Text( - '#${dash.tempPrimary.value.toRadixString(16).substring(2).toUpperCase()}', - style: TextStyle( - fontSize: 12, - fontFamily: 'monospace', - fontWeight: dash.editingPrimary - ? FontWeight.w600 - : FontWeight.normal, - color: Theme.of(widget.context) - .colorScheme - .onSurface - .withOpacity( - dash.editingPrimary ? 0.8 : 0.4, - ), - ), - ), - ], - ), - ), - const Spacer(), - GestureDetector( - onTap: () { - dash.setState(() => dash.editingPrimary = false); - setPanelState(() {}); - dash.overlayEntry?.markNeedsBuild(); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '#${dash.tempSecondary.value.toRadixString(16).substring(2).toUpperCase()}', - style: TextStyle( - fontSize: 12, - fontFamily: 'monospace', - fontWeight: !dash.editingPrimary - ? FontWeight.w600 - : FontWeight.normal, - color: Theme.of(widget.context) - .colorScheme - .onSurface - .withOpacity( - !dash.editingPrimary ? 0.8 : 0.4, - ), - ), - ), - const SizedBox(width: 8), - Container( - width: 28, - height: 28, - decoration: BoxDecoration( - color: dash.tempSecondary, - borderRadius: BorderRadius.circular(8), - border: !dash.editingPrimary - ? Border.all(color: Colors.white, width: 2) - : Border.all( - color: Colors.transparent, width: 2), - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - Text( - 'Gradient Style', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.6), - ), - ), + const SizedBox(height: 10), - Row( - children: GradientType.values.map((type) { - final isSelected = dash.tempGradientType == type; - final label = switch (type) { - GradientType.linear => 'Linear', - GradientType.linearReverse => 'Reverse', - GradientType.radial => 'Radial', - GradientType.sweep => 'Sweep', - }; - final icon = switch (type) { - GradientType.linear => Icons.trending_flat_rounded, - GradientType.linearReverse => Icons.swap_horiz_rounded, - GradientType.radial => Icons.blur_circular_rounded, - GradientType.sweep => Icons.rotate_right_rounded, - }; - return Expanded( - child: Padding( - padding: const EdgeInsets.only(right: 8), - child: GestureDetector( - onTap: () { - dash.setState(() => dash.tempGradientType = type); - setPanelState(() {}); - dash.overlayEntry?.markNeedsBuild(); - }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 150), - padding: const EdgeInsets.symmetric(vertical: 8), - decoration: BoxDecoration( - color: isSelected - ? const Color(0xFF7C6DED).withOpacity(0.15) - : Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.05), - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: isSelected - ? const Color(0xFF7C6DED) - : Colors.transparent, - width: 1.5, + + // ── Spectrum + hue + preview ── + // LayoutBuilder captures finite height from Expanded. + // Spectrum alone is wrapped in IgnorePointer when solid. + // Hue slider is ALWAYS active (used to pick the solid color too). + Expanded( + child: LayoutBuilder( + builder: (lbCtx, constraints) { + // Reserve space for: hue(20) + gap(8) + preview(26) + 2×gap(8) = 62 + const reservedBelow = 62.0; + final spectrumH = + (constraints.maxHeight - reservedBelow).clamp( + 40.0, double.infinity); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Spectrum — dimmed & blocked in Solid mode + IgnorePointer( + ignoring: isSolid, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: isSolid ? 0.3 : 1.0, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: SizedBox( + height: spectrumH, + child: ColorPickerArea( + currentHSV, + onHSVChanged, + PaletteType.hsvWithHue, + ), + ), ), ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - icon, - size: 18, - color: isSelected - ? const Color(0xFF7C6DED) - : Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.5), - ), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 10, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - color: isSelected - ? const Color(0xFF7C6DED) - : Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.5), - ), - ), - ], + ), + const SizedBox(height: 8), + // Hue slider — ALWAYS active (works for both gradient & solid) + ClipRRect( + borderRadius: BorderRadius.circular(6), + child: SizedBox( + height: 20, + child: ColorPickerSlider( + TrackType.hue, + currentHSV, + onHSVChanged, + displayThumbColor: true, + ), ), ), - ), - ), - ); - }).toList(), + const SizedBox(height: 8), + // Color hex preview row + IgnorePointer( + ignoring: isSolid, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: isSolid ? 0.4 : 1.0, + child: SizedBox( + height: 26, + child: Row( + children: [ + GestureDetector( + onTap: () { + dash.setState( + () => dash.editingPrimary = true); + setPanelState(() {}); + dash.overlayEntry?.markNeedsBuild(); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 22, + height: 22, + decoration: BoxDecoration( + color: dash.tempPrimary, + borderRadius: + BorderRadius.circular(6), + border: dash.editingPrimary + ? Border.all( + color: Colors.white, + width: 2) + : Border.all( + color: Colors.transparent, + width: 2), + ), + ), + const SizedBox(width: 5), + Text( + '#${dash.tempPrimary.value.toRadixString(16).substring(2).toUpperCase()}', + style: TextStyle( + fontSize: 11, + fontFamily: 'monospace', + fontWeight: dash.editingPrimary + ? FontWeight.w600 + : FontWeight.normal, + color: Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity( + dash.editingPrimary + ? 0.8 + : 0.4), + ), + ), + ], + ), + ), + const Spacer(), + if (!isSolid) + GestureDetector( + onTap: () { + dash.setState(() => + dash.editingPrimary = false); + setPanelState(() {}); + dash.overlayEntry?.markNeedsBuild(); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '#${dash.tempSecondary.value.toRadixString(16).substring(2).toUpperCase()}', + style: TextStyle( + fontSize: 11, + fontFamily: 'monospace', + fontWeight: !dash.editingPrimary + ? FontWeight.w600 + : FontWeight.normal, + color: Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity( + !dash.editingPrimary + ? 0.8 + : 0.4), + ), + ), + const SizedBox(width: 5), + Container( + width: 22, + height: 22, + decoration: BoxDecoration( + color: dash.tempSecondary, + borderRadius: + BorderRadius.circular(6), + border: !dash.editingPrimary + ? Border.all( + color: Colors.white, + width: 2) + : Border.all( + color: + Colors.transparent, + width: 2), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), ), - const SizedBox(height: 16), + + const SizedBox(height: 8), + + // ── Gradient type row (dimmed when Solid) ── + IgnorePointer( + ignoring: isSolid, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: isSolid ? 0.3 : 1.0, + child: Row( + children: GradientType.values + .where((t) => t != GradientType.solid) + .map((type) { + final isSelected = dash.tempGradientType == type; + final label = switch (type) { + GradientType.linear => 'Linear', + GradientType.linearReverse => 'Reverse', + GradientType.radial => 'Radial', + GradientType.sweep => 'Sweep', + GradientType.solid => '', + }; + final icon = switch (type) { + GradientType.linear => Icons.trending_flat_rounded, + GradientType.linearReverse => + Icons.swap_horiz_rounded, + GradientType.radial => Icons.blur_circular_rounded, + GradientType.sweep => Icons.rotate_right_rounded, + GradientType.solid => Icons.square_rounded, + }; + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 6), + child: GestureDetector( + onTap: () { + dash.setState( + () => dash.tempGradientType = type); + setPanelState(() {}); + dash.overlayEntry?.markNeedsBuild(); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + padding: + const EdgeInsets.symmetric(vertical: 5), + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFF7C6DED) + .withOpacity(0.15) + : Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.05), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected + ? const Color(0xFF7C6DED) + : Colors.transparent, + width: 1.5, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, + size: 15, + color: isSelected + ? const Color(0xFF7C6DED) + : Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.45)), + const SizedBox(height: 2), + Text( + label, + style: TextStyle( + fontSize: 9, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.normal, + color: isSelected + ? const Color(0xFF7C6DED) + : Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.45), + ), + ), + ], + ), + ), + ), + ), + ); + }).toList(), + ), + ), + ), + + const SizedBox(height: 8), + + // ── Reset + Apply ── Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () { - final isDark = Theme.of(widget.context).brightness == Brightness.dark; - final defPrimary = isDark + final isDarkTheme = + Theme.of(widget.context).brightness == + Brightness.dark; + final defP = isDarkTheme ? CardColorService.defaultPrimary : CardColorService.defaultPrimaryLight; - final defSecondary = isDark + final defS = isDarkTheme ? CardColorService.defaultSecondary : CardColorService.defaultSecondaryLight; dash.setState(() { - dash.tempPrimary = defPrimary; - dash.tempSecondary = defSecondary; - dash.tempPrimaryHSV = HSVColor.fromColor(defPrimary); - dash.tempSecondaryHSV = HSVColor.fromColor(defSecondary); + dash.tempPrimary = defP; + dash.tempSecondary = defS; + dash.tempPrimaryHSV = HSVColor.fromColor(defP); + dash.tempSecondaryHSV = HSVColor.fromColor(defS); dash.tempGradientType = GradientType.linear; }); setPanelState(() {}); dash.overlayEntry?.markNeedsBuild(); }, - icon: const Icon(Icons.restart_alt_rounded, size: 16), - label: const Text('Reset'), + icon: const Icon(Icons.restart_alt_rounded, size: 15), + label: const Text('Reset', + style: TextStyle(fontSize: 13)), style: OutlinedButton.styleFrom( - foregroundColor: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.7), + foregroundColor: Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.7), side: BorderSide( - color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.2), + color: Theme.of(widget.context) + .colorScheme + .onSurface + .withOpacity(0.2), ), - padding: const EdgeInsets.symmetric(vertical: 14), + padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), + borderRadius: BorderRadius.circular(12)), ), ), ), - const SizedBox(width: 12), + const SizedBox(width: 10), Expanded( flex: 2, child: ElevatedButton( @@ -406,23 +553,21 @@ class _FullScreenBlurOverlayState extends State { style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF7C6DED), foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), + padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - child: const Text( - 'Apply', - style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15), + borderRadius: BorderRadius.circular(12)), ), + child: const Text('Apply', + style: TextStyle( + fontWeight: FontWeight.w700, fontSize: 14)), ), ), ], ), ], - ); - }, - ), + ), + ); + }, ), ); } @@ -432,20 +577,21 @@ class PanelTab extends StatelessWidget { final String label; final bool isSelected; final Color color; - final VoidCallback onTap; + final VoidCallback? onTap; const PanelTab({ super.key, required this.label, required this.isSelected, required this.color, - required this.onTap, + this.onTap, }); @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final unselectedBorder = isDark ? Colors.white24 : const Color(0xFFCCCCDD); + final unselectedBorder = + isDark ? Colors.white24 : const Color(0xFFCCCCDD); final unselectedText = isDark ? Colors.white60 : Theme.of(context).colorScheme.onSurface.withOpacity(0.5); @@ -454,7 +600,9 @@ class PanelTab extends StatelessWidget { onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 150), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + // Fill full width of Expanded slot (no padding-based width) + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: isSelected ? color.withOpacity(0.15) : Colors.transparent, borderRadius: BorderRadius.circular(10), @@ -465,10 +613,11 @@ class PanelTab extends StatelessWidget { ), child: Row( mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 14, - height: 14, + width: 10, + height: 10, decoration: BoxDecoration( color: color, shape: BoxShape.circle, @@ -478,13 +627,19 @@ class PanelTab extends StatelessWidget { ), ), ), - const SizedBox(width: 8), - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - color: isSelected ? color : unselectedText, + const SizedBox(width: 6), + Flexible( + child: Text( + label, + overflow: TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: + isSelected ? FontWeight.w600 : FontWeight.normal, + color: isSelected ? color : unselectedText, + ), ), ), ], @@ -492,4 +647,4 @@ class PanelTab extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/features/settings/screen.dart b/lib/features/settings/screen.dart index b68a4a6..577cf6a 100644 --- a/lib/features/settings/screen.dart +++ b/lib/features/settings/screen.dart @@ -574,31 +574,43 @@ class _SettingsScreenState extends ConsumerState { Padding( padding: const EdgeInsets.only(bottom: 24), child: Center( - child: RichText( - textAlign: TextAlign.center, - text: const TextSpan( - style: TextStyle(fontSize: 13, color: Colors.white30), - children: [ - TextSpan( - text: 'casha', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white38, - ), + child: Builder( + builder: (context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final baseColor = isDark + ? Colors.white.withOpacity(0.25) + : Colors.black.withOpacity(0.25); + final emphasisColor = isDark + ? Colors.white.withOpacity(0.35) + : Colors.black.withOpacity(0.35); + + return RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: TextStyle(fontSize: 13, color: baseColor), + children: [ + TextSpan( + text: 'casha', + style: TextStyle( + fontWeight: FontWeight.bold, + color: emphasisColor, + ), + ), + TextSpan( + text: ' powered with ❤️ by ', + style: TextStyle(fontStyle: FontStyle.italic), + ), + TextSpan( + text: 'kolo', + style: TextStyle( + fontWeight: FontWeight.bold, + color: emphasisColor, + ), + ), + ], ), - TextSpan( - text: ' powered with ❤️ by ', - style: TextStyle(fontStyle: FontStyle.italic), - ), - TextSpan( - text: 'kolo', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white38, - ), - ), - ], - ), + ); + }, ), ), ),