Feat: Passt App für kleinere Bildschirme an

This commit is contained in:
2026-01-05 17:22:08 +01:00
parent a535603924
commit 531e819c69
4 changed files with 207 additions and 101 deletions

View File

@@ -20,27 +20,29 @@ class Dashboard extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: <Widget>[ crossAxisAlignment: CrossAxisAlignment.start,
CurrentBalance(), children: <Widget>[
SizedBox(height: 32), CurrentBalance(),
SizedBox(height: 32),
Text( Text(
'Kontostand pro Monat', 'Kontostand pro Monat',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
SizedBox(height: 12), SizedBox(height: 12),
MonthlyBalanceChart(), MonthlyBalanceChart(),
SizedBox(height: 32), SizedBox(height: 32),
Text( Text(
'Letzte Transaktionen', 'Letzte Transaktionen',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
SizedBox(height: 12), SizedBox(height: 12),
RecentTransactionsList(), RecentTransactionsList(),
], ],
),
), ),
), ),
), ),

View File

@@ -111,17 +111,24 @@ class DynamicDialog {
), ),
], ],
), ),
content: Column( content: ConstrainedBox(
mainAxisSize: MainAxisSize.min, constraints: BoxConstraints(
children: [ maxHeight: MediaQuery.of(ctx).size.height * 0.7,
if (content != null) content!, ),
...inputFields.map( child: SingleChildScrollView(
(final DialogInputField field) => Padding( child: Column(
padding: const EdgeInsets.symmetric(vertical: 6), mainAxisSize: MainAxisSize.min,
child: _getInputField(field, primaryAction), children: [
), if (content != null) content!,
...inputFields.map(
(final DialogInputField field) => Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: _getInputField(field, primaryAction),
),
),
],
), ),
], ),
), ),
actions: actions actions: actions
.map( .map(

View File

@@ -70,6 +70,7 @@ class _MonthlyBalanceChart extends State<MonthlyBalanceChart> {
final AsyncSnapshot<List<Map<String, dynamic>>> snapshot, final AsyncSnapshot<List<Map<String, dynamic>>> snapshot,
) { ) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final MediaQueryData mediaQuery = MediaQuery.of(context);
if (snapshot.hasData) { if (snapshot.hasData) {
final List<Map<String, dynamic>> monthlyBalances = snapshot.data!; final List<Map<String, dynamic>> monthlyBalances = snapshot.data!;
@@ -109,7 +110,15 @@ class _MonthlyBalanceChart extends State<MonthlyBalanceChart> {
final DateTime date = value['date']; final DateTime date = value['date'];
final DateFormat format = DateFormat('MMMM'); final DateFormat format = DateFormat('MMMM');
return format.format(date); String month = format.format(date);
if (mediaQuery.size.width < 470) {
month = month.substring(0, 1);
} else if (mediaQuery.size.width < 920) {
month = month.substring(0, 3);
}
return month;
}).toList(); }).toList();
if (value.toInt() >= 0 && if (value.toInt() >= 0 &&

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
@@ -41,6 +43,8 @@ class _InputFields extends State<InputFields> {
final TextEditingController _amountMaxController = TextEditingController(); final TextEditingController _amountMaxController = TextEditingController();
final TextEditingController _dateTimeController = TextEditingController(); final TextEditingController _dateTimeController = TextEditingController();
static const double _filterBreakpoint = 600;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -83,81 +87,165 @@ class _InputFields extends State<InputFields> {
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
return Row( return LayoutBuilder(
children: <Widget>[ builder: (final context, final constraints) {
Expanded( if (constraints.maxWidth < _filterBreakpoint) {
child: TextField( return _buildFilterButton(context, theme);
controller: _nameController, }
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
onChanged: (final String value) {
_updateUrl();
},
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _amountMinController,
decoration: const InputDecoration(
labelText: 'Min Betrag €',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: (final String value) {
_updateUrl();
},
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _amountMaxController,
decoration: const InputDecoration(
labelText: 'Max Betrag €',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: (final String value) {
_updateUrl();
},
),
),
const SizedBox(width: 8),
Expanded(
child: DateRangePicker(
controller: _dateTimeController,
onChanged: (final DateTimeRange? value) {
_updateUrl();
},
decoration: InputDecoration(
labelText: 'Zeitraum',
border: const OutlineInputBorder(),
suffixIcon: Icon(
Icons.calendar_month,
color: theme.colorScheme.onSurface,
),
),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
_nameController.text = '';
_amountMinController.text = '';
_amountMaxController.text = '';
_dateTimeController.text = '';
_updateUrl(); return _buildInlineFilters(theme);
}, },
icon: const Icon(Icons.clear),
),
],
); );
} }
Widget _buildInlineFilters(final ThemeData theme) => Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
onChanged: (_) => _updateUrl(),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _amountMinController,
decoration: const InputDecoration(
labelText: 'Min Betrag €',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: (_) => _updateUrl(),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _amountMaxController,
decoration: const InputDecoration(
labelText: 'Max Betrag €',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: (_) => _updateUrl(),
),
),
const SizedBox(width: 8),
Expanded(
child: DateRangePicker(
controller: _dateTimeController,
onChanged: (_) => _updateUrl(),
decoration: InputDecoration(
labelText: 'Zeitraum',
border: const OutlineInputBorder(),
suffixIcon: Icon(
Icons.calendar_month,
color: theme.colorScheme.onSurface,
),
),
),
),
const SizedBox(width: 8),
IconButton(onPressed: _clear, icon: const Icon(Icons.clear)),
],
);
Widget _buildFilterButton(
final BuildContext context,
final ThemeData theme,
) => Align(
alignment: Alignment.centerLeft,
child: ElevatedButton.icon(
icon: const Icon(Icons.filter_alt),
label: const Text('Filter'),
onPressed: () => _openFilterDialog(context),
),
);
void _openFilterDialog(final BuildContext context) {
unawaited(
showDialog(
context: context,
builder: (final context) => AlertDialog(
title: const Text('Filter'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _amountMinController,
decoration: const InputDecoration(
labelText: 'Min Betrag €',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
),
const SizedBox(height: 12),
TextField(
controller: _amountMaxController,
decoration: const InputDecoration(
labelText: 'Max Betrag €',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
),
const SizedBox(height: 12),
DateRangePicker(
controller: _dateTimeController,
onChanged: (_) {},
decoration: const InputDecoration(
labelText: 'Zeitraum',
border: OutlineInputBorder(),
),
),
],
),
),
actions: [
TextButton(
onPressed: () {
_clear();
Navigator.of(context).pop();
},
child: const Text('Zurücksetzen'),
),
ElevatedButton(
onPressed: () {
_updateUrl();
Navigator.of(context).pop();
},
child: const Text('Anwenden'),
),
],
),
),
);
}
void _clear() {
_nameController.clear();
_amountMinController.clear();
_amountMaxController.clear();
_dateTimeController.clear();
_updateUrl();
}
void _updateUrl() { void _updateUrl() {
final params = <String, String>{ final params = <String, String>{
if (_nameController.text != '') 'name': _nameController.text, if (_nameController.text != '') 'name': _nameController.text,