Feature: Add Settings module with PanelNavigator, AppSettingsView, and related components for nested navigation + theming
This commit is contained in:
183
finlog_app/app/lib/core/ui/panel.dart
Normal file
183
finlog_app/app/lib/core/ui/panel.dart
Normal file
@@ -0,0 +1,183 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
||||
/// Ermöglicht verschachtelte "Panels" ähnlich einem StackNavigator.
|
||||
/// Beispiel:
|
||||
/// ```dart
|
||||
/// PanelNavigator(
|
||||
/// rootBuilder: (_) => MyRootPanel(),
|
||||
/// )
|
||||
/// ```
|
||||
class PanelNavigator extends StatefulWidget {
|
||||
final WidgetBuilder rootBuilder;
|
||||
|
||||
const PanelNavigator({super.key, required this.rootBuilder});
|
||||
|
||||
@override
|
||||
State<PanelNavigator> createState() => _PanelNavigatorState();
|
||||
|
||||
/// Zugriff per InheritedWidget / Extension
|
||||
static PanelController of(BuildContext context) {
|
||||
final inherited = context
|
||||
.dependOnInheritedWidgetOfExactType<_PanelInherited>();
|
||||
assert(inherited != null, 'Kein PanelNavigator im Widget-Tree gefunden');
|
||||
return inherited!.controller;
|
||||
}
|
||||
}
|
||||
|
||||
class _PanelNavigatorState extends State<PanelNavigator> {
|
||||
late final PanelController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = PanelController(
|
||||
rootBuilder: widget.rootBuilder,
|
||||
onUpdate: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PanelInherited(
|
||||
controller: _controller,
|
||||
child: PageTransitionSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
reverse: _controller.navDirection == _NavDirection.back,
|
||||
transitionBuilder: (child, primary, secondary) {
|
||||
return SharedAxisTransition(
|
||||
animation: primary,
|
||||
secondaryAnimation: secondary,
|
||||
transitionType: SharedAxisTransitionType.horizontal,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: _controller.getCurrentPanel(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelController {
|
||||
final WidgetBuilder rootBuilder;
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
final List<_PanelEntry> _stack = [];
|
||||
_NavDirection _navDirection = _NavDirection.forward;
|
||||
int _version = 0;
|
||||
|
||||
PanelController({required this.rootBuilder, required this.onUpdate}) {
|
||||
_stack.add(_PanelEntry(title: null, builder: rootBuilder));
|
||||
}
|
||||
|
||||
_NavDirection get navDirection => _navDirection;
|
||||
|
||||
Widget getCurrentPanel(BuildContext context) {
|
||||
final entry = _stack.last;
|
||||
return KeyedSubtree(
|
||||
key: ValueKey('panel_v${_version}_${_stack.length}'),
|
||||
child: _PanelScaffold(
|
||||
title: entry.title,
|
||||
body: entry.builder(context),
|
||||
onBack: canPop ? pop : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool get canPop => _stack.length > 1;
|
||||
|
||||
void push(WidgetBuilder builder, {String? title}) {
|
||||
_navDirection = _NavDirection.forward;
|
||||
_stack.add(_PanelEntry(title: title, builder: builder));
|
||||
_version++;
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
void pop() {
|
||||
if (canPop) {
|
||||
_navDirection = _NavDirection.back;
|
||||
_stack.removeLast();
|
||||
_version++;
|
||||
onUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _NavDirection { forward, back }
|
||||
|
||||
class _PanelEntry {
|
||||
final String? title;
|
||||
final WidgetBuilder builder;
|
||||
|
||||
_PanelEntry({this.title, required this.builder});
|
||||
}
|
||||
|
||||
class _PanelScaffold extends StatelessWidget {
|
||||
final String? title;
|
||||
final Widget body;
|
||||
final VoidCallback? onBack;
|
||||
|
||||
const _PanelScaffold({required this.title, required this.body, this.onBack});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
if (title != null) PanelHeader(title: title!, onBack: onBack),
|
||||
Expanded(
|
||||
child: Padding(padding: const EdgeInsets.all(15), child: body),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PanelInherited extends InheritedWidget {
|
||||
final PanelController controller;
|
||||
|
||||
const _PanelInherited({required super.child, required this.controller});
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_PanelInherited oldWidget) =>
|
||||
controller != oldWidget.controller;
|
||||
}
|
||||
|
||||
/// ---------- PUBLIC UI COMPONENTS ----------
|
||||
|
||||
class PanelHeader extends StatelessWidget {
|
||||
final String title;
|
||||
final VoidCallback? onBack;
|
||||
|
||||
const PanelHeader({super.key, required this.title, this.onBack});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Material(
|
||||
color: scheme.surface,
|
||||
elevation: 1,
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: Row(
|
||||
children: [
|
||||
if (onBack != null)
|
||||
IconButton(icon: const Icon(Icons.arrow_back), onPressed: onBack),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ---------- EXTENSIONS ----------
|
||||
|
||||
extension PanelContext on BuildContext {
|
||||
PanelController get panels => PanelNavigator.of(this);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import 'package:app/modules/settings/modules/app/model/app_settings_view_model.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AppSettingsView extends StatelessWidget {
|
||||
const AppSettingsView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => AppSettingsViewModel()..load(),
|
||||
child: const _AppSettingsContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppSettingsContent extends StatelessWidget {
|
||||
const _AppSettingsContent();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
final isLoading = vm.isLoading;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'App-Einstellungen'),
|
||||
if (isLoading)
|
||||
const Expanded(child: Center(child: CircularProgressIndicator()))
|
||||
else
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
_SystemBackgroundSection(),
|
||||
const SizedBox(height: 16),
|
||||
_TextSizeSection(),
|
||||
const SizedBox(height: 16),
|
||||
_LanguageSection(),
|
||||
const SizedBox(height: 24),
|
||||
_SaveButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SystemBackgroundSection extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
// A few pleasant presets; extend as needed.
|
||||
final presets = <Color>[
|
||||
const Color(0xFFFFFFFF),
|
||||
const Color(0xFFF5F5F5),
|
||||
const Color(0xFF121212),
|
||||
const Color(0xFF1E1E1E),
|
||||
Colors.blueGrey.shade50,
|
||||
Colors.blueGrey.shade900,
|
||||
];
|
||||
|
||||
final isSystem = vm.backgroundColorSystem == null;
|
||||
final selected = vm.backgroundColorSystem;
|
||||
|
||||
Widget chip({
|
||||
required Widget child,
|
||||
required bool selected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(
|
||||
color: selected ? scheme.primary : scheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'System-Hintergrundfarbe',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
chip(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.phone_android, size: 18),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Systemstandard',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
selected: isSystem,
|
||||
onTap: () => vm.setSystemBackgroundColor(null),
|
||||
),
|
||||
...presets.map((c) {
|
||||
final bool sel = selected == c;
|
||||
return GestureDetector(
|
||||
onTap: () => vm.setSystemBackgroundColor(c),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: c,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: sel ? scheme.primary : scheme.outlineVariant,
|
||||
width: sel ? 2 : 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
isSystem
|
||||
? 'Aktuell: Systemstandard'
|
||||
: 'Aktuell: Benutzerdefinierte Farbe',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: scheme.outline),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TextSizeSection extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
final selected = vm.textSize;
|
||||
|
||||
Widget radio(AppTextSize size, String label, String sub) {
|
||||
return RadioListTile<AppTextSize>(
|
||||
value: size,
|
||||
groupValue: selected,
|
||||
onChanged: (v) => vm.setTextSize(v ?? AppTextSize.normal),
|
||||
title: Text(label),
|
||||
subtitle: Text(sub),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Textgröße', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
radio(AppTextSize.small, 'Klein', 'Kompakte Darstellung'),
|
||||
radio(AppTextSize.normal, 'Standard', 'Empfohlene Einstellung'),
|
||||
radio(AppTextSize.large, 'Groß', 'Größere, besser lesbare Texte'),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LanguageSection extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Sprache', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<AppLanguage>(
|
||||
value: vm.language,
|
||||
onChanged: (v) => vm.setLanguage(v ?? AppLanguage.de),
|
||||
items: const [
|
||||
DropdownMenuItem(value: AppLanguage.de, child: Text('Deutsch')),
|
||||
DropdownMenuItem(value: AppLanguage.en, child: Text('Englisch')),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Änderungen wirken sich nach dem Speichern aus.',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: scheme.outline),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SaveButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
|
||||
return FilledButton.icon(
|
||||
onPressed: vm.isSaving
|
||||
? null
|
||||
: () async {
|
||||
await vm.save();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Einstellungen gespeichert')),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: vm.isSaving
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.save_outlined),
|
||||
label: const Text('Speichern'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Domain-ish enums for clarity and easy (de)serialization.
|
||||
enum AppTextSize { small, normal, large }
|
||||
|
||||
enum AppLanguage { de, en }
|
||||
|
||||
class AppSettingsViewModel extends ChangeNotifier {
|
||||
/// Use `null` to represent "system default background".
|
||||
Color? _backgroundColorSystem;
|
||||
AppTextSize _textSize = AppTextSize.normal;
|
||||
AppLanguage _language = AppLanguage.de;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isSaving = false;
|
||||
|
||||
Color? get backgroundColorSystem => _backgroundColorSystem;
|
||||
|
||||
AppTextSize get textSize => _textSize;
|
||||
|
||||
AppLanguage get language => _language;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
bool get isSaving => _isSaving;
|
||||
|
||||
/// Pretend to load from backend. Plug your repository here later.
|
||||
Future<void> load() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
// TODO: Replace with real backend call.
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
// Example defaults (could come from server response).
|
||||
_backgroundColorSystem = null; // null => Systemstandard
|
||||
_textSize = AppTextSize.normal;
|
||||
_language = AppLanguage.de;
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Save to backend (stub).
|
||||
Future<void> save() async {
|
||||
if (_isSaving) return;
|
||||
_isSaving = true;
|
||||
notifyListeners();
|
||||
|
||||
// TODO: Replace with real backend update.
|
||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
_isSaving = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setSystemBackgroundColor(Color? colorOrNullForSystem) {
|
||||
_backgroundColorSystem = colorOrNullForSystem;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setTextSize(AppTextSize size) {
|
||||
_textSize = size;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setLanguage(AppLanguage lang) {
|
||||
_language = lang;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'backgroundColorSystem': _backgroundColorSystem?.value,
|
||||
// null => system
|
||||
'textSize': _textSize.name,
|
||||
'language': _language.name,
|
||||
};
|
||||
|
||||
void fromJson(Map<String, dynamic> json) {
|
||||
final int? colorValue = json['backgroundColorSystem'] as int?;
|
||||
_backgroundColorSystem = colorValue != null ? Color(colorValue) : null;
|
||||
|
||||
final ts = json['textSize'] as String?;
|
||||
_textSize = AppTextSize.values.firstWhere(
|
||||
(e) => e.name == ts,
|
||||
orElse: () => AppTextSize.normal,
|
||||
);
|
||||
|
||||
final lng = json['language'] as String?;
|
||||
_language = AppLanguage.values.firstWhere(
|
||||
(e) => e.name == lng,
|
||||
orElse: () => AppLanguage.de,
|
||||
);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:app/core/ui/panel.dart';
|
||||
import 'package:app/modules/settings/modules/app/app_settings_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsView extends StatelessWidget {
|
||||
@@ -5,6 +7,264 @@ class SettingsView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(child: Text('Settings'));
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Einstellungen')),
|
||||
body: PanelNavigator(rootBuilder: (ctx) => _CategoryList()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ----------------- Root panel: Category list -----------------
|
||||
class _CategoryList extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
Widget tile(IconData icon, String label, Widget Function() detail) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
onTap: () => context.panels.push((_) => detail(), title: label),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(label)),
|
||||
Icon(Icons.chevron_right, color: scheme.outline),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const _SectionHeader('App-Einstellungen'),
|
||||
tile(Icons.tune, 'App-Einstellungen', () => const AppSettingsView()),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const _SectionHeader('Meine Daten'),
|
||||
tile(
|
||||
Icons.badge_outlined,
|
||||
'Persönliche Daten',
|
||||
() => const _PersonalPanel(),
|
||||
),
|
||||
tile(
|
||||
Icons.manage_accounts_outlined,
|
||||
'Kontoverwaltung',
|
||||
() => const _AccountPanel(),
|
||||
),
|
||||
tile(
|
||||
Icons.security_outlined,
|
||||
'Sicherheit',
|
||||
() => const _SecurityPanel(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const _SectionHeader('Hilfe'),
|
||||
tile(Icons.help_outline, 'Hilfe', () => const _HelpPanel()),
|
||||
tile(Icons.feedback_outlined, 'Feedback', () => const _FeedbackPanel()),
|
||||
tile(
|
||||
Icons.gavel_outlined,
|
||||
'Rechtliches & Datenschutz',
|
||||
() => const _LegalPanel(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: const Text('Abmelden'),
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Logout… (noch nicht implementiert)'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const _SectionHeader(this.text);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 6),
|
||||
child: Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: .2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// ----------------- Detail panels (unchanged) -----------------
|
||||
|
||||
class _PersonalPanel extends StatelessWidget {
|
||||
const _PersonalPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'Persönliche Daten'),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
ListTile(
|
||||
leading: Icon(Icons.person_outline),
|
||||
title: Text('Name'),
|
||||
subtitle: Text('Max Mustermann'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountPanel extends StatelessWidget {
|
||||
const _AccountPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'Kontoverwaltung'),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
ListTile(
|
||||
leading: Icon(Icons.alternate_email),
|
||||
title: Text('E-Mail'),
|
||||
subtitle: Text('max@example.com'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SecurityPanel extends StatelessWidget {
|
||||
const _SecurityPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'Sicherheit'),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
ListTile(
|
||||
leading: Icon(Icons.lock_outline),
|
||||
title: Text('Passwort ändern'),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.phonelink_lock),
|
||||
title: Text('2-Faktor-Authentifizierung'),
|
||||
subtitle: Text('Aus'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HelpPanel extends StatelessWidget {
|
||||
const _HelpPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'Hilfe'),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
ListTile(leading: Icon(Icons.help_outline), title: Text('FAQ')),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FeedbackPanel extends StatelessWidget {
|
||||
const _FeedbackPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'Feedback'),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
ListTile(
|
||||
leading: Icon(Icons.feedback_outlined),
|
||||
title: Text('Feedback senden'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LegalPanel extends StatelessWidget {
|
||||
const _LegalPanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// const PanelHeader(title: 'Rechtliches & Datenschutz'),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
ListTile(
|
||||
leading: Icon(Icons.privacy_tip_outlined),
|
||||
title: Text('Datenschutz'),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.article_outlined),
|
||||
title: Text('Nutzungsbedingungen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user