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/settings_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/widgets/sync_status.dart';
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 _windowSizeSaveDelay = Duration(milliseconds: 350);
static const String _themeSeedColorKey = 'theme_seed_color_v1';
static const String _themeModeKey = 'theme_mode_v1';
final LocalVaultService _vaultService = LocalVaultService.instance;
final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
@@ -69,7 +70,30 @@ class _NotesAppState extends State<NotesApp>
String? _syncErrorMessage;
int _syncOperationId = 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
void initState() {
@@ -80,6 +104,7 @@ class _NotesAppState extends State<NotesApp>
windowManager.setPreventClose(true);
}
_loadThemeSeedColor();
_loadThemeMode();
_bootstrapVault();
}
@@ -106,7 +131,7 @@ class _NotesAppState extends State<NotesApp>
setState(() {
_themeSeedColor = Color(storedColorValue);
_themeData = AppTheme.theme(seedColor: _themeSeedColor);
_updateThemeData();
});
}
@@ -120,15 +145,54 @@ class _NotesAppState extends State<NotesApp>
setState(() {
_themeSeedColor = color;
_themeData = AppTheme.theme(seedColor: _themeSeedColor);
_updateThemeData();
});
}
// Cached ThemeData to avoid recomputing on every build.
ThemeData? _themeData;
Future<void> _loadThemeMode() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int? stored = prefs.getInt(_themeModeKey);
if (!mounted) return;
ThemeData get _theme =>
_themeData ??= AppTheme.theme(seedColor: _themeSeedColor);
setState(() {
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
void didChangeAppLifecycleState(AppLifecycleState state) {
@@ -429,11 +493,13 @@ class _NotesAppState extends State<NotesApp>
final bool? retry = await showDialog<bool>(
context: dialogCtx,
builder: (BuildContext context) => AlertDialog(
backgroundColor: AppColors.cardBackground,
builder: (BuildContext context) {
final AppPalette palette = _activePalette();
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border),
side: BorderSide(color: palette.border),
),
title: const Text('No se pudo activar la biometría'),
content: const Text(
@@ -449,7 +515,8 @@ class _NotesAppState extends State<NotesApp>
child: const Text('Reintentar'),
),
],
),
);
},
);
if (retry != true) {
@@ -825,6 +892,7 @@ class _NotesAppState extends State<NotesApp>
}
Widget _buildMainShell(NoteRepository repository) {
final AppPalette palette = _activePalette();
final Widget activeScreen = _currentSection == _AppSection.home
? HomeScreen(
key: const ValueKey<String>('home-screen'),
@@ -845,6 +913,8 @@ class _NotesAppState extends State<NotesApp>
onForceSync: () => _performSync(forceFull: true),
currentSeedColor: _themeSeedColor,
onThemeColorSelected: _setThemeSeedColor,
currentThemeMode: _themeMode,
onThemeModeSelected: _setThemeMode,
);
return Shortcuts(
@@ -864,9 +934,7 @@ class _NotesAppState extends State<NotesApp>
autofocus: true,
child: Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: AppColors.backdropGradient,
),
decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea(
child: Column(
children: [
@@ -1026,7 +1094,19 @@ class _NotesAppState extends State<NotesApp>
title: 'Mis Notas',
debugShowCheckedModeBanner: false,
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,
);
}
+11 -11
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart';
import 'package:notas/theme/app_palette.dart';
class BiometricChoiceScreen extends StatelessWidget {
const BiometricChoiceScreen({
@@ -15,9 +15,11 @@ class BiometricChoiceScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold(
body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient),
decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea(
child: Column(
children: [
@@ -30,12 +32,12 @@ class BiometricChoiceScreen extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColors.surface,
color: palette.cardBackground,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderMuted),
border: Border.all(color: palette.border),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
color: palette.shadowSoft,
blurRadius: 30,
offset: const Offset(0, 18),
),
@@ -55,7 +57,7 @@ class BiometricChoiceScreen extends StatelessWidget {
'Proteger con huella',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textPrimary,
color: Colors.white,
fontSize: 28,
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?',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textSecondary,
color: palette.textSecondary,
height: 1.4,
),
),
@@ -94,10 +96,8 @@ class BiometricChoiceScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric(
vertical: 14,
),
side: const BorderSide(
color: AppColors.textDisabled,
),
foregroundColor: AppColors.textPrimary,
side: BorderSide(color: palette.border),
foregroundColor: palette.textPrimary,
),
child: const Text('No, entrar sin huella'),
),
+9 -7
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart';
import 'package:notas/theme/app_palette.dart';
class BiometricGateScreen extends StatefulWidget {
const BiometricGateScreen({
@@ -39,9 +39,11 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold(
body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient),
decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea(
child: Column(
children: [
@@ -54,12 +56,12 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColors.surface,
color: palette.cardBackground,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderMuted),
border: Border.all(color: palette.border),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
color: palette.shadowSoft,
blurRadius: 30,
offset: const Offset(0, 18),
),
@@ -79,7 +81,7 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
'Desbloqueo biométrico',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textPrimary,
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.w700,
),
@@ -89,7 +91,7 @@ class _BiometricGateScreenState extends State<BiometricGateScreen> {
'Pon tu huella o cara para entrar a tus notas.',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textSecondary,
color: palette.textSecondary,
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/sync_status.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 {
const HomeScreen({
@@ -489,9 +489,11 @@ class _HomeScreenState extends State<HomeScreen> {
),
);
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold(
body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient),
decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea(
child: Column(
children: [
@@ -565,7 +567,7 @@ class _HomeScreenState extends State<HomeScreen> {
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _closeMenu,
child: Container(color: AppColors.overlay),
child: Container(color: palette.overlay),
),
),
),
@@ -578,7 +580,7 @@ class _HomeScreenState extends State<HomeScreen> {
bottom: 0,
width: 280,
child: Material(
color: AppColors.cardBackground,
color: palette.cardBackground,
elevation: 8,
child: MenuDrawer(
onMenuItemTapped: _handleMenuItemTapped,
@@ -642,11 +644,13 @@ class _DraggableNote extends StatelessWidget {
final VoidCallback onDraggableCanceled;
final VoidCallback onDragStarted;
Widget _buildFeedback() {
Widget _buildFeedback(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return MouseRegion(
cursor: SystemMouseCursors.grabbing,
child: Material(
color: AppColors.transparent,
color: palette.transparent,
elevation: 8,
child: SizedBox(
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(
cursor: SystemMouseCursors.grabbing,
child: Opacity(
@@ -684,10 +690,10 @@ class _DraggableNote extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardBackground,
color: palette.cardBackground,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: borderColor ?? AppColors.textDisabled,
color: borderColor ?? palette.textSecondary,
width: 1,
),
),
@@ -697,8 +703,8 @@ class _DraggableNote extends StatelessWidget {
children: [
Text(
note.title,
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
@@ -708,10 +714,7 @@ class _DraggableNote extends StatelessWidget {
const SizedBox(height: 8),
Text(
note.body,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: 14,
),
style: TextStyle(color: palette.textSecondary, fontSize: 14),
maxLines: 20,
overflow: TextOverflow.clip,
),
@@ -724,10 +727,12 @@ class _DraggableNote extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final Widget content = Container(
decoration: BoxDecoration(
border: isDragTargetActive
? Border.all(color: AppColors.dragTargetBorder, width: 2)
? Border.all(color: palette.dragTargetBorder, width: 2)
: null,
borderRadius: BorderRadius.circular(12),
),
@@ -747,8 +752,8 @@ class _DraggableNote extends StatelessWidget {
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(),
feedback: _buildFeedback(),
childWhenDragging: _buildChildWhenDragging(),
feedback: _buildFeedback(context),
childWhenDragging: _buildChildWhenDragging(context),
child: content,
);
}
@@ -758,8 +763,8 @@ class _DraggableNote extends StatelessWidget {
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
onDraggableCanceled: (Velocity _v, Offset _o) => onDraggableCanceled(),
feedback: _buildFeedback(),
childWhenDragging: _buildChildWhenDragging(),
feedback: _buildFeedback(context),
childWhenDragging: _buildChildWhenDragging(context),
child: content,
);
}
@@ -778,15 +783,13 @@ class _EmptyState extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.note_add_outlined,
color: AppColors.textMuted,
size: 48,
),
Icon(Icons.note_add_outlined, color: palette.textSecondary, size: 48),
const SizedBox(height: 12),
Text(
searchQuery != null && searchQuery!.isNotEmpty
@@ -796,8 +799,8 @@ class _EmptyState extends StatelessWidget {
: categoryName != null
? 'No hay notas en esta categoría'
: 'Aún no hay notas',
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 18,
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 la primera.',
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 {
final bool? confirm = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AlertDialog(
backgroundColor: AppColors.cardBackground,
builder: (BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border),
side: BorderSide(color: palette.border),
),
title: const Text('Borrar 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'),
),
TextButton(
style: TextButton.styleFrom(foregroundColor: AppColors.destructive),
style: TextButton.styleFrom(
foregroundColor: palette.destructiveAccent,
),
onPressed: () => Navigator.pop(context, true),
child: const Text('Borrar'),
),
],
),
);
},
);
if (confirm != true) {
@@ -982,11 +990,13 @@ class _CategoryDialogState extends State<_CategoryDialog> {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: AppColors.cardBackground,
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border),
side: BorderSide(color: palette.border),
),
title: Text(
widget.category == null ? 'Crear categoría' : 'Editar categoría',
@@ -1018,9 +1028,9 @@ class _CategoryDialogState extends State<_CategoryDialog> {
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
color: AppColors.fill,
color: palette.fill,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border),
border: Border.all(color: palette.border),
),
child: Column(
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: const EdgeInsets.all(12),
child: AnimatedSwitcher(
@@ -1075,11 +1085,11 @@ class _CategoryDialogState extends State<_CategoryDialog> {
borderRadius: BorderRadius.circular(10),
border: isSelected
? Border.all(
color: AppColors.textPrimary,
color: palette.textPrimary,
width: 2,
)
: Border.all(
color: AppColors.border,
color: palette.border,
width: 1,
),
),
@@ -1105,21 +1115,21 @@ class _CategoryDialogState extends State<_CategoryDialog> {
height: 42,
decoration: BoxDecoration(
color: isSelected
? AppColors.hover
: AppColors.transparent,
? palette.hover
: palette.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSelected
? AppColors.textPrimary
: AppColors.border,
? palette.textPrimary
: palette.border,
width: 1,
),
),
child: Icon(
icon,
color: isSelected
? AppColors.textPrimary
: AppColors.textSecondary,
? palette.textPrimary
: palette.textSecondary,
),
),
);
@@ -1137,9 +1147,9 @@ class _CategoryDialogState extends State<_CategoryDialog> {
if (widget.category != null)
TextButton(
onPressed: _deleteCategory,
child: const Text(
child: Text(
'Borrar',
style: TextStyle(color: AppColors.destructive),
style: TextStyle(color: palette.destructiveAccent),
),
),
TextButton(
@@ -1176,10 +1186,12 @@ class _PickerTabButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Material(
borderRadius: borderRadius,
clipBehavior: Clip.antiAlias,
color: selected ? AppColors.hover : AppColors.transparent,
color: selected ? palette.hover : palette.transparent,
child: InkWell(
borderRadius: borderRadius,
onTap: onTap,
@@ -1189,7 +1201,7 @@ class _PickerTabButton extends StatelessWidget {
child: Text(
label,
style: TextStyle(
color: selected ? AppColors.textPrimary : AppColors.textMuted,
color: selected ? palette.textPrimary : palette.textSecondary,
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/note.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';
// NoteEditorScreen: unified UI for creating and editing notes.
@@ -42,7 +42,7 @@ class NoteEditorScreen extends StatefulWidget {
return showGeneralDialog<dynamic>(
context: context,
barrierDismissible: false,
barrierColor: AppColors.transparent,
barrierColor: Colors.transparent,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animation, secondaryAnimation) {
return NoteEditorScreen(
@@ -120,6 +120,11 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
bool get _isMobileLayout => isAndroid || isIOS;
AppPalette _paletteOf(BuildContext context) {
return Theme.of(context).extension<AppPalette>() ??
AppPalette.fromBrightness(Theme.of(context).brightness);
}
@override
void initState() {
super.initState();
@@ -200,36 +205,37 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
required ValueChanged<bool> onConfirmed,
}) {
final bool isDeletedNote = _currentNote.isDeleted;
final AppPalette palette = _paletteOf(context);
return AlertDialog(
backgroundColor: AppColors.cardBackground,
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border),
side: BorderSide(color: palette.border),
),
title: Text(
isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota',
style: const TextStyle(color: AppColors.textPrimary),
style: TextStyle(color: palette.textPrimary),
),
content: Text(
isDeletedNote
? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.'
: '¿Estás seguro de que deseas eliminar esta nota?',
style: const TextStyle(color: AppColors.textSecondary),
style: TextStyle(color: palette.textSecondary),
),
actions: [
TextButton(
onPressed: () => onConfirmed(false),
child: const Text(
child: Text(
'Cancelar',
style: TextStyle(color: AppColors.textSecondary),
style: TextStyle(color: palette.textSecondary),
),
),
TextButton(
onPressed: () => onConfirmed(true),
child: Text(
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>(
context: context,
barrierDismissible: false,
barrierColor: AppColors.transparent,
barrierColor: Colors.transparent,
builder: (BuildContext dialogContext) {
return _buildDeleteConfirmationDialog(
onConfirmed: (bool confirmed) =>
@@ -258,7 +264,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
final bool? confirmed = await showDialog<bool>(
context: context,
barrierDismissible: false,
barrierColor: AppColors.transparent,
barrierColor: Colors.transparent,
builder: (BuildContext dialogContext) {
return _buildDeleteConfirmationDialog(
onConfirmed: (bool confirmed) =>
@@ -287,14 +293,11 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
};
return Material(
color: AppColors.transparent,
color: Colors.transparent,
child: Stack(
children: [
const Positioned.fill(
child: ModalBarrier(
dismissible: false,
color: AppColors.overlay,
),
child: ModalBarrier(dismissible: false, color: Colors.black54),
),
Center(
child: ConstrainedBox(
@@ -323,25 +326,30 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
}
Color _categoryBackgroundColor(Category? category) {
final AppPalette palette = _paletteOf(context);
if (category?.colorValue == null) {
return AppColors.borderMuted;
return palette.borderMuted;
}
return Color(category!.colorValue!);
}
Color _categoryForegroundColor(Category? category) {
final AppPalette palette = _paletteOf(context);
if (category == null || category.colorValue == null) {
return AppColors.textPrimary;
return palette.textPrimary;
}
final Color background = Color(category.colorValue!);
return background.computeLuminance() > 0.55
? AppColors.textOnSurfaceDark
: AppColors.textPrimary;
? palette.textOnSurfaceDark
: palette.textPrimary;
}
Widget _buildCategorySelectorBox({Category? category}) {
final AppPalette palette = _paletteOf(context);
final String label = category?.name ?? 'Sin categoría';
final IconData icon = CategoryStyle.iconForCodePoint(
category?.iconCodePoint,
@@ -358,7 +366,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
border: Border.all(
color: category?.colorValue != null
? backgroundColor.withValues(alpha: 0.85)
: AppColors.textDisabled,
: palette.textDisabled,
),
),
child: Row(
@@ -409,12 +417,13 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
_categoryMenuEntry = OverlayEntry(
builder: (BuildContext overlayContext) {
final AppPalette palette = _paletteOf(overlayContext);
final Size screenSize = MediaQuery.of(overlayContext).size;
final double menuWidth = math.min(screenSize.width - 32, 320);
final double menuHeight = math.min(screenSize.height - 32, 360);
return Material(
color: AppColors.transparent,
color: Colors.transparent,
child: Stack(
children: [
Positioned.fill(
@@ -432,7 +441,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
),
child: Material(
elevation: 10,
color: AppColors.surfaceElevated,
color: palette.surfaceElevated,
borderRadius: BorderRadius.circular(12),
clipBehavior: Clip.antiAlias,
child: ListView(
@@ -482,6 +491,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
required bool isSelected,
required VoidCallback onTap,
}) {
final AppPalette palette = _paletteOf(context);
final Color backgroundColor = _categoryBackgroundColor(category);
final Color foregroundColor = _categoryForegroundColor(category);
final IconData icon = CategoryStyle.iconForCodePoint(
@@ -492,7 +502,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
color: isSelected ? AppColors.hover : null,
color: isSelected ? palette.hover : null,
child: Row(
children: [
Container(
@@ -504,7 +514,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
border: Border.all(
color: category?.colorValue != null
? backgroundColor.withValues(alpha: 0.85)
: AppColors.textDisabled,
: palette.textDisabled,
),
),
child: Icon(icon, size: 16, color: foregroundColor),
@@ -553,6 +563,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
}
Widget _buildEditorContent({required bool isMobile}) {
final AppPalette palette = _paletteOf(context);
final double titleSpacing = isMobile ? 16.0 : 8.0;
return Column(
@@ -560,15 +571,13 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: AppColors.border, width: 1),
),
border: Border(bottom: BorderSide(color: palette.border, width: 1)),
),
child: Row(
children: [
IconButton(
onPressed: _closeWithoutSaving,
icon: const Icon(Icons.close, color: AppColors.textSecondary),
icon: Icon(Icons.close, color: palette.textSecondary),
tooltip: 'Cerrar sin guardar',
),
const SizedBox(width: 8),
@@ -579,23 +588,17 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
children: [
Text(
'Posicion: ${_currentNote.position}',
style: const TextStyle(
color: AppColors.textMuted,
fontSize: 12,
),
style: TextStyle(color: palette.textMuted, fontSize: 12),
),
Text(
'Creado: ${_formatDate(_currentNote.createdAt)}',
style: const TextStyle(
color: AppColors.textMuted,
fontSize: 12,
),
style: TextStyle(color: palette.textMuted, fontSize: 12),
),
if (_currentNote.updatedAt != _currentNote.createdAt)
Text(
'Modificado: ${_formatDate(_currentNote.updatedAt)}',
style: const TextStyle(
color: AppColors.textMuted,
style: TextStyle(
color: palette.textMuted,
fontSize: 12,
),
),
@@ -629,14 +632,14 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
children: [
TextField(
controller: _titleController,
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 28,
fontWeight: FontWeight.bold,
),
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: 'Título',
hintStyle: TextStyle(color: AppColors.textHint),
hintStyle: TextStyle(color: palette.textHint),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
@@ -648,14 +651,14 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
keyboardType: TextInputType.multiline,
maxLines: null,
expands: true,
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 16,
height: 1.6,
),
decoration: const InputDecoration(
decoration: InputDecoration(
hintText: 'Escribe tu nota...',
hintStyle: TextStyle(color: AppColors.textHint),
hintStyle: TextStyle(color: palette.textHint),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
@@ -668,7 +671,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(top: BorderSide(color: AppColors.border, width: 1)),
border: Border(top: BorderSide(color: palette.border, width: 1)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -676,9 +679,9 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
if (!_isNewNote)
IconButton(
onPressed: _deleteNote,
icon: const Icon(
icon: Icon(
Icons.delete_outline,
color: AppColors.destructive,
color: palette.destructiveAccent,
),
tooltip: 'Eliminar nota',
)
@@ -694,12 +697,14 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
@override
Widget build(BuildContext context) {
final AppPalette palette = _paletteOf(context);
if (_isMobileLayout) {
return Material(
color: AppColors.transparent,
color: palette.transparent,
child: SafeArea(
child: Container(
color: AppColors.cardBackground,
color: palette.cardBackground,
child: _buildEditorContent(isMobile: true),
),
),
@@ -714,10 +719,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
return Stack(
children: [
Positioned.fill(
child: ModalBarrier(
dismissible: false,
color: AppColors.shadowDim,
),
child: ModalBarrier(dismissible: false, color: palette.shadowDim),
),
Positioned.fill(
child: Center(
@@ -725,10 +727,10 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
width: maxWidth,
height: maxHeight,
child: Material(
color: AppColors.cardBackground,
color: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: AppColors.textDisabled, width: 1),
side: BorderSide(color: palette.textDisabled, width: 1),
),
clipBehavior: Clip.antiAlias,
child: _buildEditorContent(isMobile: false),
+120 -51
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.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/data/api_client.dart';
@@ -12,6 +12,8 @@ class SettingsScreen extends StatefulWidget {
required this.onForceSync,
required this.currentSeedColor,
required this.onThemeColorSelected,
required this.currentThemeMode,
required this.onThemeModeSelected,
});
final Future<void> Function() onDeleteAllData;
@@ -19,6 +21,8 @@ class SettingsScreen extends StatefulWidget {
final Future<void> Function() onForceSync;
final Color currentSeedColor;
final Future<void> Function(Color color) onThemeColorSelected;
final ThemeMode currentThemeMode;
final Future<void> Function(ThemeMode mode) onThemeModeSelected;
@override
State<SettingsScreen> createState() => _SettingsScreenState();
@@ -36,17 +40,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
bool _encryptionKeyLoading = false;
bool _encryptionKeyVisible = false;
late Color _selectedSeedColor;
late ThemeMode _selectedThemeMode;
static const List<Color> _themeColorOptions = AppColors.themeSeedColors;
static const List<Color> _themeColorOptions = AppPalette.themeSeedColors;
Future<void> _confirmAndDeleteAll() async {
final bool? confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColors.cardBackground,
builder: (context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border),
side: BorderSide(color: palette.border),
),
title: const Text('Borrar todos los datos'),
content: const Text(
@@ -59,13 +66,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text(
child: Text(
'Borrar',
style: TextStyle(color: AppColors.destructive),
style: TextStyle(color: palette.destructiveAccent),
),
),
],
),
);
},
);
if (confirmed != true) return;
@@ -100,11 +108,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
Future<void> _confirmAndDeleteServerData() async {
final bool? confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColors.cardBackground,
builder: (context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return AlertDialog(
backgroundColor: palette.cardBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: AppColors.border),
side: BorderSide(color: palette.border),
),
title: const Text('Borrar toda la info del servidor'),
content: const Text(
@@ -117,13 +127,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text(
child: Text(
'Borrar',
style: TextStyle(color: AppColors.destructive),
style: TextStyle(color: palette.destructiveAccent),
),
),
],
),
);
},
);
if (confirmed != true) return;
@@ -234,6 +245,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
void initState() {
super.initState();
_selectedSeedColor = widget.currentSeedColor;
_selectedThemeMode = widget.currentThemeMode;
_loadEndpoint();
}
@@ -244,6 +256,26 @@ class _SettingsScreenState extends State<SettingsScreen> {
widget.currentSeedColor != _selectedSeedColor) {
_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 {
@@ -307,10 +339,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
required bool isLoading,
required IconData icon,
}) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.destructiveAccent,
foregroundColor: AppColors.textPrimary,
backgroundColor: palette.destructiveAccent,
foregroundColor: palette.textPrimary,
textStyle: const TextStyle(fontWeight: FontWeight.w600),
),
onPressed: onPressed,
@@ -326,11 +360,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
}
Widget _buildThemeColorButton(Color color) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool isSelected = _selectedSeedColor.value == color.value;
final Color foregroundColor =
ThemeData.estimateBrightnessForColor(color) == Brightness.dark
? AppColors.textPrimary
: AppColors.textOnAccent;
? palette.textPrimary
: palette.textOnAccent;
return Semantics(
button: true,
@@ -349,14 +384,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
color: color,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? AppColors.textPrimary
: AppColors.textDisabled,
color: isSelected ? palette.textPrimary : palette.textSecondary,
width: isSelected ? 2.5 : 1.2,
),
boxShadow: [
BoxShadow(
color: AppColors.shadowSoft,
color: palette.shadowSoft,
blurRadius: 8,
offset: const Offset(0, 3),
),
@@ -458,9 +491,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
}
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold(
body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient),
decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea(
child: Column(
children: [
@@ -472,6 +507,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
titleText: 'Configuración',
),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -479,7 +516,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [
Row(
children: [
const Expanded(child: Text('Borrar datos locales:')),
const Expanded(
child: Text('Borrar datos locales:'),
),
_buildDestructiveButton(
label: 'Borrar',
onPressed: (_isBusy || _isServerDeleting)
@@ -532,9 +571,39 @@ class _SettingsScreenState extends State<SettingsScreen> {
],
),
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 SizedBox(height: 8),
Wrap(
Padding(
padding: const EdgeInsets.only(left: 4),
child: Wrap(
spacing: 12,
runSpacing: 12,
children: [
@@ -542,6 +611,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_buildThemeColorButton(color),
],
),
),
const SizedBox(height: 24),
const Text(
'API endpoint (ej: https://notas-api.lpncnd.es/api)',
@@ -557,33 +627,31 @@ class _SettingsScreenState extends State<SettingsScreen> {
)
: TextField(
controller: _endpointController,
style: const TextStyle(
color: AppColors.textPrimary,
),
style: TextStyle(color: palette.textPrimary),
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'API endpoint',
labelStyle: const TextStyle(
color: AppColors.textSecondary,
labelStyle: TextStyle(
color: palette.textSecondary,
),
filled: true,
fillColor: AppColors.fill,
fillColor: palette.fill,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
borderSide: BorderSide(
color: palette.border,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
borderSide: BorderSide(
color: palette.border,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.accent,
borderSide: BorderSide(
color: palette.accent,
width: 1.2,
),
),
@@ -591,11 +659,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
actions: [
ElevatedButton(
onPressed: _endpointLoading ? null : _saveEndpoint,
onPressed: _endpointLoading
? null
: _saveEndpoint,
child: const Text('Guardar'),
),
OutlinedButton(
onPressed: _endpointLoading ? null : _resetEndpoint,
onPressed: _endpointLoading
? null
: _resetEndpoint,
child: const Text('Restaurar'),
),
],
@@ -610,32 +682,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
obscureText: !_encryptionKeyVisible,
enableSuggestions: false,
autocorrect: false,
style: const TextStyle(color: AppColors.textPrimary),
style: TextStyle(color: palette.textPrimary),
decoration: InputDecoration(
labelText: _encryptionKeyVisible
? 'Clave de cifrado'
: 'Oculta hasta pulsar mostrar',
labelStyle: const TextStyle(
color: AppColors.textSecondary,
labelStyle: TextStyle(
color: palette.textSecondary,
),
filled: true,
fillColor: AppColors.fill,
fillColor: palette.fill,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
),
borderSide: BorderSide(color: palette.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
),
borderSide: BorderSide(color: palette.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.accent,
borderSide: BorderSide(
color: palette.accent,
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:notas/data/api_client.dart';
import 'package:notas/theme/app_colors.dart';
import 'package:notas/theme/app_palette.dart';
class VaultAccessScreen extends StatefulWidget {
const VaultAccessScreen({
@@ -75,9 +75,11 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Scaffold(
body: Container(
decoration: const BoxDecoration(gradient: AppColors.backdropGradient),
decoration: BoxDecoration(gradient: palette.backdropGradient),
child: SafeArea(
child: Column(
children: [
@@ -90,12 +92,12 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColors.surface,
color: palette.cardBackground,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderMuted),
border: Border.all(color: palette.border),
boxShadow: [
BoxShadow(
color: AppColors.shadow,
color: palette.shadowSoft,
blurRadius: 30,
offset: const Offset(0, 18),
),
@@ -107,7 +109,7 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
children: [
const Icon(
Icons.lock_outline,
color: AppColors.accent,
color: Colors.white,
size: 44,
),
const SizedBox(height: 16),
@@ -115,7 +117,7 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
'Mis Notas',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textPrimary,
color: Colors.white,
fontSize: 30,
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.',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textSecondary,
color: palette.textSecondary,
height: 1.4,
),
),
@@ -141,32 +143,30 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
controller: _endpointController,
enabled: !widget.isBusy,
keyboardType: TextInputType.url,
style: const TextStyle(
color: AppColors.textPrimary,
),
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: 'API endpoint',
labelStyle: const TextStyle(
color: AppColors.textSecondary,
labelStyle: TextStyle(
color: palette.textSecondary,
),
filled: true,
fillColor: AppColors.fill,
fillColor: palette.fill,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
borderSide: BorderSide(
color: palette.border,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
borderSide: BorderSide(
color: palette.border,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.accent,
borderSide: BorderSide(
color: palette.accent,
width: 1.2,
),
),
@@ -177,32 +177,26 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
controller: _emailController,
enabled: !widget.isBusy,
keyboardType: TextInputType.text,
style: const TextStyle(
color: AppColors.textPrimary,
),
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: 'Usuario',
labelStyle: const TextStyle(
color: AppColors.textSecondary,
labelStyle: TextStyle(
color: palette.textSecondary,
),
filled: true,
fillColor: AppColors.fill,
fillColor: palette.fill,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
),
borderSide: BorderSide(color: palette.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
),
borderSide: BorderSide(color: palette.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.accent,
borderSide: BorderSide(
color: palette.accent,
width: 1.2,
),
),
@@ -213,32 +207,26 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
controller: _passwordController,
enabled: !widget.isBusy,
obscureText: true,
style: const TextStyle(
color: AppColors.textPrimary,
),
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: 'Contraseña',
labelStyle: const TextStyle(
color: AppColors.textSecondary,
labelStyle: TextStyle(
color: palette.textSecondary,
),
filled: true,
fillColor: AppColors.fill,
fillColor: palette.fill,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
),
borderSide: BorderSide(color: palette.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.border,
),
borderSide: BorderSide(color: palette.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: AppColors.accent,
borderSide: BorderSide(
color: palette.accent,
width: 1.2,
),
),
@@ -271,10 +259,8 @@ class _VaultAccessScreenState extends State<VaultAccessScreen> {
padding: const EdgeInsets.symmetric(
vertical: 14,
),
side: const BorderSide(
color: AppColors.textDisabled,
),
foregroundColor: AppColors.textPrimary,
side: BorderSide(color: palette.border),
foregroundColor: palette.textPrimary,
),
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:notas/theme/app_colors.dart';
import 'package:notas/theme/app_palette.dart';
class AppTheme {
static ThemeData theme({Color seedColor = AppColors.defaultThemeSeedColor}) {
static ThemeData theme({
Color seedColor = Colors.amber,
Brightness brightness = Brightness.dark,
}) {
final Brightness foregroundBrightness =
ThemeData.estimateBrightnessForColor(seedColor);
final Color foregroundColor = foregroundBrightness == Brightness.dark
? AppColors.textPrimary
: AppColors.textOnAccent;
? Colors.white
: Colors.black87;
final ColorScheme scheme = ColorScheme.fromSeed(
seedColor: seedColor,
brightness: brightness,
);
final AppPalette palette = AppPalette.fromBrightness(
brightness,
seedColor: seedColor,
);
return ThemeData(
useMaterial3: true,
scaffoldBackgroundColor: AppColors.scaffoldBackground,
colorScheme: ColorScheme.fromSeed(
seedColor: seedColor,
brightness: Brightness.dark,
),
scaffoldBackgroundColor: scheme.background,
colorScheme: scheme,
extensions: <ThemeExtension<dynamic>>[palette],
brightness: brightness,
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: seedColor,
foregroundColor: foregroundColor,
+11 -2
View File
@@ -1,10 +1,19 @@
import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart';
import 'package:notas/theme/app_palette.dart';
class 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>[
Icons.label_outline_rounded,
+17 -18
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.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';
class MenuDrawer extends StatelessWidget {
@@ -21,10 +21,12 @@ class MenuDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Container(
decoration: const BoxDecoration(
color: AppColors.drawerBackground,
border: Border(right: BorderSide(color: AppColors.border, width: 0.5)),
decoration: BoxDecoration(
color: palette.drawerBackground,
border: Border(right: BorderSide(color: palette.border, width: 0.5)),
),
child: Column(
children: [
@@ -59,12 +61,10 @@ class MenuDrawer extends StatelessWidget {
? null
: () => onEditCategory?.call(category),
iconColor: Color(
category.colorValue ??
AppColors.categoryFallback.value,
category.colorValue ?? palette.accent.value,
),
textColor: Color(
category.colorValue ??
AppColors.categoryFallback.value,
category.colorValue ?? palette.accent.value,
),
trailing: IconButton(
padding: const EdgeInsets.all(8),
@@ -72,9 +72,9 @@ class MenuDrawer extends StatelessWidget {
minWidth: 0,
minHeight: 0,
),
icon: const Icon(
icon: Icon(
Icons.more_vert,
color: AppColors.textSecondary,
color: palette.textSecondary,
size: 20,
),
onPressed: () => onEditCategory?.call(category),
@@ -97,10 +97,10 @@ class MenuDrawer extends StatelessWidget {
label: 'Mis notas borradas',
selected: selectedItem == 'deleted_notes',
onTap: () => onMenuItemTapped?.call('deleted_notes'),
iconColor: AppColors.destructiveAccent,
textColor: AppColors.destructiveAccent,
iconColor: palette.destructiveAccent,
textColor: palette.destructiveAccent,
),
const Divider(color: AppColors.border, height: 16),
Divider(color: palette.border, height: 16),
_MenuItemTile(
icon: Icons.settings,
label: 'Configuración',
@@ -143,13 +143,12 @@ class _MenuItemTileState extends State<_MenuItemTile> {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool active = widget.selected || _hovering;
final Color backgroundColor = active
? AppColors.hover
: AppColors.transparent;
final Color backgroundColor = active ? palette.hover : palette.transparent;
final Color foregroundColor = active
? AppColors.textPrimary
: AppColors.textSecondary;
? palette.textPrimary
: palette.textSecondary;
final Widget? trailing = _hovering ? widget.trailing : null;
return MouseRegion(
+12 -11
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.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.
// Keep this widget lightweight and layout-agnostic: it should not force
@@ -31,6 +31,7 @@ class _NoteCardState extends State<NoteCard> {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool showGrabbing = widget.isDragging || _isPressed;
return MouseRegion(
@@ -63,10 +64,10 @@ class _NoteCardState extends State<NoteCard> {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.cardBackground,
color: palette.cardBackground,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: widget.borderColor ?? AppColors.textDisabled,
color: widget.borderColor ?? palette.textDisabled,
width: 1,
),
),
@@ -90,8 +91,8 @@ class _NoteCardState extends State<NoteCard> {
final TextPainter textPainter = TextPainter(
text: TextSpan(
text: widget.note.body,
style: const TextStyle(
color: AppColors.textSecondary,
style: TextStyle(
color: palette.textSecondary,
fontSize: 14,
),
),
@@ -110,8 +111,8 @@ class _NoteCardState extends State<NoteCard> {
children: [
Text(
widget.note.title,
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
@@ -121,8 +122,8 @@ class _NoteCardState extends State<NoteCard> {
const SizedBox(height: 8),
Text(
widget.note.body,
style: const TextStyle(
color: AppColors.textSecondary,
style: TextStyle(
color: palette.textSecondary,
fontSize: 14,
),
maxLines: 20,
@@ -130,10 +131,10 @@ class _NoteCardState extends State<NoteCard> {
),
if (isBodyTruncated) ...[
const SizedBox(height: 4),
const Text(
Text(
'...',
style: TextStyle(
color: AppColors.textMuted,
color: palette.textMuted,
fontSize: 18,
height: 1,
),
+22 -22
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:notas/theme/app_colors.dart';
import 'package:notas/theme/app_palette.dart';
class SearchAppBar extends StatefulWidget {
const SearchAppBar({
@@ -52,10 +52,12 @@ class _SearchAppBarState extends State<SearchAppBar> {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
return Container(
decoration: BoxDecoration(
color: AppColors.transparent,
border: Border(bottom: BorderSide(color: AppColors.border, width: 0.5)),
color: palette.transparent,
border: Border(bottom: BorderSide(color: palette.border, width: 0.5)),
),
padding: const EdgeInsets.only(left: 8, right: 20, top: 7, bottom: 7),
child: Row(
@@ -64,7 +66,7 @@ class _SearchAppBarState extends State<SearchAppBar> {
onPressed: widget.onLeadingPressed ?? widget.onMenuPressed,
icon: Icon(
widget.leadingIcon,
color: AppColors.textSecondary,
color: palette.textSecondary,
size: 20,
),
tooltip: widget.leadingTooltip,
@@ -84,23 +86,21 @@ class _SearchAppBarState extends State<SearchAppBar> {
child: TextField(
controller: _searchController,
onChanged: widget.onSearchChanged,
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 13,
),
cursorColor: AppColors.textSecondary,
cursorColor: palette.textSecondary,
decoration: InputDecoration(
hintText: widget.searchHint,
hintStyle: TextStyle(
color: AppColors.textSecondary.withValues(
alpha: 0.5,
),
color: palette.textSecondary.withOpacity(0.6),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(
icon: Icon(
Icons.clear,
color: AppColors.textSecondary,
color: palette.textSecondary,
size: 18,
),
onPressed: () {
@@ -112,37 +112,37 @@ class _SearchAppBarState extends State<SearchAppBar> {
minHeight: 36,
),
)
: const Padding(
padding: EdgeInsets.only(right: 8),
: Padding(
padding: const EdgeInsets.only(right: 8),
child: Icon(
Icons.search,
color: AppColors.textSecondary,
color: palette.textSecondary,
size: 18,
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.borderStrong,
color: palette.border,
width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.borderStrong,
color: palette.border,
width: 0.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.searchFocusBorder,
width: 0.5,
color: palette.accent,
width: 0.6,
),
),
filled: true,
fillColor: AppColors.fill,
fillColor: palette.fill,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
@@ -156,8 +156,8 @@ class _SearchAppBarState extends State<SearchAppBar> {
alignment: Alignment.centerLeft,
child: Text(
widget.titleText ?? '',
style: const TextStyle(
color: AppColors.textPrimary,
style: TextStyle(
color: palette.textPrimary,
fontSize: 18,
fontWeight: FontWeight.w600,
),
+12 -14
View File
@@ -1,5 +1,5 @@
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';
class SyncStatusIndicator extends StatelessWidget {
@@ -87,16 +87,14 @@ class SyncStatusIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
switch (status) {
case SyncStatus.idle:
return Tooltip(
message: _messageForStatus(),
child: _buildIndicator(
const Icon(
Icons.cloud_outlined,
size: 16,
color: AppColors.textSubtle,
),
Icon(Icons.cloud_outlined, size: 16, color: palette.textSecondary),
),
);
@@ -106,7 +104,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator(
_buildStatusBadge(
icon: Icons.sync,
color: AppColors.syncPreparing,
color: palette.syncPreparing,
determinate: false,
),
),
@@ -118,7 +116,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator(
_buildStatusBadge(
icon: Icons.cloud_upload_outlined,
color: AppColors.syncEncrypting,
color: palette.syncEncrypting,
determinate: true,
),
),
@@ -130,7 +128,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator(
_buildStatusBadge(
icon: Icons.cloud_upload,
color: AppColors.syncUploading,
color: palette.syncUploading,
determinate: false,
),
),
@@ -142,7 +140,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator(
_buildStatusBadge(
icon: Icons.cloud_sync_outlined,
color: AppColors.syncWaiting,
color: palette.syncWaiting,
determinate: false,
),
),
@@ -154,7 +152,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator(
_buildStatusBadge(
icon: Icons.cloud_download_outlined,
color: AppColors.syncDecrypting,
color: palette.syncDecrypting,
determinate: true,
),
),
@@ -166,7 +164,7 @@ class SyncStatusIndicator extends StatelessWidget {
child: _buildIndicator(
_buildStatusBadge(
icon: Icons.sync,
color: AppColors.syncWaiting,
color: palette.syncWaiting,
determinate: false,
),
),
@@ -176,7 +174,7 @@ class SyncStatusIndicator extends StatelessWidget {
return Tooltip(
message: _messageForStatus(),
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(
message: _messageForStatus(),
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());
expect(find.byType(MaterialApp), findsOneWidget);
expect(find.text('Mis Notas'), findsWidgets);
expect(find.text('Preparando el vault local...'), findsWidgets);
});
}