Refactor theme management: Replace AppColors with AppPalette

- Removed AppColors class and migrated all references to AppPalette.
- Updated VaultAccessScreen, MenuDrawer, NoteCard, SearchAppBar, and other widgets to use AppPalette for color management.
- Introduced AppPalette to handle light and dark themes with appropriate color schemes.
- Adjusted theme application in AppTheme to utilize AppPalette extensions.
- Updated tests to reflect changes in theme structure and color references.
This commit is contained in:
2026-05-23 13:55:40 +02:00
parent 29881183ed
commit 1dede9eb78
16 changed files with 1031 additions and 618 deletions
+96 -16
View File
@@ -14,7 +14,7 @@ import 'package:notas/screens/biometric_gate_screen.dart';
import 'package:notas/screens/home_screen.dart'; import 'package:notas/screens/home_screen.dart';
import 'package:notas/screens/settings_screen.dart'; import 'package:notas/screens/settings_screen.dart';
import 'package:notas/screens/vault_access_screen.dart'; import 'package:notas/screens/vault_access_screen.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
import 'package:notas/theme/app_theme.dart'; import 'package:notas/theme/app_theme.dart';
import 'package:notas/widgets/sync_status.dart'; import 'package:notas/widgets/sync_status.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@@ -44,6 +44,7 @@ class _NotesAppState extends State<NotesApp>
static const Duration _syncInterval = Duration(minutes: 5); static const Duration _syncInterval = Duration(minutes: 5);
static const Duration _windowSizeSaveDelay = Duration(milliseconds: 350); static const Duration _windowSizeSaveDelay = Duration(milliseconds: 350);
static const String _themeSeedColorKey = 'theme_seed_color_v1'; static const String _themeSeedColorKey = 'theme_seed_color_v1';
static const String _themeModeKey = 'theme_mode_v1';
final LocalVaultService _vaultService = LocalVaultService.instance; final LocalVaultService _vaultService = LocalVaultService.instance;
final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
@@ -69,7 +70,30 @@ class _NotesAppState extends State<NotesApp>
String? _syncErrorMessage; String? _syncErrorMessage;
int _syncOperationId = 0; int _syncOperationId = 0;
int _homeRefreshToken = 0; int _homeRefreshToken = 0;
Color _themeSeedColor = AppColors.defaultThemeSeedColor; Color _themeSeedColor = Colors.amber;
ThemeMode _themeMode = ThemeMode.system;
// Cached ThemeData for light and dark variants.
ThemeData? _lightTheme;
ThemeData? _darkTheme;
Brightness _effectiveBrightness() {
switch (_themeMode) {
case ThemeMode.dark:
return Brightness.dark;
case ThemeMode.light:
return Brightness.light;
case ThemeMode.system:
return WidgetsBinding.instance.platformDispatcher.platformBrightness;
}
}
AppPalette _activePalette() {
return AppPalette.fromBrightness(
_effectiveBrightness(),
seedColor: _themeSeedColor,
);
}
@override @override
void initState() { void initState() {
@@ -80,6 +104,7 @@ class _NotesAppState extends State<NotesApp>
windowManager.setPreventClose(true); windowManager.setPreventClose(true);
} }
_loadThemeSeedColor(); _loadThemeSeedColor();
_loadThemeMode();
_bootstrapVault(); _bootstrapVault();
} }
@@ -106,7 +131,7 @@ class _NotesAppState extends State<NotesApp>
setState(() { setState(() {
_themeSeedColor = Color(storedColorValue); _themeSeedColor = Color(storedColorValue);
_themeData = AppTheme.theme(seedColor: _themeSeedColor); _updateThemeData();
}); });
} }
@@ -120,15 +145,54 @@ class _NotesAppState extends State<NotesApp>
setState(() { setState(() {
_themeSeedColor = color; _themeSeedColor = color;
_themeData = AppTheme.theme(seedColor: _themeSeedColor); _updateThemeData();
}); });
} }
// Cached ThemeData to avoid recomputing on every build. Future<void> _loadThemeMode() async {
ThemeData? _themeData; final SharedPreferences prefs = await SharedPreferences.getInstance();
final int? stored = prefs.getInt(_themeModeKey);
if (!mounted) return;
ThemeData get _theme => setState(() {
_themeData ??= AppTheme.theme(seedColor: _themeSeedColor); if (stored == null) {
_themeMode = ThemeMode.system;
} else if (stored == 1) {
_themeMode = ThemeMode.light;
} else if (stored == 2) {
_themeMode = ThemeMode.dark;
} else {
_themeMode = ThemeMode.system;
}
_updateThemeData();
});
}
Future<void> _setThemeMode(ThemeMode mode) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int stored = mode == ThemeMode.system
? 0
: (mode == ThemeMode.light ? 1 : 2);
await prefs.setInt(_themeModeKey, stored);
if (!mounted) return;
setState(() {
_themeMode = mode;
});
}
void _updateThemeData() {
_lightTheme = AppTheme.theme(
seedColor: _themeSeedColor,
brightness: Brightness.light,
);
_darkTheme = AppTheme.theme(
seedColor: _themeSeedColor,
brightness: Brightness.dark,
);
// Updated light/dark themes regenerated
}
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
@@ -429,11 +493,13 @@ class _NotesAppState extends State<NotesApp>
final bool? retry = await showDialog<bool>( final bool? retry = await showDialog<bool>(
context: dialogCtx, context: dialogCtx,
builder: (BuildContext context) => AlertDialog( builder: (BuildContext context) {
backgroundColor: AppColors.cardBackground, final AppPalette palette = _activePalette();
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border), side: BorderSide(color: palette.border),
), ),
title: const Text('No se pudo activar la biometría'), title: const Text('No se pudo activar la biometría'),
content: const Text( content: const Text(
@@ -449,7 +515,8 @@ class _NotesAppState extends State<NotesApp>
child: const Text('Reintentar'), child: const Text('Reintentar'),
), ),
], ],
), );
},
); );
if (retry != true) { if (retry != true) {
@@ -825,6 +892,7 @@ class _NotesAppState extends State<NotesApp>
} }
Widget _buildMainShell(NoteRepository repository) { Widget _buildMainShell(NoteRepository repository) {
final AppPalette palette = _activePalette();
final Widget activeScreen = _currentSection == _AppSection.home final Widget activeScreen = _currentSection == _AppSection.home
? HomeScreen( ? HomeScreen(
key: const ValueKey<String>('home-screen'), key: const ValueKey<String>('home-screen'),
@@ -845,6 +913,8 @@ class _NotesAppState extends State<NotesApp>
onForceSync: () => _performSync(forceFull: true), onForceSync: () => _performSync(forceFull: true),
currentSeedColor: _themeSeedColor, currentSeedColor: _themeSeedColor,
onThemeColorSelected: _setThemeSeedColor, onThemeColorSelected: _setThemeSeedColor,
currentThemeMode: _themeMode,
onThemeModeSelected: _setThemeMode,
); );
return Shortcuts( return Shortcuts(
@@ -864,9 +934,7 @@ class _NotesAppState extends State<NotesApp>
autofocus: true, autofocus: true,
child: Scaffold( child: Scaffold(
body: Container( body: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(gradient: palette.backdropGradient),
gradient: AppColors.backdropGradient,
),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -1026,7 +1094,19 @@ class _NotesAppState extends State<NotesApp>
title: 'Mis Notas', title: 'Mis Notas',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
scaffoldMessengerKey: _scaffoldMessengerKey, scaffoldMessengerKey: _scaffoldMessengerKey,
theme: _theme, theme:
_lightTheme ??
AppTheme.theme(
seedColor: _themeSeedColor,
brightness: Brightness.light,
),
darkTheme:
_darkTheme ??
AppTheme.theme(
seedColor: _themeSeedColor,
brightness: Brightness.dark,
),
themeMode: _themeMode,
home: homeWidget, home: homeWidget,
); );
} }
+11 -11
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class BiometricChoiceScreen extends StatelessWidget { class BiometricChoiceScreen extends StatelessWidget {
const BiometricChoiceScreen({ const BiometricChoiceScreen({
@@ -15,9 +15,11 @@ class BiometricChoiceScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient), decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -30,12 +32,12 @@ class BiometricChoiceScreen extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.surface, color: palette.cardBackground,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderMuted), border: Border.all(color: palette.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.shadow, color: palette.shadowSoft,
blurRadius: 30, blurRadius: 30,
offset: const Offset(0, 18), offset: const Offset(0, 18),
), ),
@@ -55,7 +57,7 @@ class BiometricChoiceScreen extends StatelessWidget {
'Proteger con huella', 'Proteger con huella',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: Colors.white,
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -65,7 +67,7 @@ class BiometricChoiceScreen extends StatelessWidget {
'¿Quieres que la app te pida huella o cara antes de entrar a tus notas?', '¿Quieres que la app te pida huella o cara antes de entrar a tus notas?',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
height: 1.4, height: 1.4,
), ),
), ),
@@ -94,10 +96,8 @@ class BiometricChoiceScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 14, vertical: 14,
), ),
side: const BorderSide( side: BorderSide(color: palette.border),
color: AppColors.textDisabled, foregroundColor: palette.textPrimary,
),
foregroundColor: AppColors.textPrimary,
), ),
child: const Text('No, entrar sin huella'), child: const Text('No, entrar sin huella'),
), ),
+9 -7
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class BiometricGateScreen extends StatefulWidget { class BiometricGateScreen extends StatefulWidget {
const BiometricGateScreen({ const BiometricGateScreen({
@@ -39,9 +39,11 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient), decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -54,12 +56,12 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
child: Container( child: Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.surface, color: palette.cardBackground,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderMuted), border: Border.all(color: palette.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.shadow, color: palette.shadowSoft,
blurRadius: 30, blurRadius: 30,
offset: const Offset(0, 18), offset: const Offset(0, 18),
), ),
@@ -79,7 +81,7 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
'Desbloqueo biométrico', 'Desbloqueo biométrico',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: Colors.white,
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -89,7 +91,7 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
'Pon tu huella o cara para entrar a tus notas.', 'Pon tu huella o cara para entrar a tus notas.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
height: 1.4, height: 1.4,
), ),
), ),
+62 -50
View File
@@ -15,7 +15,7 @@ import 'package:notas/widgets/note_card.dart';
import 'package:notas/widgets/search_app_bar.dart'; import 'package:notas/widgets/search_app_bar.dart';
import 'package:notas/widgets/sync_status.dart'; import 'package:notas/widgets/sync_status.dart';
import 'package:notas/widgets/sync_status_indicator.dart'; import 'package:notas/widgets/sync_status_indicator.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
const HomeScreen({ const HomeScreen({
@@ -489,9 +489,11 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
); );
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient), decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -565,7 +567,7 @@ class _HomeScreenState extends State<HomeScreen> {
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: _closeMenu, onTap: _closeMenu,
child: Container(color: AppColors.overlay), child: Container(color: palette.overlay),
), ),
), ),
), ),
@@ -578,7 +580,7 @@ class _HomeScreenState extends State<HomeScreen> {
bottom: 0, bottom: 0,
width: 280, width: 280,
child: Material( child: Material(
color: AppColors.cardBackground, color: palette.cardBackground,
elevation: 8, elevation: 8,
child: MenuDrawer( child: MenuDrawer(
onMenuItemTapped: _handleMenuItemTapped, onMenuItemTapped: _handleMenuItemTapped,
@@ -642,11 +644,13 @@ class _DraggableNote extends StatelessWidget {
final VoidCallback onDraggableCanceled; final VoidCallback onDraggableCanceled;
final VoidCallback onDragStarted; final VoidCallback onDragStarted;
Widget _buildFeedback() { Widget _buildFeedback(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.grabbing, cursor: SystemMouseCursors.grabbing,
child: Material( child: Material(
color: AppColors.transparent, color: palette.transparent,
elevation: 8, elevation: 8,
child: SizedBox( child: SizedBox(
width: cellWidth, width: cellWidth,
@@ -676,7 +680,9 @@ class _DraggableNote extends StatelessWidget {
); );
} }
Widget _buildChildWhenDragging() { Widget _buildChildWhenDragging(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.grabbing, cursor: SystemMouseCursors.grabbing,
child: Opacity( child: Opacity(
@@ -684,10 +690,10 @@ class _DraggableNote extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBackground, color: palette.cardBackground,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: borderColor ?? AppColors.textDisabled, color: borderColor ?? palette.textSecondary,
width: 1, width: 1,
), ),
), ),
@@ -697,8 +703,8 @@ class _DraggableNote extends StatelessWidget {
children: [ children: [
Text( Text(
note.title, note.title,
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -708,10 +714,7 @@ class _DraggableNote extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
note.body, note.body,
style: const TextStyle( style: TextStyle(color: palette.textSecondary, fontSize: 14),
color: AppColors.textSecondary,
fontSize: 14,
),
maxLines: 20, maxLines: 20,
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
), ),
@@ -724,10 +727,12 @@ class _DraggableNote extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final Widget content = Container( final Widget content = Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: isDragTargetActive border: isDragTargetActive
? Border.all(color: AppColors.dragTargetBorder, width: 2) ? Border.all(color: palette.dragTargetBorder, width: 2)
: null, : null,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@@ -747,8 +752,8 @@ class _DraggableNote extends StatelessWidget {
onDragStarted: onDragStarted, onDragStarted: onDragStarted,
onDragEnd: onDragEnd, onDragEnd: onDragEnd,
onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(), onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(),
feedback: _buildFeedback(), feedback: _buildFeedback(context),
childWhenDragging: _buildChildWhenDragging(), childWhenDragging: _buildChildWhenDragging(context),
child: content, child: content,
); );
} }
@@ -758,8 +763,8 @@ class _DraggableNote extends StatelessWidget {
onDragStarted: onDragStarted, onDragStarted: onDragStarted,
onDragEnd: onDragEnd, onDragEnd: onDragEnd,
onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(), onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(),
feedback: _buildFeedback(), feedback: _buildFeedback(context),
childWhenDragging: _buildChildWhenDragging(), childWhenDragging: _buildChildWhenDragging(context),
child: content, child: content,
); );
} }
@@ -778,15 +783,13 @@ class _EmptyState extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Center( return Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon( Icon(Icons.note_add_outlined, color: palette.textSecondary, size: 48),
Icons.note_add_outlined,
color: AppColors.textMuted,
size: 48,
),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
searchQuery != null && searchQuery!.isNotEmpty searchQuery != null && searchQuery!.isNotEmpty
@@ -796,8 +799,8 @@ class _EmptyState extends StatelessWidget {
: categoryName != null : categoryName != null
? 'No hay notas en esta categoría' ? 'No hay notas en esta categoría'
: 'Aún no hay notas', : 'Aún no hay notas',
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -812,7 +815,7 @@ class _EmptyState extends StatelessWidget {
? 'Pulsa el botón + para crear una nota en “$categoryName”.' ? 'Pulsa el botón + para crear una nota en “$categoryName”.'
: 'Pulsa el botón + para crear la primera.', : 'Pulsa el botón + para crear la primera.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.textSecondary), style: TextStyle(color: palette.textSecondary),
), ),
], ],
), ),
@@ -934,11 +937,13 @@ class _CategoryDialogState extends State<_CategoryDialog> {
Future<void> _deleteCategory() async { Future<void> _deleteCategory() async {
final bool? confirm = await showDialog<bool>( final bool? confirm = await showDialog<bool>(
context: context, context: context,
builder: (BuildContext context) => AlertDialog( builder: (BuildContext context) {
backgroundColor: AppColors.cardBackground, final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border), side: BorderSide(color: palette.border),
), ),
title: const Text('Borrar categoría'), title: const Text('Borrar categoría'),
content: const Text('¿Seguro que quieres borrar esta categoría?'), content: const Text('¿Seguro que quieres borrar esta categoría?'),
@@ -948,12 +953,15 @@ class _CategoryDialogState extends State<_CategoryDialog> {
child: const Text('Cancelar'), child: const Text('Cancelar'),
), ),
TextButton( TextButton(
style: TextButton.styleFrom(foregroundColor: AppColors.destructive), style: TextButton.styleFrom(
foregroundColor: palette.destructiveAccent,
),
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: const Text('Borrar'), child: const Text('Borrar'),
), ),
], ],
), );
},
); );
if (confirm != true) { if (confirm != true) {
@@ -982,11 +990,13 @@ class _CategoryDialogState extends State<_CategoryDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog( return AlertDialog(
backgroundColor: AppColors.cardBackground, backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border), side: BorderSide(color: palette.border),
), ),
title: Text( title: Text(
widget.category == null ? 'Crear categoría' : 'Editar categoría', widget.category == null ? 'Crear categoría' : 'Editar categoría',
@@ -1018,9 +1028,9 @@ class _CategoryDialogState extends State<_CategoryDialog> {
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.fill, color: palette.fill,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border), border: Border.all(color: palette.border),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -1049,7 +1059,7 @@ class _CategoryDialogState extends State<_CategoryDialog> {
), ),
], ],
), ),
const Divider(height: 1, color: AppColors.border), Divider(height: 1, color: palette.border),
Padding( Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: AnimatedSwitcher( child: AnimatedSwitcher(
@@ -1075,11 +1085,11 @@ class _CategoryDialogState extends State<_CategoryDialog> {
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: isSelected border: isSelected
? Border.all( ? Border.all(
color: AppColors.textPrimary, color: palette.textPrimary,
width: 2, width: 2,
) )
: Border.all( : Border.all(
color: AppColors.border, color: palette.border,
width: 1, width: 1,
), ),
), ),
@@ -1105,21 +1115,21 @@ class _CategoryDialogState extends State<_CategoryDialog> {
height: 42, height: 42,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? AppColors.hover ? palette.hover
: AppColors.transparent, : palette.transparent,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? AppColors.textPrimary ? palette.textPrimary
: AppColors.border, : palette.border,
width: 1, width: 1,
), ),
), ),
child: Icon( child: Icon(
icon, icon,
color: isSelected color: isSelected
? AppColors.textPrimary ? palette.textPrimary
: AppColors.textSecondary, : palette.textSecondary,
), ),
), ),
); );
@@ -1137,9 +1147,9 @@ class _CategoryDialogState extends State<_CategoryDialog> {
if (widget.category != null) if (widget.category != null)
TextButton( TextButton(
onPressed: _deleteCategory, onPressed: _deleteCategory,
child: const Text( child: Text(
'Borrar', 'Borrar',
style: TextStyle(color: AppColors.destructive), style: TextStyle(color: palette.destructiveAccent),
), ),
), ),
TextButton( TextButton(
@@ -1176,10 +1186,12 @@ class _PickerTabButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Material( return Material(
borderRadius: borderRadius, borderRadius: borderRadius,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
color: selected ? AppColors.hover : AppColors.transparent, color: selected ? palette.hover : palette.transparent,
child: InkWell( child: InkWell(
borderRadius: borderRadius, borderRadius: borderRadius,
onTap: onTap, onTap: onTap,
@@ -1189,7 +1201,7 @@ class _PickerTabButton extends StatelessWidget {
child: Text( child: Text(
label, label,
style: TextStyle( style: TextStyle(
color: selected ? AppColors.textPrimary : AppColors.textMuted, color: selected ? palette.textPrimary : palette.textSecondary,
fontWeight: selected ? FontWeight.w600 : FontWeight.w400, fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
), ),
), ),
+60 -58
View File
@@ -7,7 +7,7 @@ import 'package:intl/intl.dart';
import 'package:notas/models/category.dart'; import 'package:notas/models/category.dart';
import 'package:notas/models/note.dart'; import 'package:notas/models/note.dart';
import 'package:notas/platform/app_platform.dart'; import 'package:notas/platform/app_platform.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
import 'package:notas/widgets/category_style.dart'; import 'package:notas/widgets/category_style.dart';
// NoteEditorScreen: unified UI for creating and editing notes. // NoteEditorScreen: unified UI for creating and editing notes.
@@ -42,7 +42,7 @@ class NoteEditorScreen extends StatefulWidget {
return showGeneralDialog<dynamic>( return showGeneralDialog<dynamic>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
barrierColor: AppColors.transparent, barrierColor: Colors.transparent,
transitionDuration: const Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animation, secondaryAnimation) { pageBuilder: (context, animation, secondaryAnimation) {
return NoteEditorScreen( return NoteEditorScreen(
@@ -120,6 +120,11 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
bool get _isMobileLayout => isAndroid || isIOS; bool get _isMobileLayout => isAndroid || isIOS;
AppPalette _paletteOf(BuildContext context) {
return Theme.of(context).extension<AppPalette>() ??
AppPalette.fromBrightness(Theme.of(context).brightness);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -200,36 +205,37 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
required ValueChanged<bool> onConfirmed, required ValueChanged<bool> onConfirmed,
}) { }) {
final bool isDeletedNote = _currentNote.isDeleted; final bool isDeletedNote = _currentNote.isDeleted;
final AppPalette palette = _paletteOf(context);
return AlertDialog( return AlertDialog(
backgroundColor: AppColors.cardBackground, backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border), side: BorderSide(color: palette.border),
), ),
title: Text( title: Text(
isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota', isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota',
style: const TextStyle(color: AppColors.textPrimary), style: TextStyle(color: palette.textPrimary),
), ),
content: Text( content: Text(
isDeletedNote isDeletedNote
? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.' ? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.'
: '¿Estás seguro de que deseas eliminar esta nota?', : '¿Estás seguro de que deseas eliminar esta nota?',
style: const TextStyle(color: AppColors.textSecondary), style: TextStyle(color: palette.textSecondary),
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => onConfirmed(false), onPressed: () => onConfirmed(false),
child: const Text( child: Text(
'Cancelar', 'Cancelar',
style: TextStyle(color: AppColors.textSecondary), style: TextStyle(color: palette.textSecondary),
), ),
), ),
TextButton( TextButton(
onPressed: () => onConfirmed(true), onPressed: () => onConfirmed(true),
child: Text( child: Text(
isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar', isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar',
style: const TextStyle(color: AppColors.destructive), style: TextStyle(color: palette.destructiveAccent),
), ),
), ),
], ],
@@ -241,7 +247,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
final bool? confirmed = await showDialog<bool>( final bool? confirmed = await showDialog<bool>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
barrierColor: AppColors.transparent, barrierColor: Colors.transparent,
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
return _buildDeleteConfirmationDialog( return _buildDeleteConfirmationDialog(
onConfirmed: (bool confirmed) => onConfirmed: (bool confirmed) =>
@@ -258,7 +264,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
final bool? confirmed = await showDialog<bool>( final bool? confirmed = await showDialog<bool>(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
barrierColor: AppColors.transparent, barrierColor: Colors.transparent,
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
return _buildDeleteConfirmationDialog( return _buildDeleteConfirmationDialog(
onConfirmed: (bool confirmed) => onConfirmed: (bool confirmed) =>
@@ -287,14 +293,11 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
}; };
return Material( return Material(
color: AppColors.transparent, color: Colors.transparent,
child: Stack( child: Stack(
children: [ children: [
const Positioned.fill( const Positioned.fill(
child: ModalBarrier( child: ModalBarrier(dismissible: false, color: Colors.black54),
dismissible: false,
color: AppColors.overlay,
),
), ),
Center( Center(
child: ConstrainedBox( child: ConstrainedBox(
@@ -323,25 +326,30 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
} }
Color _categoryBackgroundColor(Category? category) { Color _categoryBackgroundColor(Category? category) {
final AppPalette palette = _paletteOf(context);
if (category?.colorValue == null) { if (category?.colorValue == null) {
return AppColors.borderMuted; return palette.borderMuted;
} }
return Color(category!.colorValue!); return Color(category!.colorValue!);
} }
Color _categoryForegroundColor(Category? category) { Color _categoryForegroundColor(Category? category) {
final AppPalette palette = _paletteOf(context);
if (category == null || category.colorValue == null) { if (category == null || category.colorValue == null) {
return AppColors.textPrimary; return palette.textPrimary;
} }
final Color background = Color(category.colorValue!); final Color background = Color(category.colorValue!);
return background.computeLuminance() > 0.55 return background.computeLuminance() > 0.55
? AppColors.textOnSurfaceDark ? palette.textOnSurfaceDark
: AppColors.textPrimary; : palette.textPrimary;
} }
Widget _buildCategorySelectorBox({Category? category}) { Widget _buildCategorySelectorBox({Category? category}) {
final AppPalette palette = _paletteOf(context);
final String label = category?.name ?? 'Sin categoría'; final String label = category?.name ?? 'Sin categoría';
final IconData icon = CategoryStyle.iconForCodePoint( final IconData icon = CategoryStyle.iconForCodePoint(
category?.iconCodePoint, category?.iconCodePoint,
@@ -358,7 +366,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
border: Border.all( border: Border.all(
color: category?.colorValue != null color: category?.colorValue != null
? backgroundColor.withValues(alpha: 0.85) ? backgroundColor.withValues(alpha: 0.85)
: AppColors.textDisabled, : palette.textDisabled,
), ),
), ),
child: Row( child: Row(
@@ -409,12 +417,13 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
_categoryMenuEntry = OverlayEntry( _categoryMenuEntry = OverlayEntry(
builder: (BuildContext overlayContext) { builder: (BuildContext overlayContext) {
final AppPalette palette = _paletteOf(overlayContext);
final Size screenSize = MediaQuery.of(overlayContext).size; final Size screenSize = MediaQuery.of(overlayContext).size;
final double menuWidth = math.min(screenSize.width - 32, 320); final double menuWidth = math.min(screenSize.width - 32, 320);
final double menuHeight = math.min(screenSize.height - 32, 360); final double menuHeight = math.min(screenSize.height - 32, 360);
return Material( return Material(
color: AppColors.transparent, color: Colors.transparent,
child: Stack( child: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
@@ -432,7 +441,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
), ),
child: Material( child: Material(
elevation: 10, elevation: 10,
color: AppColors.surfaceElevated, color: palette.surfaceElevated,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: ListView( child: ListView(
@@ -482,6 +491,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
}) { }) {
final AppPalette palette = _paletteOf(context);
final Color backgroundColor = _categoryBackgroundColor(category); final Color backgroundColor = _categoryBackgroundColor(category);
final Color foregroundColor = _categoryForegroundColor(category); final Color foregroundColor = _categoryForegroundColor(category);
final IconData icon = CategoryStyle.iconForCodePoint( final IconData icon = CategoryStyle.iconForCodePoint(
@@ -492,7 +502,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
onTap: onTap, onTap: onTap,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
color: isSelected ? AppColors.hover : null, color: isSelected ? palette.hover : null,
child: Row( child: Row(
children: [ children: [
Container( Container(
@@ -504,7 +514,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
border: Border.all( border: Border.all(
color: category?.colorValue != null color: category?.colorValue != null
? backgroundColor.withValues(alpha: 0.85) ? backgroundColor.withValues(alpha: 0.85)
: AppColors.textDisabled, : palette.textDisabled,
), ),
), ),
child: Icon(icon, size: 16, color: foregroundColor), child: Icon(icon, size: 16, color: foregroundColor),
@@ -553,6 +563,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
} }
Widget _buildEditorContent({required bool isMobile}) { Widget _buildEditorContent({required bool isMobile}) {
final AppPalette palette = _paletteOf(context);
final double titleSpacing = isMobile ? 16.0 : 8.0; final double titleSpacing = isMobile ? 16.0 : 8.0;
return Column( return Column(
@@ -560,15 +571,13 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(bottom: BorderSide(color: palette.border, width: 1)),
bottom: BorderSide(color: AppColors.border, width: 1),
),
), ),
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
onPressed: _closeWithoutSaving, onPressed: _closeWithoutSaving,
icon: const Icon(Icons.close, color: AppColors.textSecondary), icon: Icon(Icons.close, color: palette.textSecondary),
tooltip: 'Cerrar sin guardar', tooltip: 'Cerrar sin guardar',
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -579,23 +588,17 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
children: [ children: [
Text( Text(
'Posicion: ${_currentNote.position}', 'Posicion: ${_currentNote.position}',
style: const TextStyle( style: TextStyle(color: palette.textMuted, fontSize: 12),
color: AppColors.textMuted,
fontSize: 12,
),
), ),
Text( Text(
'Creado: ${_formatDate(_currentNote.createdAt)}', 'Creado: ${_formatDate(_currentNote.createdAt)}',
style: const TextStyle( style: TextStyle(color: palette.textMuted, fontSize: 12),
color: AppColors.textMuted,
fontSize: 12,
),
), ),
if (_currentNote.updatedAt != _currentNote.createdAt) if (_currentNote.updatedAt != _currentNote.createdAt)
Text( Text(
'Modificado: ${_formatDate(_currentNote.updatedAt)}', 'Modificado: ${_formatDate(_currentNote.updatedAt)}',
style: const TextStyle( style: TextStyle(
color: AppColors.textMuted, color: palette.textMuted,
fontSize: 12, fontSize: 12,
), ),
), ),
@@ -629,14 +632,14 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
children: [ children: [
TextField( TextField(
controller: _titleController, controller: _titleController,
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'Título', hintText: 'Título',
hintStyle: TextStyle(color: AppColors.textHint), hintStyle: TextStyle(color: palette.textHint),
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
), ),
@@ -648,14 +651,14 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: null, maxLines: null,
expands: true, expands: true,
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 16, fontSize: 16,
height: 1.6, height: 1.6,
), ),
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'Escribe tu nota...', hintText: 'Escribe tu nota...',
hintStyle: TextStyle(color: AppColors.textHint), hintStyle: TextStyle(color: palette.textHint),
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
), ),
@@ -668,7 +671,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border(top: BorderSide(color: AppColors.border, width: 1)), border: Border(top: BorderSide(color: palette.border, width: 1)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -676,9 +679,9 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
if (!_isNewNote) if (!_isNewNote)
IconButton( IconButton(
onPressed: _deleteNote, onPressed: _deleteNote,
icon: const Icon( icon: Icon(
Icons.delete_outline, Icons.delete_outline,
color: AppColors.destructive, color: palette.destructiveAccent,
), ),
tooltip: 'Eliminar nota', tooltip: 'Eliminar nota',
) )
@@ -694,12 +697,14 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = _paletteOf(context);
if (_isMobileLayout) { if (_isMobileLayout) {
return Material( return Material(
color: AppColors.transparent, color: palette.transparent,
child: SafeArea( child: SafeArea(
child: Container( child: Container(
color: AppColors.cardBackground, color: palette.cardBackground,
child: _buildEditorContent(isMobile: true), child: _buildEditorContent(isMobile: true),
), ),
), ),
@@ -714,10 +719,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
return Stack( return Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: ModalBarrier( child: ModalBarrier(dismissible: false, color: palette.shadowDim),
dismissible: false,
color: AppColors.shadowDim,
),
), ),
Positioned.fill( Positioned.fill(
child: Center( child: Center(
@@ -725,10 +727,10 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
child: Material( child: Material(
color: AppColors.cardBackground, color: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
side: BorderSide(color: AppColors.textDisabled, width: 1), side: BorderSide(color: palette.textDisabled, width: 1),
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: _buildEditorContent(isMobile: false), child: _buildEditorContent(isMobile: false),
+120 -51
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/data/local_vault_service.dart'; import 'package:notas/data/local_vault_service.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
import 'package:notas/widgets/search_app_bar.dart'; import 'package:notas/widgets/search_app_bar.dart';
import 'package:notas/data/api_client.dart'; import 'package:notas/data/api_client.dart';
@@ -12,6 +12,8 @@ class SettingsScreen extends StatefulWidget {
required this.onForceSync, required this.onForceSync,
required this.currentSeedColor, required this.currentSeedColor,
required this.onThemeColorSelected, required this.onThemeColorSelected,
required this.currentThemeMode,
required this.onThemeModeSelected,
}); });
final Future<void> Function() onDeleteAllData; final Future<void> Function() onDeleteAllData;
@@ -19,6 +21,8 @@ class SettingsScreen extends StatefulWidget {
final Future<void> Function() onForceSync; final Future<void> Function() onForceSync;
final Color currentSeedColor; final Color currentSeedColor;
final Future<void> Function(Color color) onThemeColorSelected; final Future<void> Function(Color color) onThemeColorSelected;
final ThemeMode currentThemeMode;
final Future<void> Function(ThemeMode mode) onThemeModeSelected;
@override @override
State<SettingsScreen> createState() => _SettingsScreenState(); State<SettingsScreen> createState() => _SettingsScreenState();
@@ -36,17 +40,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
bool _encryptionKeyLoading = false; bool _encryptionKeyLoading = false;
bool _encryptionKeyVisible = false; bool _encryptionKeyVisible = false;
late Color _selectedSeedColor; late Color _selectedSeedColor;
late ThemeMode _selectedThemeMode;
static const List<Color> _themeColorOptions = AppColors.themeSeedColors; static const List<Color> _themeColorOptions = AppPalette.themeSeedColors;
Future<void> _confirmAndDeleteAll() async { Future<void> _confirmAndDeleteAll() async {
final bool? confirmed = await showDialog<bool>( final bool? confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) {
backgroundColor: AppColors.cardBackground, final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border), side: BorderSide(color: palette.border),
), ),
title: const Text('Borrar todos los datos'), title: const Text('Borrar todos los datos'),
content: const Text( content: const Text(
@@ -59,13 +66,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: const Text( child: Text(
'Borrar', 'Borrar',
style: TextStyle(color: AppColors.destructive), style: TextStyle(color: palette.destructiveAccent),
), ),
), ),
], ],
), );
},
); );
if (confirmed != true) return; if (confirmed != true) return;
@@ -100,11 +108,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
Future<void> _confirmAndDeleteServerData() async { Future<void> _confirmAndDeleteServerData() async {
final bool? confirmed = await showDialog<bool>( final bool? confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) {
backgroundColor: AppColors.cardBackground, final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border), side: BorderSide(color: palette.border),
), ),
title: const Text('Borrar toda la info del servidor'), title: const Text('Borrar toda la info del servidor'),
content: const Text( content: const Text(
@@ -117,13 +127,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(true), onPressed: () => Navigator.of(context).pop(true),
child: const Text( child: Text(
'Borrar', 'Borrar',
style: TextStyle(color: AppColors.destructive), style: TextStyle(color: palette.destructiveAccent),
), ),
), ),
], ],
), );
},
); );
if (confirmed != true) return; if (confirmed != true) return;
@@ -234,6 +245,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_selectedSeedColor = widget.currentSeedColor; _selectedSeedColor = widget.currentSeedColor;
_selectedThemeMode = widget.currentThemeMode;
_loadEndpoint(); _loadEndpoint();
} }
@@ -244,6 +256,26 @@ class _SettingsScreenState extends State<SettingsScreen> {
widget.currentSeedColor != _selectedSeedColor) { widget.currentSeedColor != _selectedSeedColor) {
_selectedSeedColor = widget.currentSeedColor; _selectedSeedColor = widget.currentSeedColor;
} }
if (oldWidget.currentThemeMode != widget.currentThemeMode) {
_selectedThemeMode = widget.currentThemeMode;
}
}
Future<void> _selectThemeMode(ThemeMode mode) async {
if (_selectedThemeMode == mode) return;
setState(() {
_selectedThemeMode = mode;
});
try {
await widget.onThemeModeSelected(mode);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('No se pudo guardar la preferencia de tema: $e'),
),
);
}
} }
Future<void> _loadEndpoint() async { Future<void> _loadEndpoint() async {
@@ -307,10 +339,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
required bool isLoading, required bool isLoading,
required IconData icon, required IconData icon,
}) { }) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return ElevatedButton.icon( return ElevatedButton.icon(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.destructiveAccent, backgroundColor: palette.destructiveAccent,
foregroundColor: AppColors.textPrimary, foregroundColor: palette.textPrimary,
textStyle: const TextStyle(fontWeight: FontWeight.w600), textStyle: const TextStyle(fontWeight: FontWeight.w600),
), ),
onPressed: onPressed, onPressed: onPressed,
@@ -326,11 +360,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
Widget _buildThemeColorButton(Color color) { Widget _buildThemeColorButton(Color color) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool isSelected = _selectedSeedColor.value == color.value; final bool isSelected = _selectedSeedColor.value == color.value;
final Color foregroundColor = final Color foregroundColor =
ThemeData.estimateBrightnessForColor(color) == Brightness.dark ThemeData.estimateBrightnessForColor(color) == Brightness.dark
? AppColors.textPrimary ? palette.textPrimary
: AppColors.textOnAccent; : palette.textOnAccent;
return Semantics( return Semantics(
button: true, button: true,
@@ -349,14 +384,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
color: color, color: color,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: isSelected color: isSelected ? palette.textPrimary : palette.textSecondary,
? AppColors.textPrimary
: AppColors.textDisabled,
width: isSelected ? 2.5 : 1.2, width: isSelected ? 2.5 : 1.2,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.shadowSoft, color: palette.shadowSoft,
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 3), offset: const Offset(0, 3),
), ),
@@ -458,9 +491,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient), decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -472,6 +507,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
titleText: 'Configuración', titleText: 'Configuración',
), ),
Expanded( Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
@@ -479,7 +516,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [ children: [
Row( Row(
children: [ children: [
const Expanded(child: Text('Borrar datos locales:')), const Expanded(
child: Text('Borrar datos locales:'),
),
_buildDestructiveButton( _buildDestructiveButton(
label: 'Borrar', label: 'Borrar',
onPressed: (_isBusy || _isServerDeleting) onPressed: (_isBusy || _isServerDeleting)
@@ -532,9 +571,39 @@ class _SettingsScreenState extends State<SettingsScreen> {
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
const Text('Apariencia'),
const SizedBox(height: 8),
Column(
children: [
RadioListTile<ThemeMode>(
title: const Text('Seguir modo del sistema'),
value: ThemeMode.system,
groupValue: _selectedThemeMode,
onChanged: (ThemeMode? v) =>
_selectThemeMode(ThemeMode.system),
),
RadioListTile<ThemeMode>(
title: const Text('Modo claro'),
value: ThemeMode.light,
groupValue: _selectedThemeMode,
onChanged: (ThemeMode? v) =>
_selectThemeMode(ThemeMode.light),
),
RadioListTile<ThemeMode>(
title: const Text('Modo oscuro'),
value: ThemeMode.dark,
groupValue: _selectedThemeMode,
onChanged: (ThemeMode? v) =>
_selectThemeMode(ThemeMode.dark),
),
],
),
const SizedBox(height: 16),
const Text('Color del esquema'), const Text('Color del esquema'),
const SizedBox(height: 8), const SizedBox(height: 8),
Wrap( Padding(
padding: const EdgeInsets.only(left: 4),
child: Wrap(
spacing: 12, spacing: 12,
runSpacing: 12, runSpacing: 12,
children: [ children: [
@@ -542,6 +611,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_buildThemeColorButton(color), _buildThemeColorButton(color),
], ],
), ),
),
const SizedBox(height: 24), const SizedBox(height: 24),
const Text( const Text(
'API endpoint (ej: https://notas-api.lpncnd.es/api)', 'API endpoint (ej: https://notas-api.lpncnd.es/api)',
@@ -557,33 +627,31 @@ class _SettingsScreenState extends State<SettingsScreen> {
) )
: TextField( : TextField(
controller: _endpointController, controller: _endpointController,
style: const TextStyle( style: TextStyle(color: palette.textPrimary),
color: AppColors.textPrimary,
),
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'API endpoint', labelText: 'API endpoint',
labelStyle: const TextStyle( labelStyle: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
), ),
filled: true, filled: true,
fillColor: AppColors.fill, fillColor: palette.fill,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.border, color: palette.border,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.border, color: palette.border,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.accent, color: palette.accent,
width: 1.2, width: 1.2,
), ),
), ),
@@ -591,11 +659,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
actions: [ actions: [
ElevatedButton( ElevatedButton(
onPressed: _endpointLoading ? null : _saveEndpoint, onPressed: _endpointLoading
? null
: _saveEndpoint,
child: const Text('Guardar'), child: const Text('Guardar'),
), ),
OutlinedButton( OutlinedButton(
onPressed: _endpointLoading ? null : _resetEndpoint, onPressed: _endpointLoading
? null
: _resetEndpoint,
child: const Text('Restaurar'), child: const Text('Restaurar'),
), ),
], ],
@@ -610,32 +682,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
obscureText: !_encryptionKeyVisible, obscureText: !_encryptionKeyVisible,
enableSuggestions: false, enableSuggestions: false,
autocorrect: false, autocorrect: false,
style: const TextStyle(color: AppColors.textPrimary), style: TextStyle(color: palette.textPrimary),
decoration: InputDecoration( decoration: InputDecoration(
labelText: _encryptionKeyVisible labelText: _encryptionKeyVisible
? 'Clave de cifrado' ? 'Clave de cifrado'
: 'Oculta hasta pulsar mostrar', : 'Oculta hasta pulsar mostrar',
labelStyle: const TextStyle( labelStyle: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
), ),
filled: true, filled: true,
fillColor: AppColors.fill, fillColor: palette.fill,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(color: palette.border),
color: AppColors.border,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(color: palette.border),
color: AppColors.border,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.accent, color: palette.accent,
width: 1.2, width: 1.2,
), ),
), ),
@@ -668,6 +736,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
), ),
), ),
),
], ],
), ),
), ),
+38 -52
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/data/api_client.dart'; import 'package:notas/data/api_client.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class VaultAccessScreen extends StatefulWidget { class VaultAccessScreen extends StatefulWidget {
const VaultAccessScreen({ const VaultAccessScreen({
@@ -75,9 +75,11 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient), decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -90,12 +92,12 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
child: Container( child: Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.surface, color: palette.cardBackground,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderMuted), border: Border.all(color: palette.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.shadow, color: palette.shadowSoft,
blurRadius: 30, blurRadius: 30,
offset: const Offset(0, 18), offset: const Offset(0, 18),
), ),
@@ -107,7 +109,7 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
children: [ children: [
const Icon( const Icon(
Icons.lock_outline, Icons.lock_outline,
color: AppColors.accent, color: Colors.white,
size: 44, size: 44,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -115,7 +117,7 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
'Mis Notas', 'Mis Notas',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: Colors.white,
fontSize: 30, fontSize: 30,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -125,7 +127,7 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
'Tus notas se guardan cifradas en este dispositivo. La cuenta y la sincronización vendrán después.', 'Tus notas se guardan cifradas en este dispositivo. La cuenta y la sincronización vendrán después.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
height: 1.4, height: 1.4,
), ),
), ),
@@ -141,32 +143,30 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
controller: _endpointController, controller: _endpointController,
enabled: !widget.isBusy, enabled: !widget.isBusy,
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
style: const TextStyle( style: const TextStyle(color: Colors.white),
color: AppColors.textPrimary,
),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'API endpoint', labelText: 'API endpoint',
labelStyle: const TextStyle( labelStyle: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
), ),
filled: true, filled: true,
fillColor: AppColors.fill, fillColor: palette.fill,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.border, color: palette.border,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.border, color: palette.border,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.accent, color: palette.accent,
width: 1.2, width: 1.2,
), ),
), ),
@@ -177,32 +177,26 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
controller: _emailController, controller: _emailController,
enabled: !widget.isBusy, enabled: !widget.isBusy,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
style: const TextStyle( style: const TextStyle(color: Colors.white),
color: AppColors.textPrimary,
),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Usuario', labelText: 'Usuario',
labelStyle: const TextStyle( labelStyle: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
), ),
filled: true, filled: true,
fillColor: AppColors.fill, fillColor: palette.fill,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(color: palette.border),
color: AppColors.border,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(color: palette.border),
color: AppColors.border,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.accent, color: palette.accent,
width: 1.2, width: 1.2,
), ),
), ),
@@ -213,32 +207,26 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
controller: _passwordController, controller: _passwordController,
enabled: !widget.isBusy, enabled: !widget.isBusy,
obscureText: true, obscureText: true,
style: const TextStyle( style: const TextStyle(color: Colors.white),
color: AppColors.textPrimary,
),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Contraseña', labelText: 'Contraseña',
labelStyle: const TextStyle( labelStyle: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
), ),
filled: true, filled: true,
fillColor: AppColors.fill, fillColor: palette.fill,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(color: palette.border),
color: AppColors.border,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(color: palette.border),
color: AppColors.border,
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColors.accent, color: palette.accent,
width: 1.2, width: 1.2,
), ),
), ),
@@ -271,10 +259,8 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 14, vertical: 14,
), ),
side: const BorderSide( side: BorderSide(color: palette.border),
color: AppColors.textDisabled, foregroundColor: palette.textPrimary,
),
foregroundColor: AppColors.textPrimary,
), ),
child: const Text('Iniciar sesión'), child: const Text('Iniciar sesión'),
), ),
-78
View File
@@ -1,78 +0,0 @@
import 'package:flutter/material.dart';
class AppColors {
AppColors._();
static const Color defaultThemeSeedColor = Colors.amber;
static const Color scaffoldBackground = Color.fromRGBO(31, 32, 33, 1);
static const Color backdropStart = Color(0xFF191A1D);
static const Color backdropMid = Color(0xFF222326);
static const Color backdropEnd = Color(0xFF101114);
static const LinearGradient backdropGradient = LinearGradient(
colors: <Color>[backdropStart, backdropMid, backdropEnd],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const Color surface = Color(0xFF1D1E20);
static const Color surfaceElevated = Color(0xFF303134);
static const Color drawerBackground = Color.fromARGB(255, 30, 31, 35);
static const Color cardBackground = Color.fromRGBO(24, 25, 26, 1);
static const Color overlay = Color.fromARGB(140, 0, 0, 0);
static const Color shadow = Color.fromRGBO(0, 0, 0, 0.35);
static const Color transparent = Colors.transparent;
static const Color accent = Colors.amber;
static const Color destructive = Colors.red;
static const Color destructiveAccent = Colors.redAccent;
static const Color success = Colors.green;
static const Color textOnAccent = Colors.black;
static const Color textOnSurfaceDark = Colors.black87;
static const Color textPrimary = Colors.white;
static const Color textSecondary = Colors.white70;
static const Color textMuted = Colors.white54;
static const Color textSubtle = Colors.white38;
static const Color textDisabled = Colors.white24;
static const Color textHint = Color.fromRGBO(255, 255, 255, 0.30);
static const Color borderStrong = Color.fromRGBO(255, 255, 255, 0.20);
static const Color border = Color.fromRGBO(255, 255, 255, 0.12);
static const Color borderMuted = Color.fromRGBO(255, 255, 255, 0.08);
static const Color fill = Color.fromRGBO(255, 255, 255, 0.05);
static const Color hover = Color.fromRGBO(255, 255, 255, 0.10);
static const Color searchFocusBorder = Color.fromRGBO(255, 255, 255, 0.40);
static const Color shadowSoft = Color.fromRGBO(0, 0, 0, 0.25);
static const Color shadowDim = Color.fromARGB(54, 0, 0, 0);
static const Color categoryFallback = Color(0xFFFFC107);
static const List<Color> categoryColors = <Color>[
Colors.amber,
Colors.blue,
Colors.green,
Colors.purple,
Colors.red,
Colors.teal,
Colors.orange,
Colors.grey,
];
static const List<Color> themeSeedColors = <Color>[
Colors.amber,
Colors.blue,
Colors.teal,
Colors.green,
Colors.pink,
Colors.purple,
];
static const Color dragTargetBorder = Color(0xFF42A5F5);
static const Color syncPreparing = Color.fromARGB(255, 165, 165, 165);
static const Color syncEncrypting = Color.fromARGB(255, 109, 191, 255);
static const Color syncUploading = Color.fromARGB(255, 98, 190, 255);
static const Color syncWaiting = Color.fromARGB(255, 150, 150, 150);
static const Color syncDecrypting = Color.fromARGB(255, 154, 194, 112);
}
+321
View File
@@ -0,0 +1,321 @@
import 'package:flutter/material.dart';
@immutable
class AppPalette extends ThemeExtension<AppPalette> {
static const Color darkDefaultThemeSeedColor = Colors.amber;
static const Color lightDefaultThemeSeedColor = Colors.blue;
static const List<Color> defaultCategoryColors = <Color>[
Colors.amber,
Colors.blue,
Colors.green,
Colors.purple,
Colors.red,
Colors.teal,
Colors.orange,
Colors.grey,
];
static const List<Color> themeSeedColors = <Color>[
Colors.blue,
Colors.teal,
Colors.green,
Colors.pink,
Colors.purple,
Colors.orange,
];
static const Gradient _darkBackdropGradient = LinearGradient(
colors: <Color>[Color(0xFF191A1D), Color(0xFF222326), Color(0xFF101114)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const Gradient _lightBackdropGradient = LinearGradient(
colors: <Color>[Color(0xFFFFFFFF), Color(0xFFF2F4F6), Color(0xFFECEFF1)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const Color _darkDrawerBackground = Color.fromARGB(255, 30, 31, 35);
static const Color _lightDrawerBackground = Color(0xFFF7F9FA);
static const Color _darkCardBackground = Color.fromRGBO(24, 25, 26, 1);
static const Color _lightCardBackground = Color(0xFFFFFFFF);
static const Color _darkSurfaceElevated = Color(0xFF303134);
static const Color _lightSurfaceElevated = Color(0xFFF0F2F4);
static const Color _darkBorder = Color.fromRGBO(255, 255, 255, 0.12);
static const Color _lightBorder = Color.fromRGBO(15, 23, 32, 0.06);
static const Color _darkBorderMuted = Color.fromRGBO(255, 255, 255, 0.08);
static const Color _lightBorderMuted = Color.fromRGBO(15, 23, 32, 0.03);
static const Color _darkTextPrimary = Colors.white;
static const Color _lightTextPrimary = Color(0xFF0F1720);
static const Color _darkTextSecondary = Colors.white70;
static const Color _lightTextSecondary = Color(0xFF374151);
static const Color _darkTextOnSurfaceDark = Colors.black87;
static const Color _lightTextOnSurfaceDark = Colors.black87;
static const Color _darkTextMuted = Colors.white54;
static const Color _lightTextMuted = Color(0xFF6B7280);
static const Color _darkTextDisabled = Colors.white24;
static const Color _lightTextDisabled = Color(0xFFBDBDBD);
static const Color _darkTextHint = Color.fromRGBO(255, 255, 255, 0.30);
static const Color _lightTextHint = Color.fromRGBO(15, 23, 32, 0.30);
static const Color _darkFill = Color.fromRGBO(255, 255, 255, 0.05);
static const Color _lightFill = Color.fromRGBO(15, 23, 32, 0.02);
static const Color _darkHover = Color.fromRGBO(255, 255, 255, 0.10);
static const Color _lightHover = Color.fromRGBO(15, 23, 32, 0.04);
static const Color _darkShadowSoft = Color.fromRGBO(0, 0, 0, 0.25);
static const Color _lightShadowSoft = Color.fromRGBO(0, 0, 0, 0.08);
static const Color _darkShadowDim = Color.fromARGB(54, 0, 0, 0);
static const Color _lightShadowDim = Color.fromARGB(40, 0, 0, 0);
static const Color _darkAccent = Colors.amber;
static const Color _lightAccent = Colors.blue;
static const Color _darkTextOnAccent = Colors.black;
static const Color _lightTextOnAccent = Colors.white;
static const Color _darkOverlay = Color.fromARGB(140, 0, 0, 0);
static const Color _lightOverlay = Color.fromARGB(120, 0, 0, 0);
static const Color _transparent = Colors.transparent;
static const Color _darkDragTargetBorder = Color(0xFF42A5F5);
static const Color _lightDragTargetBorder = Color(0xFF1565C0);
static const Color _darkSyncPreparing = Color.fromARGB(255, 165, 165, 165);
static const Color _lightSyncPreparing = Color.fromARGB(255, 120, 120, 120);
static const Color _darkSyncEncrypting = Color.fromARGB(255, 109, 191, 255);
static const Color _lightSyncEncrypting = Color.fromARGB(255, 2, 136, 209);
static const Color _darkSyncUploading = Color.fromARGB(255, 98, 190, 255);
static const Color _lightSyncUploading = Color.fromARGB(255, 2, 119, 189);
static const Color _darkSyncWaiting = Color.fromARGB(255, 150, 150, 150);
static const Color _lightSyncWaiting = Color.fromARGB(255, 150, 150, 150);
static const Color _darkSyncDecrypting = Color.fromARGB(255, 154, 194, 112);
static const Color _lightSyncDecrypting = Color.fromARGB(255, 76, 175, 80);
static const Color _darkSuccess = Colors.green;
static const Color _lightSuccess = Colors.green;
const AppPalette({
required this.backdropGradient,
required this.drawerBackground,
required this.cardBackground,
required this.categoryColors,
required this.surfaceElevated,
required this.border,
required this.borderMuted,
required this.accent,
required this.textPrimary,
required this.textSecondary,
required this.textOnSurfaceDark,
required this.textMuted,
required this.textDisabled,
required this.textHint,
required this.fill,
required this.hover,
required this.shadowSoft,
required this.shadowDim,
required this.destructiveAccent,
required this.textOnAccent,
required this.overlay,
required this.transparent,
required this.dragTargetBorder,
required this.syncPreparing,
required this.syncEncrypting,
required this.syncUploading,
required this.syncWaiting,
required this.syncDecrypting,
required this.success,
});
final Gradient backdropGradient;
final Color drawerBackground;
final Color cardBackground;
final List<Color> categoryColors;
final Color surfaceElevated;
final Color border;
final Color borderMuted;
final Color accent;
final Color textPrimary;
final Color textSecondary;
final Color textOnSurfaceDark;
final Color textMuted;
final Color textDisabled;
final Color textHint;
final Color fill;
final Color hover;
final Color shadowSoft;
final Color shadowDim;
final Color destructiveAccent;
final Color textOnAccent;
final Color overlay;
final Color transparent;
final Color dragTargetBorder;
final Color syncPreparing;
final Color syncEncrypting;
final Color syncUploading;
final Color syncWaiting;
final Color syncDecrypting;
final Color success;
@override
AppPalette copyWith({
Gradient? backdropGradient,
Color? drawerBackground,
Color? cardBackground,
List<Color>? categoryColors,
Color? surfaceElevated,
Color? border,
Color? borderMuted,
Color? accent,
Color? textPrimary,
Color? textSecondary,
Color? textOnSurfaceDark,
Color? textMuted,
Color? textDisabled,
Color? textHint,
Color? fill,
Color? hover,
Color? shadowSoft,
Color? shadowDim,
Color? destructiveAccent,
Color? textOnAccent,
Color? overlay,
Color? transparent,
Color? dragTargetBorder,
Color? syncPreparing,
Color? syncEncrypting,
Color? syncUploading,
Color? syncWaiting,
Color? syncDecrypting,
Color? success,
}) {
return AppPalette(
backdropGradient: backdropGradient ?? this.backdropGradient,
drawerBackground: drawerBackground ?? this.drawerBackground,
cardBackground: cardBackground ?? this.cardBackground,
categoryColors: categoryColors ?? this.categoryColors,
surfaceElevated: surfaceElevated ?? this.surfaceElevated,
border: border ?? this.border,
borderMuted: borderMuted ?? this.borderMuted,
accent: accent ?? this.accent,
textPrimary: textPrimary ?? this.textPrimary,
textSecondary: textSecondary ?? this.textSecondary,
textOnSurfaceDark: textOnSurfaceDark ?? this.textOnSurfaceDark,
textMuted: textMuted ?? this.textMuted,
textDisabled: textDisabled ?? this.textDisabled,
textHint: textHint ?? this.textHint,
fill: fill ?? this.fill,
hover: hover ?? this.hover,
shadowSoft: shadowSoft ?? this.shadowSoft,
shadowDim: shadowDim ?? this.shadowDim,
destructiveAccent: destructiveAccent ?? this.destructiveAccent,
textOnAccent: textOnAccent ?? this.textOnAccent,
overlay: overlay ?? this.overlay,
transparent: transparent ?? this.transparent,
dragTargetBorder: dragTargetBorder ?? this.dragTargetBorder,
syncPreparing: syncPreparing ?? this.syncPreparing,
syncEncrypting: syncEncrypting ?? this.syncEncrypting,
syncUploading: syncUploading ?? this.syncUploading,
syncWaiting: syncWaiting ?? this.syncWaiting,
syncDecrypting: syncDecrypting ?? this.syncDecrypting,
success: success ?? this.success,
);
}
@override
AppPalette lerp(ThemeExtension<AppPalette>? other, double t) {
if (other is! AppPalette) return this;
return AppPalette(
backdropGradient:
LinearGradient.lerp(
backdropGradient as LinearGradient,
other.backdropGradient as LinearGradient,
t,
) ??
backdropGradient,
drawerBackground:
Color.lerp(drawerBackground, other.drawerBackground, t) ??
drawerBackground,
cardBackground:
Color.lerp(cardBackground, other.cardBackground, t) ?? cardBackground,
categoryColors: t < 0.5 ? categoryColors : other.categoryColors,
surfaceElevated:
Color.lerp(surfaceElevated, other.surfaceElevated, t) ??
surfaceElevated,
border: Color.lerp(border, other.border, t) ?? border,
borderMuted: Color.lerp(borderMuted, other.borderMuted, t) ?? borderMuted,
accent: Color.lerp(accent, other.accent, t) ?? accent,
textPrimary: Color.lerp(textPrimary, other.textPrimary, t) ?? textPrimary,
textSecondary:
Color.lerp(textSecondary, other.textSecondary, t) ?? textSecondary,
textOnSurfaceDark:
Color.lerp(textOnSurfaceDark, other.textOnSurfaceDark, t) ??
textOnSurfaceDark,
textMuted: Color.lerp(textMuted, other.textMuted, t) ?? textMuted,
textDisabled:
Color.lerp(textDisabled, other.textDisabled, t) ?? textDisabled,
textHint: Color.lerp(textHint, other.textHint, t) ?? textHint,
fill: Color.lerp(fill, other.fill, t) ?? fill,
hover: Color.lerp(hover, other.hover, t) ?? hover,
shadowSoft: Color.lerp(shadowSoft, other.shadowSoft, t) ?? shadowSoft,
shadowDim: Color.lerp(shadowDim, other.shadowDim, t) ?? shadowDim,
destructiveAccent:
Color.lerp(destructiveAccent, other.destructiveAccent, t) ??
destructiveAccent,
textOnAccent:
Color.lerp(textOnAccent, other.textOnAccent, t) ?? textOnAccent,
overlay: Color.lerp(overlay, other.overlay, t) ?? overlay,
transparent: Color.lerp(transparent, other.transparent, t) ?? transparent,
dragTargetBorder:
Color.lerp(dragTargetBorder, other.dragTargetBorder, t) ??
dragTargetBorder,
syncPreparing:
Color.lerp(syncPreparing, other.syncPreparing, t) ?? syncPreparing,
syncEncrypting:
Color.lerp(syncEncrypting, other.syncEncrypting, t) ?? syncEncrypting,
syncUploading:
Color.lerp(syncUploading, other.syncUploading, t) ?? syncUploading,
syncWaiting: Color.lerp(syncWaiting, other.syncWaiting, t) ?? syncWaiting,
syncDecrypting:
Color.lerp(syncDecrypting, other.syncDecrypting, t) ?? syncDecrypting,
success: Color.lerp(success, other.success, t) ?? success,
);
}
static AppPalette fromBrightness(Brightness brightness, {Color? seedColor}) {
final bool isLight = brightness == Brightness.light;
return AppPalette(
backdropGradient: isLight
? _lightBackdropGradient
: _darkBackdropGradient,
drawerBackground: isLight
? _lightDrawerBackground
: _darkDrawerBackground,
cardBackground: isLight ? _lightCardBackground : _darkCardBackground,
categoryColors: defaultCategoryColors,
surfaceElevated: isLight ? _lightSurfaceElevated : _darkSurfaceElevated,
border: isLight ? _lightBorder : _darkBorder,
borderMuted: isLight ? _lightBorderMuted : _darkBorderMuted,
accent: seedColor ?? (isLight ? _lightAccent : _darkAccent),
textPrimary: isLight ? _lightTextPrimary : _darkTextPrimary,
textSecondary: isLight ? _lightTextSecondary : _darkTextSecondary,
textOnSurfaceDark: isLight
? _lightTextOnSurfaceDark
: _darkTextOnSurfaceDark,
textMuted: isLight ? _lightTextMuted : _darkTextMuted,
textDisabled: isLight ? _lightTextDisabled : _darkTextDisabled,
textHint: isLight ? _lightTextHint : _darkTextHint,
fill: isLight ? _lightFill : _darkFill,
hover: isLight ? _lightHover : _darkHover,
shadowSoft: isLight ? _lightShadowSoft : _darkShadowSoft,
shadowDim: isLight ? _lightShadowDim : _darkShadowDim,
destructiveAccent: Colors.redAccent,
textOnAccent: isLight ? _lightTextOnAccent : _darkTextOnAccent,
overlay: isLight ? _lightOverlay : _darkOverlay,
transparent: _transparent,
dragTargetBorder: isLight
? _lightDragTargetBorder
: _darkDragTargetBorder,
syncPreparing: isLight ? _lightSyncPreparing : _darkSyncPreparing,
syncEncrypting: isLight ? _lightSyncEncrypting : _darkSyncEncrypting,
syncUploading: isLight ? _lightSyncUploading : _darkSyncUploading,
syncWaiting: isLight ? _lightSyncWaiting : _darkSyncWaiting,
syncDecrypting: isLight ? _lightSyncDecrypting : _darkSyncDecrypting,
success: isLight ? _lightSuccess : _darkSuccess,
);
}
}
+21 -9
View File
@@ -1,21 +1,33 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class AppTheme { class AppTheme {
static ThemeData theme({Color seedColor = AppColors.defaultThemeSeedColor}) { static ThemeData theme({
Color seedColor = Colors.amber,
Brightness brightness = Brightness.dark,
}) {
final Brightness foregroundBrightness = final Brightness foregroundBrightness =
ThemeData.estimateBrightnessForColor(seedColor); ThemeData.estimateBrightnessForColor(seedColor);
final Color foregroundColor = foregroundBrightness == Brightness.dark final Color foregroundColor = foregroundBrightness == Brightness.dark
? AppColors.textPrimary ? Colors.white
: AppColors.textOnAccent; : Colors.black87;
final ColorScheme scheme = ColorScheme.fromSeed(
seedColor: seedColor,
brightness: brightness,
);
final AppPalette palette = AppPalette.fromBrightness(
brightness,
seedColor: seedColor,
);
return ThemeData( return ThemeData(
useMaterial3: true, useMaterial3: true,
scaffoldBackgroundColor: AppColors.scaffoldBackground, scaffoldBackgroundColor: scheme.background,
colorScheme: ColorScheme.fromSeed( colorScheme: scheme,
seedColor: seedColor, extensions: <ThemeExtension<dynamic>>[palette],
brightness: Brightness.dark, brightness: brightness,
),
floatingActionButtonTheme: FloatingActionButtonThemeData( floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: seedColor, backgroundColor: seedColor,
foregroundColor: foregroundColor, foregroundColor: foregroundColor,
+11 -2
View File
@@ -1,10 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class CategoryStyle { class CategoryStyle {
CategoryStyle._(); CategoryStyle._();
static const List<Color> colors = AppColors.categoryColors; static const List<Color> colors = AppPalette.defaultCategoryColors;
static List<Color> colorsOf(BuildContext context) {
final AppPalette? palette = Theme.of(context).extension<AppPalette>();
if (palette != null) {
return palette.categoryColors;
}
return AppPalette.defaultCategoryColors;
}
static const List<IconData> icons = <IconData>[ static const List<IconData> icons = <IconData>[
Icons.label_outline_rounded, Icons.label_outline_rounded,
+17 -18
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/models/category.dart'; import 'package:notas/models/category.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
import 'package:notas/widgets/category_style.dart'; import 'package:notas/widgets/category_style.dart';
class MenuDrawer extends StatelessWidget { class MenuDrawer extends StatelessWidget {
@@ -21,10 +21,12 @@ class MenuDrawer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Container( return Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColors.drawerBackground, color: palette.drawerBackground,
border: Border(right: BorderSide(color: AppColors.border, width: 0.5)), border: Border(right: BorderSide(color: palette.border, width: 0.5)),
), ),
child: Column( child: Column(
children: [ children: [
@@ -59,12 +61,10 @@ class MenuDrawer extends StatelessWidget {
? null ? null
: () => onEditCategory?.call(category), : () => onEditCategory?.call(category),
iconColor: Color( iconColor: Color(
category.colorValue ?? category.colorValue ?? palette.accent.value,
AppColors.categoryFallback.value,
), ),
textColor: Color( textColor: Color(
category.colorValue ?? category.colorValue ?? palette.accent.value,
AppColors.categoryFallback.value,
), ),
trailing: IconButton( trailing: IconButton(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
@@ -72,9 +72,9 @@ class MenuDrawer extends StatelessWidget {
minWidth: 0, minWidth: 0,
minHeight: 0, minHeight: 0,
), ),
icon: const Icon( icon: Icon(
Icons.more_vert, Icons.more_vert,
color: AppColors.textSecondary, color: palette.textSecondary,
size: 20, size: 20,
), ),
onPressed: () => onEditCategory?.call(category), onPressed: () => onEditCategory?.call(category),
@@ -97,10 +97,10 @@ class MenuDrawer extends StatelessWidget {
label: 'Mis notas borradas', label: 'Mis notas borradas',
selected: selectedItem == 'deleted_notes', selected: selectedItem == 'deleted_notes',
onTap: () => onMenuItemTapped?.call('deleted_notes'), onTap: () => onMenuItemTapped?.call('deleted_notes'),
iconColor: AppColors.destructiveAccent, iconColor: palette.destructiveAccent,
textColor: AppColors.destructiveAccent, textColor: palette.destructiveAccent,
), ),
const Divider(color: AppColors.border, height: 16), Divider(color: palette.border, height: 16),
_MenuItemTile( _MenuItemTile(
icon: Icons.settings, icon: Icons.settings,
label: 'Configuración', label: 'Configuración',
@@ -143,13 +143,12 @@ class _MenuItemTileState extends State<_MenuItemTile> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool active = widget.selected || _hovering; final bool active = widget.selected || _hovering;
final Color backgroundColor = active final Color backgroundColor = active ? palette.hover : palette.transparent;
? AppColors.hover
: AppColors.transparent;
final Color foregroundColor = active final Color foregroundColor = active
? AppColors.textPrimary ? palette.textPrimary
: AppColors.textSecondary; : palette.textSecondary;
final Widget? trailing = _hovering ? widget.trailing : null; final Widget? trailing = _hovering ? widget.trailing : null;
return MouseRegion( return MouseRegion(
+12 -11
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/models/note.dart'; import 'package:notas/models/note.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
// Small presentational widget for a note inside the grid. // Small presentational widget for a note inside the grid.
// Keep this widget lightweight and layout-agnostic: it should not force // Keep this widget lightweight and layout-agnostic: it should not force
@@ -31,6 +31,7 @@ class _NoteCardState extends State<NoteCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool showGrabbing = widget.isDragging || _isPressed; final bool showGrabbing = widget.isDragging || _isPressed;
return MouseRegion( return MouseRegion(
@@ -63,10 +64,10 @@ class _NoteCardState extends State<NoteCard> {
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBackground, color: palette.cardBackground,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: widget.borderColor ?? AppColors.textDisabled, color: widget.borderColor ?? palette.textDisabled,
width: 1, width: 1,
), ),
), ),
@@ -90,8 +91,8 @@ class _NoteCardState extends State<NoteCard> {
final TextPainter textPainter = TextPainter( final TextPainter textPainter = TextPainter(
text: TextSpan( text: TextSpan(
text: widget.note.body, text: widget.note.body,
style: const TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
fontSize: 14, fontSize: 14,
), ),
), ),
@@ -110,8 +111,8 @@ class _NoteCardState extends State<NoteCard> {
children: [ children: [
Text( Text(
widget.note.title, widget.note.title,
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -121,8 +122,8 @@ class _NoteCardState extends State<NoteCard> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
widget.note.body, widget.note.body,
style: const TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: palette.textSecondary,
fontSize: 14, fontSize: 14,
), ),
maxLines: 20, maxLines: 20,
@@ -130,10 +131,10 @@ class _NoteCardState extends State<NoteCard> {
), ),
if (isBodyTruncated) ...[ if (isBodyTruncated) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'...', '...',
style: TextStyle( style: TextStyle(
color: AppColors.textMuted, color: palette.textMuted,
fontSize: 18, fontSize: 18,
height: 1, height: 1,
), ),
+22 -22
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
class SearchAppBar extends StatefulWidget { class SearchAppBar extends StatefulWidget {
const SearchAppBar({ const SearchAppBar({
@@ -52,10 +52,12 @@ class _SearchAppBarState extends State<SearchAppBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.transparent, color: palette.transparent,
border: Border(bottom: BorderSide(color: AppColors.border, width: 0.5)), border: Border(bottom: BorderSide(color: palette.border, width: 0.5)),
), ),
padding: const EdgeInsets.only(left: 8, right: 20, top: 7, bottom: 7), padding: const EdgeInsets.only(left: 8, right: 20, top: 7, bottom: 7),
child: Row( child: Row(
@@ -64,7 +66,7 @@ class _SearchAppBarState extends State<SearchAppBar> {
onPressed: widget.onLeadingPressed ?? widget.onMenuPressed, onPressed: widget.onLeadingPressed ?? widget.onMenuPressed,
icon: Icon( icon: Icon(
widget.leadingIcon, widget.leadingIcon,
color: AppColors.textSecondary, color: palette.textSecondary,
size: 20, size: 20,
), ),
tooltip: widget.leadingTooltip, tooltip: widget.leadingTooltip,
@@ -84,23 +86,21 @@ class _SearchAppBarState extends State<SearchAppBar> {
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
onChanged: widget.onSearchChanged, onChanged: widget.onSearchChanged,
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 13, fontSize: 13,
), ),
cursorColor: AppColors.textSecondary, cursorColor: palette.textSecondary,
decoration: InputDecoration( decoration: InputDecoration(
hintText: widget.searchHint, hintText: widget.searchHint,
hintStyle: TextStyle( hintStyle: TextStyle(
color: AppColors.textSecondary.withValues( color: palette.textSecondary.withOpacity(0.6),
alpha: 0.5,
),
), ),
suffixIcon: _searchController.text.isNotEmpty suffixIcon: _searchController.text.isNotEmpty
? IconButton( ? IconButton(
icon: const Icon( icon: Icon(
Icons.clear, Icons.clear,
color: AppColors.textSecondary, color: palette.textSecondary,
size: 18, size: 18,
), ),
onPressed: () { onPressed: () {
@@ -112,37 +112,37 @@ class _SearchAppBarState extends State<SearchAppBar> {
minHeight: 36, minHeight: 36,
), ),
) )
: const Padding( : Padding(
padding: EdgeInsets.only(right: 8), padding: const EdgeInsets.only(right: 8),
child: Icon( child: Icon(
Icons.search, Icons.search,
color: AppColors.textSecondary, color: palette.textSecondary,
size: 18, size: 18,
), ),
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: AppColors.borderStrong, color: palette.border,
width: 0.5, width: 0.5,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: AppColors.borderStrong, color: palette.border,
width: 0.5, width: 0.5,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: AppColors.searchFocusBorder, color: palette.accent,
width: 0.5, width: 0.6,
), ),
), ),
filled: true, filled: true,
fillColor: AppColors.fill, fillColor: palette.fill,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 12, horizontal: 12,
vertical: 8, vertical: 8,
@@ -156,8 +156,8 @@ class _SearchAppBarState extends State<SearchAppBar> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
widget.titleText ?? '', widget.titleText ?? '',
style: const TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: palette.textPrimary,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
+12 -14
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart'; import 'package:notas/theme/app_palette.dart';
import 'package:notas/widgets/sync_status.dart'; import 'package:notas/widgets/sync_status.dart';
class SyncStatusIndicator extends StatelessWidget { class SyncStatusIndicator extends StatelessWidget {
@@ -87,16 +87,14 @@ class SyncStatusIndicator extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
switch (status) { switch (status) {
case SyncStatus.idle: case SyncStatus.idle:
return Tooltip( return Tooltip(
message: _messageForStatus(), message: _messageForStatus(),
child: _buildIndicator( child: _buildIndicator(
const Icon( Icon(Icons.cloud_outlined, size: 16, color: palette.textSecondary),
Icons.cloud_outlined,
size: 16,
color: AppColors.textSubtle,
),
), ),
); );
@@ -106,7 +104,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator( child: _buildIndicator(
_buildStatusBadge( _buildStatusBadge(
icon: Icons.sync, icon: Icons.sync,
color: AppColors.syncPreparing, color: palette.syncPreparing,
determinate: false, determinate: false,
), ),
), ),
@@ -118,7 +116,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator( child: _buildIndicator(
_buildStatusBadge( _buildStatusBadge(
icon: Icons.cloud_upload_outlined, icon: Icons.cloud_upload_outlined,
color: AppColors.syncEncrypting, color: palette.syncEncrypting,
determinate: true, determinate: true,
), ),
), ),
@@ -130,7 +128,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator( child: _buildIndicator(
_buildStatusBadge( _buildStatusBadge(
icon: Icons.cloud_upload, icon: Icons.cloud_upload,
color: AppColors.syncUploading, color: palette.syncUploading,
determinate: false, determinate: false,
), ),
), ),
@@ -142,7 +140,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator( child: _buildIndicator(
_buildStatusBadge( _buildStatusBadge(
icon: Icons.cloud_sync_outlined, icon: Icons.cloud_sync_outlined,
color: AppColors.syncWaiting, color: palette.syncWaiting,
determinate: false, determinate: false,
), ),
), ),
@@ -154,7 +152,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator( child: _buildIndicator(
_buildStatusBadge( _buildStatusBadge(
icon: Icons.cloud_download_outlined, icon: Icons.cloud_download_outlined,
color: AppColors.syncDecrypting, color: palette.syncDecrypting,
determinate: true, determinate: true,
), ),
), ),
@@ -166,7 +164,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator( child: _buildIndicator(
_buildStatusBadge( _buildStatusBadge(
icon: Icons.sync, icon: Icons.sync,
color: AppColors.syncWaiting, color: palette.syncWaiting,
determinate: false, determinate: false,
), ),
), ),
@@ -176,7 +174,7 @@ class SyncStatusIndicator extends StatelessWidget {
return Tooltip( return Tooltip(
message: _messageForStatus(), message: _messageForStatus(),
child: _buildIndicator( child: _buildIndicator(
const Icon(Icons.check_circle, size: 16, color: AppColors.success), Icon(Icons.check_circle, size: 16, color: palette.success),
), ),
); );
@@ -184,7 +182,7 @@ class SyncStatusIndicator extends StatelessWidget {
return Tooltip( return Tooltip(
message: _messageForStatus(), message: _messageForStatus(),
child: _buildIndicator( child: _buildIndicator(
const Icon(Icons.error, size: 16, color: AppColors.destructive), Icon(Icons.error, size: 16, color: palette.destructiveAccent),
), ),
); );
} }
+1 -1
View File
@@ -8,6 +8,6 @@ void main() {
await tester.pumpWidget(const NotesApp()); await tester.pumpWidget(const NotesApp());
expect(find.byType(MaterialApp), findsOneWidget); expect(find.byType(MaterialApp), findsOneWidget);
expect(find.text('Mis Notas'), findsWidgets); expect(find.text('Preparando el vault local...'), findsWidgets);
}); });
} }