Feature: Add feature toggles and settings for modular features (e.g., Car, Inventory), enhance navigation for mobile/desktop, and improve i18n integration.
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Einstellungen",
|
"title": "Einstellungen",
|
||||||
|
"featureSettings": "Funktionseinstellungen",
|
||||||
"sections": {
|
"sections": {
|
||||||
"account": "Konto & Daten",
|
"account": "Konto & Daten",
|
||||||
"app": "App",
|
"app": "App",
|
||||||
@@ -76,5 +77,23 @@
|
|||||||
"privacy": "Datenschutz",
|
"privacy": "Datenschutz",
|
||||||
"termsOfService": "Nutzungsbedingungen"
|
"termsOfService": "Nutzungsbedingungen"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"inventory": {
|
||||||
|
"displayName": "Inventar",
|
||||||
|
"description": "Verwaltet Gegenstände, Kategorien und Lagerorte."
|
||||||
|
},
|
||||||
|
"car": {
|
||||||
|
"displayName": "Auto",
|
||||||
|
"description": "KFZ-Tracking (Tanken, Wartung, Kosten, Kilometer)."
|
||||||
|
},
|
||||||
|
"household": {
|
||||||
|
"displayName": "Haushalt (inkl. Budget)",
|
||||||
|
"description": "Haushaltsfunktionen inkl. Budgetplanung."
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"displayName": "Berichte",
|
||||||
|
"description": "Statistiken von Ausgaben"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
|
"featureSettings": "Feature Settings",
|
||||||
"sections": {
|
"sections": {
|
||||||
"account": "Account & Data",
|
"account": "Account & Data",
|
||||||
"app": "App",
|
"app": "App",
|
||||||
@@ -76,5 +77,23 @@
|
|||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"termsOfService": "Terms of Service"
|
"termsOfService": "Terms of Service"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"inventory": {
|
||||||
|
"displayName": "Inventory",
|
||||||
|
"description": "Manages items, categories and storage locations."
|
||||||
|
},
|
||||||
|
"car": {
|
||||||
|
"displayName": "Car",
|
||||||
|
"description": "Vehicle tracking (fuel, maintenance, costs, mileage)."
|
||||||
|
},
|
||||||
|
"household": {
|
||||||
|
"displayName": "Household (incl. Budget)",
|
||||||
|
"description": "Household functions including budget planning."
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"displayName": "Reports",
|
||||||
|
"description": "Statistics of expenses"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:app/modules/app_shell.dart';
|
import 'package:app/modules/app_shell.dart';
|
||||||
import 'package:app/modules/budget/budget_view.dart';
|
import 'package:app/modules/budget/budget_view.dart';
|
||||||
|
import 'package:app/modules/car/car_view.dart';
|
||||||
import 'package:app/modules/dashboard/dashboard_view.dart';
|
import 'package:app/modules/dashboard/dashboard_view.dart';
|
||||||
import 'package:app/modules/login/pages/login_page.dart';
|
import 'package:app/modules/login/pages/login_page.dart';
|
||||||
import 'package:app/modules/settings/settings_view.dart';
|
import 'package:app/modules/settings/settings_view.dart';
|
||||||
@@ -15,7 +16,8 @@ enum AppRoute {
|
|||||||
budget('/budget'),
|
budget('/budget'),
|
||||||
expenses('/expenses'),
|
expenses('/expenses'),
|
||||||
reports('/reports'),
|
reports('/reports'),
|
||||||
settings('/settings');
|
settings('/settings'),
|
||||||
|
car('/car');
|
||||||
|
|
||||||
const AppRoute(this.path);
|
const AppRoute(this.path);
|
||||||
|
|
||||||
@@ -95,6 +97,7 @@ GoRouter buildAppRouter(AppRoute initialRoute) {
|
|||||||
(ctx, st) =>
|
(ctx, st) =>
|
||||||
const Center(child: Text('Reports – Auswertungen & Diagramme')),
|
const Center(child: Text('Reports – Auswertungen & Diagramme')),
|
||||||
),
|
),
|
||||||
|
_r(AppRoute.car, (ctx, st) => CarView()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
116
finlog_app/app/lib/core/features/feature_controller.dart
Normal file
116
finlog_app/app/lib/core/features/feature_controller.dart
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttery/fluttery.dart';
|
||||||
|
import 'package:fluttery/preferences.dart';
|
||||||
|
import 'package:app/core/i18n/translations.g.dart';
|
||||||
|
|
||||||
|
/// Define all toggleable features here.
|
||||||
|
/// You can freely add more later.
|
||||||
|
enum AppFeature {
|
||||||
|
inventory,
|
||||||
|
car,
|
||||||
|
household, // incl. budget
|
||||||
|
reports,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppFeatureKey on AppFeature {
|
||||||
|
String get prefKey {
|
||||||
|
switch (this) {
|
||||||
|
case AppFeature.inventory:
|
||||||
|
return 'feature.inventory.enabled';
|
||||||
|
case AppFeature.car:
|
||||||
|
return 'feature.car.enabled';
|
||||||
|
case AppFeature.household:
|
||||||
|
return 'feature.household.enabled';
|
||||||
|
case AppFeature.reports:
|
||||||
|
return 'feature.reports.enabled';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional: default state if the pref isn't set yet
|
||||||
|
bool get defaultEnabled {
|
||||||
|
switch (this) {
|
||||||
|
case AppFeature.inventory:
|
||||||
|
return true;
|
||||||
|
case AppFeature.car:
|
||||||
|
return false;
|
||||||
|
case AppFeature.household:
|
||||||
|
return true;
|
||||||
|
case AppFeature.reports:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Human-readable name for UI using translations
|
||||||
|
String displayName(BuildContext context) {
|
||||||
|
final t = Translations.of(context);
|
||||||
|
switch (this) {
|
||||||
|
case AppFeature.inventory:
|
||||||
|
return t.features.inventory.displayName;
|
||||||
|
case AppFeature.car:
|
||||||
|
return t.features.car.displayName;
|
||||||
|
case AppFeature.household:
|
||||||
|
return t.features.household.displayName;
|
||||||
|
case AppFeature.reports:
|
||||||
|
return t.features.reports.displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Description/help text shown below the switch using translations
|
||||||
|
String description(BuildContext context) {
|
||||||
|
final t = Translations.of(context);
|
||||||
|
switch (this) {
|
||||||
|
case AppFeature.inventory:
|
||||||
|
return t.features.inventory.description;
|
||||||
|
case AppFeature.car:
|
||||||
|
return t.features.car.description;
|
||||||
|
case AppFeature.household:
|
||||||
|
return t.features.household.description;
|
||||||
|
case AppFeature.reports:
|
||||||
|
return t.features.reports.description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional: an icon to show in list tiles (import material in the view)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controller pattern like your other controllers (ChangeNotifier + init)
|
||||||
|
class FeatureController extends ChangeNotifier {
|
||||||
|
final Preferences _prefs;
|
||||||
|
|
||||||
|
FeatureController() : _prefs = App.service<Preferences>();
|
||||||
|
|
||||||
|
/// In-memory cache of feature states
|
||||||
|
final Map<AppFeature, bool> _enabled = {
|
||||||
|
for (final f in AppFeature.values) f: f.defaultEnabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Call during app bootstrap (similar to other controllers).
|
||||||
|
Future<void> init() async {
|
||||||
|
for (final f in AppFeature.values) {
|
||||||
|
final v = await _prefs.getBool(f.prefKey);
|
||||||
|
_enabled[f] = v ?? true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnabled(AppFeature feature) =>
|
||||||
|
_enabled[feature] ?? feature.defaultEnabled;
|
||||||
|
|
||||||
|
/// Convenience map for UI bindings
|
||||||
|
Map<AppFeature, bool> get allStates => Map.unmodifiable(_enabled);
|
||||||
|
|
||||||
|
Future<void> setEnabled(AppFeature feature, bool value) async {
|
||||||
|
_enabled[feature] = value;
|
||||||
|
await _prefs.setBool(feature.prefKey, value);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional helper: filter routes/features in the app shell, etc.
|
||||||
|
bool get hasInventory => isEnabled(AppFeature.inventory);
|
||||||
|
|
||||||
|
bool get hasCar => isEnabled(AppFeature.car);
|
||||||
|
|
||||||
|
bool get hasHousehold => isEnabled(AppFeature.household);
|
||||||
|
|
||||||
|
bool get hasReports => isEnabled(AppFeature.reports);
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 2
|
/// Locales: 2
|
||||||
/// Strings: 104 (52 per locale)
|
/// Strings: 122 (61 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2025-09-27 at 10:12 UTC
|
/// Built on 2025-09-27 at 11:35 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint, unused_import
|
// ignore_for_file: type=lint, unused_import
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class TranslationsDe implements Translations {
|
|||||||
@override late final _TranslationsBudgetDe budget = _TranslationsBudgetDe._(_root);
|
@override late final _TranslationsBudgetDe budget = _TranslationsBudgetDe._(_root);
|
||||||
@override late final _TranslationsAppDe app = _TranslationsAppDe._(_root);
|
@override late final _TranslationsAppDe app = _TranslationsAppDe._(_root);
|
||||||
@override late final _TranslationsSettingsDe settings = _TranslationsSettingsDe._(_root);
|
@override late final _TranslationsSettingsDe settings = _TranslationsSettingsDe._(_root);
|
||||||
|
@override late final _TranslationsFeaturesDe features = _TranslationsFeaturesDe._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: login
|
// Path: login
|
||||||
@@ -105,6 +106,7 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
|||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get title => 'Einstellungen';
|
@override String get title => 'Einstellungen';
|
||||||
|
@override String get featureSettings => 'Funktionseinstellungen';
|
||||||
@override late final _TranslationsSettingsSectionsDe sections = _TranslationsSettingsSectionsDe._(_root);
|
@override late final _TranslationsSettingsSectionsDe sections = _TranslationsSettingsSectionsDe._(_root);
|
||||||
@override late final _TranslationsSettingsItemsDe items = _TranslationsSettingsItemsDe._(_root);
|
@override late final _TranslationsSettingsItemsDe items = _TranslationsSettingsItemsDe._(_root);
|
||||||
@override late final _TranslationsSettingsMessagesDe messages = _TranslationsSettingsMessagesDe._(_root);
|
@override late final _TranslationsSettingsMessagesDe messages = _TranslationsSettingsMessagesDe._(_root);
|
||||||
@@ -115,6 +117,19 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
|||||||
@override late final _TranslationsSettingsLegalDe legal = _TranslationsSettingsLegalDe._(_root);
|
@override late final _TranslationsSettingsLegalDe legal = _TranslationsSettingsLegalDe._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: features
|
||||||
|
class _TranslationsFeaturesDe implements TranslationsFeaturesEn {
|
||||||
|
_TranslationsFeaturesDe._(this._root);
|
||||||
|
|
||||||
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override late final _TranslationsFeaturesInventoryDe inventory = _TranslationsFeaturesInventoryDe._(_root);
|
||||||
|
@override late final _TranslationsFeaturesCarDe car = _TranslationsFeaturesCarDe._(_root);
|
||||||
|
@override late final _TranslationsFeaturesHouseholdDe household = _TranslationsFeaturesHouseholdDe._(_root);
|
||||||
|
@override late final _TranslationsFeaturesReportsDe reports = _TranslationsFeaturesReportsDe._(_root);
|
||||||
|
}
|
||||||
|
|
||||||
// Path: settings.sections
|
// Path: settings.sections
|
||||||
class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn {
|
class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn {
|
||||||
_TranslationsSettingsSectionsDe._(this._root);
|
_TranslationsSettingsSectionsDe._(this._root);
|
||||||
@@ -220,6 +235,50 @@ class _TranslationsSettingsLegalDe implements TranslationsSettingsLegalEn {
|
|||||||
@override String get termsOfService => 'Nutzungsbedingungen';
|
@override String get termsOfService => 'Nutzungsbedingungen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: features.inventory
|
||||||
|
class _TranslationsFeaturesInventoryDe implements TranslationsFeaturesInventoryEn {
|
||||||
|
_TranslationsFeaturesInventoryDe._(this._root);
|
||||||
|
|
||||||
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get displayName => 'Inventar';
|
||||||
|
@override String get description => 'Verwaltet Gegenstände, Kategorien und Lagerorte.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path: features.car
|
||||||
|
class _TranslationsFeaturesCarDe implements TranslationsFeaturesCarEn {
|
||||||
|
_TranslationsFeaturesCarDe._(this._root);
|
||||||
|
|
||||||
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get displayName => 'Auto';
|
||||||
|
@override String get description => 'KFZ-Tracking (Tanken, Wartung, Kosten, Kilometer).';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path: features.household
|
||||||
|
class _TranslationsFeaturesHouseholdDe implements TranslationsFeaturesHouseholdEn {
|
||||||
|
_TranslationsFeaturesHouseholdDe._(this._root);
|
||||||
|
|
||||||
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get displayName => 'Haushalt (inkl. Budget)';
|
||||||
|
@override String get description => 'Haushaltsfunktionen inkl. Budgetplanung.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path: features.reports
|
||||||
|
class _TranslationsFeaturesReportsDe implements TranslationsFeaturesReportsEn {
|
||||||
|
_TranslationsFeaturesReportsDe._(this._root);
|
||||||
|
|
||||||
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get displayName => 'Berichte';
|
||||||
|
@override String get description => 'Statistiken von Ausgaben';
|
||||||
|
}
|
||||||
|
|
||||||
/// Flat map(s) containing all translations.
|
/// Flat map(s) containing all translations.
|
||||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||||
extension on TranslationsDe {
|
extension on TranslationsDe {
|
||||||
@@ -244,6 +303,7 @@ extension on TranslationsDe {
|
|||||||
case 'app.tooltipExpandRail': return 'Leiste erweitern';
|
case 'app.tooltipExpandRail': return 'Leiste erweitern';
|
||||||
case 'app.drawerSettings': return 'Einstellungen';
|
case 'app.drawerSettings': return 'Einstellungen';
|
||||||
case 'settings.title': return 'Einstellungen';
|
case 'settings.title': return 'Einstellungen';
|
||||||
|
case 'settings.featureSettings': return 'Funktionseinstellungen';
|
||||||
case 'settings.sections.account': return 'Konto & Daten';
|
case 'settings.sections.account': return 'Konto & Daten';
|
||||||
case 'settings.sections.app': return 'App';
|
case 'settings.sections.app': return 'App';
|
||||||
case 'settings.sections.help': return 'Hilfe & Rechtliches';
|
case 'settings.sections.help': return 'Hilfe & Rechtliches';
|
||||||
@@ -277,6 +337,14 @@ extension on TranslationsDe {
|
|||||||
case 'settings.help.sendFeedback': return 'Feedback senden';
|
case 'settings.help.sendFeedback': return 'Feedback senden';
|
||||||
case 'settings.legal.privacy': return 'Datenschutz';
|
case 'settings.legal.privacy': return 'Datenschutz';
|
||||||
case 'settings.legal.termsOfService': return 'Nutzungsbedingungen';
|
case 'settings.legal.termsOfService': return 'Nutzungsbedingungen';
|
||||||
|
case 'features.inventory.displayName': return 'Inventar';
|
||||||
|
case 'features.inventory.description': return 'Verwaltet Gegenstände, Kategorien und Lagerorte.';
|
||||||
|
case 'features.car.displayName': return 'Auto';
|
||||||
|
case 'features.car.description': return 'KFZ-Tracking (Tanken, Wartung, Kosten, Kilometer).';
|
||||||
|
case 'features.household.displayName': return 'Haushalt (inkl. Budget)';
|
||||||
|
case 'features.household.description': return 'Haushaltsfunktionen inkl. Budgetplanung.';
|
||||||
|
case 'features.reports.displayName': return 'Berichte';
|
||||||
|
case 'features.reports.description': return 'Statistiken von Ausgaben';
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
|||||||
late final TranslationsBudgetEn budget = TranslationsBudgetEn._(_root);
|
late final TranslationsBudgetEn budget = TranslationsBudgetEn._(_root);
|
||||||
late final TranslationsAppEn app = TranslationsAppEn._(_root);
|
late final TranslationsAppEn app = TranslationsAppEn._(_root);
|
||||||
late final TranslationsSettingsEn settings = TranslationsSettingsEn._(_root);
|
late final TranslationsSettingsEn settings = TranslationsSettingsEn._(_root);
|
||||||
|
late final TranslationsFeaturesEn features = TranslationsFeaturesEn._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: login
|
// Path: login
|
||||||
@@ -148,6 +149,9 @@ class TranslationsSettingsEn {
|
|||||||
/// en: 'Settings'
|
/// en: 'Settings'
|
||||||
String get title => 'Settings';
|
String get title => 'Settings';
|
||||||
|
|
||||||
|
/// en: 'Feature Settings'
|
||||||
|
String get featureSettings => 'Feature Settings';
|
||||||
|
|
||||||
late final TranslationsSettingsSectionsEn sections = TranslationsSettingsSectionsEn._(_root);
|
late final TranslationsSettingsSectionsEn sections = TranslationsSettingsSectionsEn._(_root);
|
||||||
late final TranslationsSettingsItemsEn items = TranslationsSettingsItemsEn._(_root);
|
late final TranslationsSettingsItemsEn items = TranslationsSettingsItemsEn._(_root);
|
||||||
late final TranslationsSettingsMessagesEn messages = TranslationsSettingsMessagesEn._(_root);
|
late final TranslationsSettingsMessagesEn messages = TranslationsSettingsMessagesEn._(_root);
|
||||||
@@ -158,6 +162,19 @@ class TranslationsSettingsEn {
|
|||||||
late final TranslationsSettingsLegalEn legal = TranslationsSettingsLegalEn._(_root);
|
late final TranslationsSettingsLegalEn legal = TranslationsSettingsLegalEn._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: features
|
||||||
|
class TranslationsFeaturesEn {
|
||||||
|
TranslationsFeaturesEn._(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
late final TranslationsFeaturesInventoryEn inventory = TranslationsFeaturesInventoryEn._(_root);
|
||||||
|
late final TranslationsFeaturesCarEn car = TranslationsFeaturesCarEn._(_root);
|
||||||
|
late final TranslationsFeaturesHouseholdEn household = TranslationsFeaturesHouseholdEn._(_root);
|
||||||
|
late final TranslationsFeaturesReportsEn reports = TranslationsFeaturesReportsEn._(_root);
|
||||||
|
}
|
||||||
|
|
||||||
// Path: settings.sections
|
// Path: settings.sections
|
||||||
class TranslationsSettingsSectionsEn {
|
class TranslationsSettingsSectionsEn {
|
||||||
TranslationsSettingsSectionsEn._(this._root);
|
TranslationsSettingsSectionsEn._(this._root);
|
||||||
@@ -329,6 +346,66 @@ class TranslationsSettingsLegalEn {
|
|||||||
String get termsOfService => 'Terms of Service';
|
String get termsOfService => 'Terms of Service';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: features.inventory
|
||||||
|
class TranslationsFeaturesInventoryEn {
|
||||||
|
TranslationsFeaturesInventoryEn._(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Inventory'
|
||||||
|
String get displayName => 'Inventory';
|
||||||
|
|
||||||
|
/// en: 'Manages items, categories and storage locations.'
|
||||||
|
String get description => 'Manages items, categories and storage locations.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path: features.car
|
||||||
|
class TranslationsFeaturesCarEn {
|
||||||
|
TranslationsFeaturesCarEn._(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Car'
|
||||||
|
String get displayName => 'Car';
|
||||||
|
|
||||||
|
/// en: 'Vehicle tracking (fuel, maintenance, costs, mileage).'
|
||||||
|
String get description => 'Vehicle tracking (fuel, maintenance, costs, mileage).';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path: features.household
|
||||||
|
class TranslationsFeaturesHouseholdEn {
|
||||||
|
TranslationsFeaturesHouseholdEn._(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Household (incl. Budget)'
|
||||||
|
String get displayName => 'Household (incl. Budget)';
|
||||||
|
|
||||||
|
/// en: 'Household functions including budget planning.'
|
||||||
|
String get description => 'Household functions including budget planning.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path: features.reports
|
||||||
|
class TranslationsFeaturesReportsEn {
|
||||||
|
TranslationsFeaturesReportsEn._(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Reports'
|
||||||
|
String get displayName => 'Reports';
|
||||||
|
|
||||||
|
/// en: 'Statistics of expenses'
|
||||||
|
String get description => 'Statistics of expenses';
|
||||||
|
}
|
||||||
|
|
||||||
/// Flat map(s) containing all translations.
|
/// Flat map(s) containing all translations.
|
||||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||||
extension on Translations {
|
extension on Translations {
|
||||||
@@ -353,6 +430,7 @@ extension on Translations {
|
|||||||
case 'app.tooltipExpandRail': return 'Expand Rail';
|
case 'app.tooltipExpandRail': return 'Expand Rail';
|
||||||
case 'app.drawerSettings': return 'Settings';
|
case 'app.drawerSettings': return 'Settings';
|
||||||
case 'settings.title': return 'Settings';
|
case 'settings.title': return 'Settings';
|
||||||
|
case 'settings.featureSettings': return 'Feature Settings';
|
||||||
case 'settings.sections.account': return 'Account & Data';
|
case 'settings.sections.account': return 'Account & Data';
|
||||||
case 'settings.sections.app': return 'App';
|
case 'settings.sections.app': return 'App';
|
||||||
case 'settings.sections.help': return 'Help & Legal';
|
case 'settings.sections.help': return 'Help & Legal';
|
||||||
@@ -386,6 +464,14 @@ extension on Translations {
|
|||||||
case 'settings.help.sendFeedback': return 'Send Feedback';
|
case 'settings.help.sendFeedback': return 'Send Feedback';
|
||||||
case 'settings.legal.privacy': return 'Privacy';
|
case 'settings.legal.privacy': return 'Privacy';
|
||||||
case 'settings.legal.termsOfService': return 'Terms of Service';
|
case 'settings.legal.termsOfService': return 'Terms of Service';
|
||||||
|
case 'features.inventory.displayName': return 'Inventory';
|
||||||
|
case 'features.inventory.description': return 'Manages items, categories and storage locations.';
|
||||||
|
case 'features.car.displayName': return 'Car';
|
||||||
|
case 'features.car.description': return 'Vehicle tracking (fuel, maintenance, costs, mileage).';
|
||||||
|
case 'features.household.displayName': return 'Household (incl. Budget)';
|
||||||
|
case 'features.household.description': return 'Household functions including budget planning.';
|
||||||
|
case 'features.reports.displayName': return 'Reports';
|
||||||
|
case 'features.reports.description': return 'Statistics of expenses';
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:app/core/app/router.dart';
|
import 'package:app/core/app/router.dart';
|
||||||
import 'package:app/core/app/startup/domain/initialize_app.dart';
|
import 'package:app/core/app/startup/domain/initialize_app.dart';
|
||||||
|
import 'package:app/core/features/feature_controller.dart';
|
||||||
import 'package:app/core/i18n/translations.g.dart';
|
import 'package:app/core/i18n/translations.g.dart';
|
||||||
import 'package:app/core/ui/controller/locale_controller.dart';
|
import 'package:app/core/ui/controller/locale_controller.dart';
|
||||||
import 'package:app/core/ui/controller/scale_controller.dart';
|
import 'package:app/core/ui/controller/scale_controller.dart';
|
||||||
@@ -33,12 +34,24 @@ Future<void> main() async {
|
|||||||
final localeController = LocaleController();
|
final localeController = LocaleController();
|
||||||
await localeController.init();
|
await localeController.init();
|
||||||
|
|
||||||
|
final features = FeatureController();
|
||||||
|
await features.init();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => themeController),
|
ChangeNotifierProvider<ThemeController>(
|
||||||
ChangeNotifierProvider(create: (context) => scaleController),
|
create: (context) => themeController,
|
||||||
ChangeNotifierProvider(create: (context) => localeController),
|
),
|
||||||
|
ChangeNotifierProvider<ScaleController>(
|
||||||
|
create: (context) => scaleController,
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<LocaleController>(
|
||||||
|
create: (context) => localeController,
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<FeatureController>(
|
||||||
|
create: (context) => features,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: TranslationProvider(
|
child: TranslationProvider(
|
||||||
child: FinlogApp(router: buildAppRouter(startRoute)),
|
child: FinlogApp(router: buildAppRouter(startRoute)),
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ class AppShell extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppShellState extends State<AppShell> {
|
class _AppShellState extends State<AppShell> {
|
||||||
static const double _railBreakpoint = 800; // tablet and up
|
static const double _breakpoint = 800; // Tablet/Desktop threshold
|
||||||
bool _railExtended = true; // start "open" on tablet/desktop
|
|
||||||
final FocusNode _contentFocus = FocusNode(debugLabel: 'contentFocus');
|
final FocusNode _contentFocus = FocusNode(debugLabel: 'contentFocus');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -24,14 +23,16 @@ class _AppShellState extends State<AppShell> {
|
|||||||
|
|
||||||
// --- NAV ITEMS -------------------------------------------------------------
|
// --- NAV ITEMS -------------------------------------------------------------
|
||||||
|
|
||||||
List<({IconData icon, IconData? selectedIcon, String label, String route})> _getItems(BuildContext context) {
|
// Desktop/Tablet drawer items (you can keep them rich/longer here)
|
||||||
|
List<({IconData icon, IconData? selectedIcon, String label, String route})>
|
||||||
|
_getDesktopItems(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
return [
|
return [
|
||||||
(
|
(
|
||||||
icon: Icons.dashboard_outlined,
|
icon: Icons.dashboard_outlined,
|
||||||
selectedIcon: Icons.dashboard,
|
selectedIcon: Icons.dashboard,
|
||||||
label: t.app.navigationDashboard,
|
label: t.app.navigationDashboard,
|
||||||
route: '/home',
|
route: '/',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
icon: Icons.account_balance_wallet_outlined,
|
icon: Icons.account_balance_wallet_outlined,
|
||||||
@@ -52,41 +53,62 @@ class _AppShellState extends State<AppShell> {
|
|||||||
route: '/reports',
|
route: '/reports',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
icon: Icons.settings,
|
icon: Icons.settings_outlined,
|
||||||
selectedIcon: Icons.settings_outlined,
|
selectedIcon: Icons.settings,
|
||||||
label: t.app.navigationSettings,
|
label: t.app.navigationSettings,
|
||||||
route: '/settings',
|
route: '/settings',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
int _indexForPath(String p, List<({IconData icon, IconData? selectedIcon, String label, String route})> items) {
|
// Mobile bottom bar items (exactly the four you asked for)
|
||||||
|
List<({IconData icon, String label, String route})> _getMobileTabs(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
final t = Translations.of(context);
|
||||||
|
return [
|
||||||
|
(icon: Icons.dashboard, label: t.app.navigationDashboard, route: '/home'),
|
||||||
|
// “Haushalt (inkl. Budget)” → map to /budget for now
|
||||||
|
(icon: Icons.home, label: 'Haushalt', route: '/budget'),
|
||||||
|
(icon: Icons.inventory_2, label: 'Inventar', route: '/inventory'),
|
||||||
|
(icon: Icons.directions_car, label: 'Auto', route: '/car'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
int _indexForPath<T>(String path, List<T> items, String Function(T) routeOf) {
|
||||||
for (var i = 0; i < items.length; i++) {
|
for (var i = 0; i < items.length; i++) {
|
||||||
if (p.startsWith(items[i].route)) return i;
|
if (path.startsWith(routeOf(items[i]))) return i;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goForIndex(BuildContext ctx, int i, List<({IconData icon, IconData? selectedIcon, String label, String route})> items) => ctx.go(items[i].route);
|
void _goForIndex<T>(
|
||||||
|
BuildContext ctx,
|
||||||
|
int i,
|
||||||
|
List<T> items,
|
||||||
|
String Function(T) routeOf,
|
||||||
|
) {
|
||||||
|
ctx.go(routeOf(items[i]));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
final items = _getItems(context);
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
final isRail = width >= _railBreakpoint;
|
final isDesktop = width >= _breakpoint;
|
||||||
final currentPath = GoRouterState.of(context).matchedLocation;
|
final currentPath = GoRouterState.of(context).matchedLocation;
|
||||||
|
|
||||||
// keep focus on right/content pane on wide layouts
|
// keep focus on right/content pane on wide layouts
|
||||||
if (isRail) {
|
if (isDesktop) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted && !_contentFocus.hasFocus) _contentFocus.requestFocus();
|
if (mounted && !_contentFocus.hasFocus) _contentFocus.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final appBar = AppBar(
|
final appBar = AppBar(
|
||||||
title: _LogoHeader(),
|
title: const _LogoHeader(),
|
||||||
leading: isRail
|
// On desktop we use a persistent drawer, so no burger button.
|
||||||
|
leading: isDesktop
|
||||||
? null
|
? null
|
||||||
: Builder(
|
: Builder(
|
||||||
builder: (ctx) => IconButton(
|
builder: (ctx) => IconButton(
|
||||||
@@ -109,79 +131,42 @@ class _AppShellState extends State<AppShell> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isRail) {
|
if (!isDesktop) {
|
||||||
// ------------------- MOBILE: Drawer -------------------
|
// ------------------- MOBILE: Bottom Navigation -------------------
|
||||||
final selectedIndex = _indexForPath(currentPath, items);
|
final tabs = _getMobileTabs(context);
|
||||||
|
final selected = _indexForPath(currentPath, tabs, (it) => it.route);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
drawer: _AppDrawer(items: items, selectedIndex: selectedIndex),
|
// Keep drawer for mobile? You asked for bottom bar instead — remove drawer.
|
||||||
body: SafeArea(child: widget.child),
|
body: SafeArea(child: widget.child),
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
selectedIndex: selected,
|
||||||
|
onDestinationSelected: (i) =>
|
||||||
|
_goForIndex(context, i, tabs, (it) => it.route),
|
||||||
|
destinations: [
|
||||||
|
for (final it in tabs)
|
||||||
|
NavigationDestination(icon: Icon(it.icon), label: it.label),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------- TABLET/DESKTOP: NavigationRail -------------------
|
// ------------------- TABLET/DESKTOP: Persistent Drawer -------------------
|
||||||
final selected = _indexForPath(currentPath, items);
|
final items = _getDesktopItems(context);
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final selected = _indexForPath(currentPath, items, (it) => it.route);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
NavigationRailTheme(
|
// Persistent drawer area
|
||||||
data: NavigationRailThemeData(
|
SizedBox(
|
||||||
groupAlignment: -1.0,
|
width: 300,
|
||||||
// align to top
|
child: _DesktopDrawer(items: items, selectedIndex: selected),
|
||||||
useIndicator: true,
|
|
||||||
indicatorColor: scheme.secondaryContainer,
|
|
||||||
// background for selected "button"
|
|
||||||
indicatorShape: const StadiumBorder(),
|
|
||||||
selectedIconTheme: IconThemeData(
|
|
||||||
color: scheme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
selectedLabelTextStyle: TextStyle(
|
|
||||||
color: scheme.onSecondaryContainer,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: NavigationRail(
|
|
||||||
extended: _railExtended,
|
|
||||||
selectedIndex: selected,
|
|
||||||
onDestinationSelected: (i) => _goForIndex(context, i, items),
|
|
||||||
// leading: const Padding(
|
|
||||||
// padding: EdgeInsets.only(top: 8),
|
|
||||||
// child: _LogoHeader(),
|
|
||||||
// ),
|
|
||||||
destinations: [
|
|
||||||
for (final it in items)
|
|
||||||
NavigationRailDestination(
|
|
||||||
icon: Icon(it.icon),
|
|
||||||
selectedIcon: Icon(it.selectedIcon ?? it.icon),
|
|
||||||
label: Text(it.label),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
trailingAtBottom: true,
|
|
||||||
trailing: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Divider(height: 1),
|
|
||||||
// toggle lives at the very bottom so layout doesn't jump
|
|
||||||
IconButton(
|
|
||||||
tooltip: _railExtended
|
|
||||||
? t.app.tooltipCollapseRail
|
|
||||||
: t.app.tooltipExpandRail,
|
|
||||||
onPressed: () =>
|
|
||||||
setState(() => _railExtended = !_railExtended),
|
|
||||||
icon: Icon(
|
|
||||||
_railExtended
|
|
||||||
? Icons.keyboard_double_arrow_left
|
|
||||||
: Icons.keyboard_double_arrow_right,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const VerticalDivider(width: 1),
|
const VerticalDivider(width: 1),
|
||||||
|
// Content
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Focus(
|
child: Focus(
|
||||||
@@ -197,6 +182,49 @@ class _AppShellState extends State<AppShell> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DesktopDrawer extends StatelessWidget {
|
||||||
|
final List<
|
||||||
|
({IconData icon, IconData? selectedIcon, String label, String route})
|
||||||
|
>
|
||||||
|
items;
|
||||||
|
final int selectedIndex;
|
||||||
|
|
||||||
|
const _DesktopDrawer({required this.items, required this.selectedIndex});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
elevation: 0,
|
||||||
|
child: SafeArea(
|
||||||
|
child: ListTileTheme(
|
||||||
|
selectedColor: scheme.onSecondaryContainer,
|
||||||
|
selectedTileColor: scheme.secondaryContainer,
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
children: [
|
||||||
|
const _LogoHeader(),
|
||||||
|
const Divider(),
|
||||||
|
for (var i = 0; i < items.length; i++)
|
||||||
|
ListTile(
|
||||||
|
selected: i == selectedIndex,
|
||||||
|
leading: Icon(
|
||||||
|
i == selectedIndex
|
||||||
|
? (items[i].selectedIcon ?? items[i].icon)
|
||||||
|
: items[i].icon,
|
||||||
|
),
|
||||||
|
title: Text(items[i].label),
|
||||||
|
onTap: () => context.go(items[i].route),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _AppDrawer extends StatelessWidget {
|
class _AppDrawer extends StatelessWidget {
|
||||||
final List<
|
final List<
|
||||||
({IconData icon, IconData? selectedIcon, String label, String route})
|
({IconData icon, IconData? selectedIcon, String label, String route})
|
||||||
|
|||||||
10
finlog_app/app/lib/modules/car/car_view.dart
Normal file
10
finlog_app/app/lib/modules/car/car_view.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CarView extends StatelessWidget {
|
||||||
|
const CarView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Center(child: Text('Auto-Manager '));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:app/core/features/feature_controller.dart';
|
||||||
|
import 'package:app/modules/settings/modules/app/model/feature_settings_view_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:app/core/i18n/translations.g.dart';
|
||||||
|
|
||||||
|
/// A dedicated section under "Einstellungen" to enable/disable features.
|
||||||
|
/// You can link this screen from your existing Settings list.
|
||||||
|
/// If you keep a single Settings page, render _FeatureSettingsSection in-place.
|
||||||
|
class FeatureSettingsView extends StatelessWidget {
|
||||||
|
const FeatureSettingsView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = Translations.of(context);
|
||||||
|
final featureController = context.read<FeatureController>();
|
||||||
|
final model = FeatureSettingsViewModel(featureController);
|
||||||
|
|
||||||
|
return ChangeNotifierProvider(
|
||||||
|
create: (BuildContext context) => model,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(title: Text(t.settings.featureSettings)),
|
||||||
|
body: const _FeatureSettingsSection(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If you prefer to embed this into your existing AppSettingsView,
|
||||||
|
/// use this widget directly inside your Settings ListView/CustomScrollView.
|
||||||
|
class _FeatureSettingsSection extends StatelessWidget {
|
||||||
|
const _FeatureSettingsSection();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = context.watch<FeatureController>();
|
||||||
|
final states = controller.allStates;
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
itemCount: states.length,
|
||||||
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||||
|
itemBuilder: (ctx, i) {
|
||||||
|
final feature = states.keys.elementAt(i);
|
||||||
|
final enabled = states[feature] ?? feature.defaultEnabled;
|
||||||
|
|
||||||
|
final icon = _iconFor(feature);
|
||||||
|
|
||||||
|
return SwitchListTile(
|
||||||
|
value: enabled,
|
||||||
|
secondary: Icon(icon),
|
||||||
|
title: Text(feature.displayName(context)),
|
||||||
|
subtitle: Text(feature.description(context)),
|
||||||
|
onChanged: (v) => controller.setEnabled(feature, v),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData _iconFor(AppFeature f) {
|
||||||
|
switch (f) {
|
||||||
|
case AppFeature.inventory:
|
||||||
|
return Icons.inventory_2_outlined;
|
||||||
|
case AppFeature.car:
|
||||||
|
return Icons.directions_car;
|
||||||
|
case AppFeature.household:
|
||||||
|
return Icons.home_outlined;
|
||||||
|
case AppFeature.reports:
|
||||||
|
return Icons.bar_chart_outlined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:app/core/features/feature_controller.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Lightweight VM that wraps FeatureController for the Settings screen.
|
||||||
|
/// Mirrors your other *ViewModel classes’ init pattern.
|
||||||
|
class FeatureSettingsViewModel extends ChangeNotifier {
|
||||||
|
FeatureSettingsViewModel(this._featureController);
|
||||||
|
|
||||||
|
final FeatureController _featureController;
|
||||||
|
|
||||||
|
Map<AppFeature, bool> get states => _featureController.allStates;
|
||||||
|
|
||||||
|
bool isEnabled(AppFeature f) => _featureController.isEnabled(f);
|
||||||
|
|
||||||
|
Future<void> toggle(AppFeature f, bool value) async {
|
||||||
|
await _featureController.setEnabled(f, value);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expose the controller to listen from the view if needed
|
||||||
|
FeatureController get controller => _featureController;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:app/core/i18n/translations.g.dart';
|
import 'package:app/core/i18n/translations.g.dart';
|
||||||
import 'package:app/core/ui/panel.dart';
|
import 'package:app/core/ui/panel.dart';
|
||||||
import 'package:app/modules/settings/modules/app/app_settings_view.dart';
|
import 'package:app/modules/settings/modules/app/app_settings_view.dart';
|
||||||
|
import 'package:app/modules/settings/modules/app/features_settings_view.dart';
|
||||||
import 'package:app/modules/settings/modules/help/feedback_view.dart';
|
import 'package:app/modules/settings/modules/help/feedback_view.dart';
|
||||||
import 'package:app/modules/settings/modules/help/help_view.dart';
|
import 'package:app/modules/settings/modules/help/help_view.dart';
|
||||||
import 'package:app/modules/settings/modules/help/legal_view.dart';
|
import 'package:app/modules/settings/modules/help/legal_view.dart';
|
||||||
@@ -16,7 +17,7 @@ class SettingsView extends StatelessWidget {
|
|||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(t.settings.title)),
|
// appBar: AppBar(title: Text(t.settings.title)),
|
||||||
body: PanelNavigator(rootBuilder: (ctx) => _CategoryList()),
|
body: PanelNavigator(rootBuilder: (ctx) => _CategoryList()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,12 @@ class _CategoryList extends StatelessWidget {
|
|||||||
() => const AppSettingsView(),
|
() => const AppSettingsView(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
tile(
|
||||||
|
Icons.tune,
|
||||||
|
t.settings.featureSettings,
|
||||||
|
() => const FeatureSettingsView(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
_SectionHeader(t.settings.sections.account),
|
_SectionHeader(t.settings.sections.account),
|
||||||
tile(
|
tile(
|
||||||
|
|||||||
Reference in New Issue
Block a user