search on budget + transactions; extended FAB for add tx; M3 expressive SearchBar
This commit is contained in:
parent
993e4f62a1
commit
83523b1811
25
lib/app.dart
25
lib/app.dart
|
|
@ -93,7 +93,7 @@ class _YnabAppState extends ConsumerState<YnabApp> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
FilledButton.tonal(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
child: const Text('Annuler'),
|
child: const Text('Annuler'),
|
||||||
),
|
),
|
||||||
|
|
@ -139,14 +139,12 @@ class _YnabAppState extends ConsumerState<YnabApp> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: _searchActive
|
title: _searchActive
|
||||||
? TextField(
|
? SearchBar(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
autofocus: true,
|
hintText: 'Rechercher…',
|
||||||
decoration: const InputDecoration(
|
leading: const Icon(Icons.search, size: 20),
|
||||||
hintText: 'Rechercher…',
|
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 4)),
|
||||||
border: InputBorder.none,
|
constraints: BoxConstraints.tightFor(height: 44),
|
||||||
isDense: true,
|
|
||||||
),
|
|
||||||
onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v,
|
onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v,
|
||||||
)
|
)
|
||||||
: Text(_navItems[selectedIndex].label),
|
: Text(_navItems[selectedIndex].label),
|
||||||
|
|
@ -158,13 +156,13 @@ class _YnabAppState extends ConsumerState<YnabApp> {
|
||||||
tooltip: 'Fermer la recherche',
|
tooltip: 'Fermer la recherche',
|
||||||
)
|
)
|
||||||
else ...[
|
else ...[
|
||||||
if (selectedIndex == 0 || selectedIndex == 3)
|
if (selectedIndex == 0)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.add, color: colorScheme.primary),
|
icon: Icon(Icons.add, color: colorScheme.primary),
|
||||||
onPressed: _addTransaction,
|
onPressed: _addTransaction,
|
||||||
tooltip: 'Ajouter une transaction',
|
tooltip: 'Ajouter une transaction',
|
||||||
),
|
),
|
||||||
if (selectedIndex == 2 || selectedIndex == 3)
|
if (selectedIndex >= 1)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.search, color: colorScheme.onSurfaceVariant),
|
icon: Icon(Icons.search, color: colorScheme.onSurfaceVariant),
|
||||||
onPressed: _toggleSearch,
|
onPressed: _toggleSearch,
|
||||||
|
|
@ -177,6 +175,13 @@ class _YnabAppState extends ConsumerState<YnabApp> {
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
child: _screens[selectedIndex],
|
child: _screens[selectedIndex],
|
||||||
),
|
),
|
||||||
|
floatingActionButton: selectedIndex == 3
|
||||||
|
? FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Transaction'),
|
||||||
|
onPressed: _addTransaction,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
selectedIndex: selectedIndex,
|
selectedIndex: selectedIndex,
|
||||||
onDestinationSelected: (int index) {
|
onDestinationSelected: (int index) {
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ class _AccountCard extends ConsumerWidget {
|
||||||
child: const Text('Supprimer'),
|
child: const Text('Supprimer'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
TextButton(
|
FilledButton.tonal(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
child: const Text('Annuler'),
|
child: const Text('Annuler'),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,25 @@ class BudgetScreen extends ConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final groups = ref.watch(categoryGroupsProvider).valueOrNull ?? [];
|
final groups = ref.watch(categoryGroupsProvider).valueOrNull ?? [];
|
||||||
final catsByGroup = ref.watch(categoriesByGroupProvider);
|
final catsByGroup = ref.watch(categoriesByGroupProvider);
|
||||||
|
final query = ref.watch(searchQueryProvider).toLowerCase();
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final months = ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
final months = ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
||||||
final monthLabel = '${months[now.month]} ${now.year}';
|
final monthLabel = '${months[now.month]} ${now.year}';
|
||||||
|
|
||||||
|
// filter categories and groups by search query
|
||||||
|
final filteredGroups = query.isEmpty
|
||||||
|
? groups
|
||||||
|
: groups.where((g) {
|
||||||
|
final cats = catsByGroup[g.id] ?? [];
|
||||||
|
return cats.any((c) => c.name.toLowerCase().contains(query));
|
||||||
|
}).toList();
|
||||||
|
final filteredCatsByGroup = query.isEmpty
|
||||||
|
? catsByGroup
|
||||||
|
: catsByGroup.map((key, value) => MapEntry(
|
||||||
|
key,
|
||||||
|
value.where((c) => c.name.toLowerCase().contains(query)).toList(),
|
||||||
|
));
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -33,8 +48,8 @@ class BudgetScreen extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
...groups.map((group) {
|
...filteredGroups.map((group) {
|
||||||
final cats = catsByGroup[group.id] ?? [];
|
final cats = filteredCatsByGroup[group.id] ?? [];
|
||||||
if (cats.isEmpty) return const SizedBox.shrink();
|
if (cats.isEmpty) return const SizedBox.shrink();
|
||||||
return _CategoryGroupTile(group: group, categoryIds: cats.map((c) => c.id).toList());
|
return _CategoryGroupTile(group: group, categoryIds: cats.map((c) => c.id).toList());
|
||||||
}),
|
}),
|
||||||
|
|
@ -119,7 +134,7 @@ class _CategoryRow extends ConsumerWidget {
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Annuler')),
|
FilledButton.tonal(onPressed: () => Navigator.pop(ctx), child: const Text('Annuler')),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final v = double.tryParse(ctrl.text.trim().replaceAll(',', '.'));
|
final v = double.tryParse(ctrl.text.trim().replaceAll(',', '.'));
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class DashboardScreen extends ConsumerWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('Transactions récentes', style: Theme.of(context).textTheme.titleMedium),
|
Text('Transactions récentes', style: Theme.of(context).textTheme.titleMedium),
|
||||||
TextButton.icon(
|
FilledButton.tonalIcon(
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
icon: const Icon(Icons.arrow_forward_ios, size: 14),
|
icon: const Icon(Icons.arrow_forward_ios, size: 14),
|
||||||
label: const Text('Voir tout'),
|
label: const Text('Voir tout'),
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,17 @@ final ThemeData ynabTheme = ThemeData(
|
||||||
backgroundColor: kSurfaceCard,
|
backgroundColor: kSurfaceCard,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
),
|
),
|
||||||
|
searchBarTheme: SearchBarThemeData(
|
||||||
|
elevation: WidgetStatePropertyAll(0),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(kSurfaceCard),
|
||||||
|
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
side: BorderSide(color: kDivider),
|
||||||
|
)),
|
||||||
|
padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 8)),
|
||||||
|
textStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontSize: 14, color: kTextPrimary)),
|
||||||
|
hintStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontSize: 14, color: kTextSecondary)),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final ThemeData ynabDarkTheme = ThemeData(
|
final ThemeData ynabDarkTheme = ThemeData(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue