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:
@@ -1,5 +1,6 @@
|
||||
import 'package:app/modules/app_shell.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/login/pages/login_page.dart';
|
||||
import 'package:app/modules/settings/settings_view.dart';
|
||||
@@ -15,7 +16,8 @@ enum AppRoute {
|
||||
budget('/budget'),
|
||||
expenses('/expenses'),
|
||||
reports('/reports'),
|
||||
settings('/settings');
|
||||
settings('/settings'),
|
||||
car('/car');
|
||||
|
||||
const AppRoute(this.path);
|
||||
|
||||
@@ -95,6 +97,7 @@ GoRouter buildAppRouter(AppRoute initialRoute) {
|
||||
(ctx, st) =>
|
||||
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`
|
||||
///
|
||||
/// 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
|
||||
// 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 _TranslationsAppDe app = _TranslationsAppDe._(_root);
|
||||
@override late final _TranslationsSettingsDe settings = _TranslationsSettingsDe._(_root);
|
||||
@override late final _TranslationsFeaturesDe features = _TranslationsFeaturesDe._(_root);
|
||||
}
|
||||
|
||||
// Path: login
|
||||
@@ -105,6 +106,7 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
||||
|
||||
// Translations
|
||||
@override String get title => 'Einstellungen';
|
||||
@override String get featureSettings => 'Funktionseinstellungen';
|
||||
@override late final _TranslationsSettingsSectionsDe sections = _TranslationsSettingsSectionsDe._(_root);
|
||||
@override late final _TranslationsSettingsItemsDe items = _TranslationsSettingsItemsDe._(_root);
|
||||
@override late final _TranslationsSettingsMessagesDe messages = _TranslationsSettingsMessagesDe._(_root);
|
||||
@@ -115,6 +117,19 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
||||
@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
|
||||
class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn {
|
||||
_TranslationsSettingsSectionsDe._(this._root);
|
||||
@@ -220,6 +235,50 @@ class _TranslationsSettingsLegalDe implements TranslationsSettingsLegalEn {
|
||||
@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.
|
||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||
extension on TranslationsDe {
|
||||
@@ -244,6 +303,7 @@ extension on TranslationsDe {
|
||||
case 'app.tooltipExpandRail': return 'Leiste erweitern';
|
||||
case 'app.drawerSettings': return 'Einstellungen';
|
||||
case 'settings.title': return 'Einstellungen';
|
||||
case 'settings.featureSettings': return 'Funktionseinstellungen';
|
||||
case 'settings.sections.account': return 'Konto & Daten';
|
||||
case 'settings.sections.app': return 'App';
|
||||
case 'settings.sections.help': return 'Hilfe & Rechtliches';
|
||||
@@ -277,6 +337,14 @@ extension on TranslationsDe {
|
||||
case 'settings.help.sendFeedback': return 'Feedback senden';
|
||||
case 'settings.legal.privacy': return 'Datenschutz';
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||
late final TranslationsBudgetEn budget = TranslationsBudgetEn._(_root);
|
||||
late final TranslationsAppEn app = TranslationsAppEn._(_root);
|
||||
late final TranslationsSettingsEn settings = TranslationsSettingsEn._(_root);
|
||||
late final TranslationsFeaturesEn features = TranslationsFeaturesEn._(_root);
|
||||
}
|
||||
|
||||
// Path: login
|
||||
@@ -148,6 +149,9 @@ class TranslationsSettingsEn {
|
||||
/// en: 'Settings'
|
||||
String get title => 'Settings';
|
||||
|
||||
/// en: 'Feature Settings'
|
||||
String get featureSettings => 'Feature Settings';
|
||||
|
||||
late final TranslationsSettingsSectionsEn sections = TranslationsSettingsSectionsEn._(_root);
|
||||
late final TranslationsSettingsItemsEn items = TranslationsSettingsItemsEn._(_root);
|
||||
late final TranslationsSettingsMessagesEn messages = TranslationsSettingsMessagesEn._(_root);
|
||||
@@ -158,6 +162,19 @@ class TranslationsSettingsEn {
|
||||
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
|
||||
class TranslationsSettingsSectionsEn {
|
||||
TranslationsSettingsSectionsEn._(this._root);
|
||||
@@ -329,6 +346,66 @@ class TranslationsSettingsLegalEn {
|
||||
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.
|
||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||
extension on Translations {
|
||||
@@ -353,6 +430,7 @@ extension on Translations {
|
||||
case 'app.tooltipExpandRail': return 'Expand Rail';
|
||||
case 'app.drawerSettings': return 'Settings';
|
||||
case 'settings.title': return 'Settings';
|
||||
case 'settings.featureSettings': return 'Feature Settings';
|
||||
case 'settings.sections.account': return 'Account & Data';
|
||||
case 'settings.sections.app': return 'App';
|
||||
case 'settings.sections.help': return 'Help & Legal';
|
||||
@@ -386,6 +464,14 @@ extension on Translations {
|
||||
case 'settings.help.sendFeedback': return 'Send Feedback';
|
||||
case 'settings.legal.privacy': return 'Privacy';
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user