feat: add localisation
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
arb-dir: lib/l10n
|
||||||
|
template-arb-file: app_en.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
output-class: AppLocalizations
|
||||||
|
nullable-getter: false
|
||||||
+15
-5
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../models/settings.dart';
|
import '../models/settings.dart';
|
||||||
import '../viewmodels/settings_view_model.dart';
|
import '../viewmodels/settings_view_model.dart';
|
||||||
import '../theme.dart';
|
import '../theme.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class KoolTabApp extends StatefulWidget {
|
class KoolTabApp extends StatefulWidget {
|
||||||
const KoolTabApp({super.key, required this.router});
|
const KoolTabApp({super.key, required this.router});
|
||||||
@@ -30,9 +31,12 @@ class _KoolTabAppState extends State<KoolTabApp> {
|
|||||||
final settings = context.watch<SettingsViewModel>().settings;
|
final settings = context.watch<SettingsViewModel>().settings;
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
title: 'KoolTab',
|
onGenerateTitle: (context) => AppLocalizations.of(context).appTitle,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
routerConfig: widget.router,
|
routerConfig: widget.router,
|
||||||
|
locale: _localeFor(settings.language),
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
theme: lightTheme,
|
theme: lightTheme,
|
||||||
darkTheme: _themeFor(settings.themeMode),
|
darkTheme: _themeFor(settings.themeMode),
|
||||||
themeMode: _mapThemeMode(settings.themeMode, context),
|
themeMode: _mapThemeMode(settings.themeMode, context),
|
||||||
@@ -46,16 +50,22 @@ class _KoolTabAppState extends State<KoolTabApp> {
|
|||||||
AppThemeMode.dark => ThemeMode.dark,
|
AppThemeMode.dark => ThemeMode.dark,
|
||||||
AppThemeMode.light => ThemeMode.light,
|
AppThemeMode.light => ThemeMode.light,
|
||||||
AppThemeMode.ugly => ThemeMode.dark,
|
AppThemeMode.ugly => ThemeMode.dark,
|
||||||
AppThemeMode.system =>
|
AppThemeMode.system => hasLoaded ? ThemeMode.system : ThemeMode.dark,
|
||||||
hasLoaded ? ThemeMode.system : ThemeMode.dark,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ThemeData _themeFor(AppThemeMode mode) {
|
ThemeData _themeFor(AppThemeMode mode) {
|
||||||
return switch (mode) {
|
return switch (mode) {
|
||||||
AppThemeMode.ugly => uglyTheme,
|
AppThemeMode.ugly => uglyTheme,
|
||||||
_ => darkTheme,
|
_ => darkTheme,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Locale? _localeFor(AppLanguage language) {
|
||||||
|
return switch (language) {
|
||||||
|
AppLanguage.system => null,
|
||||||
|
AppLanguage.english => const Locale('en'),
|
||||||
|
AppLanguage.dutch => const Locale('nl'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ class AppSettingsTable extends Table {
|
|||||||
|
|
||||||
TextColumn get themeMode => text().withDefault(const Constant('system'))();
|
TextColumn get themeMode => text().withDefault(const Constant('system'))();
|
||||||
|
|
||||||
|
TextColumn get language => text().withDefault(const Constant('system'))();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
@@ -129,7 +131,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 6;
|
int get schemaVersion => 7;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration {
|
MigrationStrategy get migration {
|
||||||
@@ -159,6 +161,10 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
if (from < 6) {
|
if (from < 6) {
|
||||||
await migrator.addColumn(closedTabs, closedTabs.paymentMethod);
|
await migrator.addColumn(closedTabs, closedTabs.paymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (from < 7) {
|
||||||
|
await migrator.addColumn(appSettingsTable, appSettingsTable.language);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2219,8 +2219,20 @@ class $AppSettingsTableTable extends AppSettingsTable
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: const Constant('system'),
|
defaultValue: const Constant('system'),
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _languageMeta = const VerificationMeta(
|
||||||
|
'language',
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [id, pinRequired, themeMode];
|
late final GeneratedColumn<String> language = GeneratedColumn<String>(
|
||||||
|
'language',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const Constant('system'),
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, pinRequired, themeMode, language];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
@@ -2253,6 +2265,12 @@ class $AppSettingsTableTable extends AppSettingsTable
|
|||||||
themeMode.isAcceptableOrUnknown(data['theme_mode']!, _themeModeMeta),
|
themeMode.isAcceptableOrUnknown(data['theme_mode']!, _themeModeMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('language')) {
|
||||||
|
context.handle(
|
||||||
|
_languageMeta,
|
||||||
|
language.isAcceptableOrUnknown(data['language']!, _languageMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2274,6 +2292,10 @@ class $AppSettingsTableTable extends AppSettingsTable
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}theme_mode'],
|
data['${effectivePrefix}theme_mode'],
|
||||||
)!,
|
)!,
|
||||||
|
language: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}language'],
|
||||||
|
)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2287,10 +2309,12 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
final String id;
|
final String id;
|
||||||
final bool pinRequired;
|
final bool pinRequired;
|
||||||
final String themeMode;
|
final String themeMode;
|
||||||
|
final String language;
|
||||||
const AppSettingsRow({
|
const AppSettingsRow({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.pinRequired,
|
required this.pinRequired,
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
|
required this.language,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -2298,6 +2322,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
map['id'] = Variable<String>(id);
|
map['id'] = Variable<String>(id);
|
||||||
map['pin_required'] = Variable<bool>(pinRequired);
|
map['pin_required'] = Variable<bool>(pinRequired);
|
||||||
map['theme_mode'] = Variable<String>(themeMode);
|
map['theme_mode'] = Variable<String>(themeMode);
|
||||||
|
map['language'] = Variable<String>(language);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2306,6 +2331,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
id: Value(id),
|
id: Value(id),
|
||||||
pinRequired: Value(pinRequired),
|
pinRequired: Value(pinRequired),
|
||||||
themeMode: Value(themeMode),
|
themeMode: Value(themeMode),
|
||||||
|
language: Value(language),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2318,6 +2344,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
id: serializer.fromJson<String>(json['id']),
|
id: serializer.fromJson<String>(json['id']),
|
||||||
pinRequired: serializer.fromJson<bool>(json['pinRequired']),
|
pinRequired: serializer.fromJson<bool>(json['pinRequired']),
|
||||||
themeMode: serializer.fromJson<String>(json['themeMode']),
|
themeMode: serializer.fromJson<String>(json['themeMode']),
|
||||||
|
language: serializer.fromJson<String>(json['language']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -2327,15 +2354,21 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
'id': serializer.toJson<String>(id),
|
'id': serializer.toJson<String>(id),
|
||||||
'pinRequired': serializer.toJson<bool>(pinRequired),
|
'pinRequired': serializer.toJson<bool>(pinRequired),
|
||||||
'themeMode': serializer.toJson<String>(themeMode),
|
'themeMode': serializer.toJson<String>(themeMode),
|
||||||
|
'language': serializer.toJson<String>(language),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettingsRow copyWith({String? id, bool? pinRequired, String? themeMode}) =>
|
AppSettingsRow copyWith({
|
||||||
AppSettingsRow(
|
String? id,
|
||||||
id: id ?? this.id,
|
bool? pinRequired,
|
||||||
pinRequired: pinRequired ?? this.pinRequired,
|
String? themeMode,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
String? language,
|
||||||
);
|
}) => AppSettingsRow(
|
||||||
|
id: id ?? this.id,
|
||||||
|
pinRequired: pinRequired ?? this.pinRequired,
|
||||||
|
themeMode: themeMode ?? this.themeMode,
|
||||||
|
language: language ?? this.language,
|
||||||
|
);
|
||||||
AppSettingsRow copyWithCompanion(AppSettingsTableCompanion data) {
|
AppSettingsRow copyWithCompanion(AppSettingsTableCompanion data) {
|
||||||
return AppSettingsRow(
|
return AppSettingsRow(
|
||||||
id: data.id.present ? data.id.value : this.id,
|
id: data.id.present ? data.id.value : this.id,
|
||||||
@@ -2343,6 +2376,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
? data.pinRequired.value
|
? data.pinRequired.value
|
||||||
: this.pinRequired,
|
: this.pinRequired,
|
||||||
themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode,
|
themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode,
|
||||||
|
language: data.language.present ? data.language.value : this.language,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2351,49 +2385,56 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
return (StringBuffer('AppSettingsRow(')
|
return (StringBuffer('AppSettingsRow(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('pinRequired: $pinRequired, ')
|
..write('pinRequired: $pinRequired, ')
|
||||||
..write('themeMode: $themeMode')
|
..write('themeMode: $themeMode, ')
|
||||||
|
..write('language: $language')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(id, pinRequired, themeMode);
|
int get hashCode => Object.hash(id, pinRequired, themeMode, language);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is AppSettingsRow &&
|
(other is AppSettingsRow &&
|
||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
other.pinRequired == this.pinRequired &&
|
other.pinRequired == this.pinRequired &&
|
||||||
other.themeMode == this.themeMode);
|
other.themeMode == this.themeMode &&
|
||||||
|
other.language == this.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
||||||
final Value<String> id;
|
final Value<String> id;
|
||||||
final Value<bool> pinRequired;
|
final Value<bool> pinRequired;
|
||||||
final Value<String> themeMode;
|
final Value<String> themeMode;
|
||||||
|
final Value<String> language;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const AppSettingsTableCompanion({
|
const AppSettingsTableCompanion({
|
||||||
this.id = const Value.absent(),
|
this.id = const Value.absent(),
|
||||||
this.pinRequired = const Value.absent(),
|
this.pinRequired = const Value.absent(),
|
||||||
this.themeMode = const Value.absent(),
|
this.themeMode = const Value.absent(),
|
||||||
|
this.language = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
AppSettingsTableCompanion.insert({
|
AppSettingsTableCompanion.insert({
|
||||||
required String id,
|
required String id,
|
||||||
this.pinRequired = const Value.absent(),
|
this.pinRequired = const Value.absent(),
|
||||||
this.themeMode = const Value.absent(),
|
this.themeMode = const Value.absent(),
|
||||||
|
this.language = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : id = Value(id);
|
}) : id = Value(id);
|
||||||
static Insertable<AppSettingsRow> custom({
|
static Insertable<AppSettingsRow> custom({
|
||||||
Expression<String>? id,
|
Expression<String>? id,
|
||||||
Expression<bool>? pinRequired,
|
Expression<bool>? pinRequired,
|
||||||
Expression<String>? themeMode,
|
Expression<String>? themeMode,
|
||||||
|
Expression<String>? language,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
if (pinRequired != null) 'pin_required': pinRequired,
|
if (pinRequired != null) 'pin_required': pinRequired,
|
||||||
if (themeMode != null) 'theme_mode': themeMode,
|
if (themeMode != null) 'theme_mode': themeMode,
|
||||||
|
if (language != null) 'language': language,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2402,12 +2443,14 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
Value<String>? id,
|
Value<String>? id,
|
||||||
Value<bool>? pinRequired,
|
Value<bool>? pinRequired,
|
||||||
Value<String>? themeMode,
|
Value<String>? themeMode,
|
||||||
|
Value<String>? language,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return AppSettingsTableCompanion(
|
return AppSettingsTableCompanion(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
pinRequired: pinRequired ?? this.pinRequired,
|
pinRequired: pinRequired ?? this.pinRequired,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
|
language: language ?? this.language,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2424,6 +2467,9 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
if (themeMode.present) {
|
if (themeMode.present) {
|
||||||
map['theme_mode'] = Variable<String>(themeMode.value);
|
map['theme_mode'] = Variable<String>(themeMode.value);
|
||||||
}
|
}
|
||||||
|
if (language.present) {
|
||||||
|
map['language'] = Variable<String>(language.value);
|
||||||
|
}
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
@@ -2436,6 +2482,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('pinRequired: $pinRequired, ')
|
..write('pinRequired: $pinRequired, ')
|
||||||
..write('themeMode: $themeMode, ')
|
..write('themeMode: $themeMode, ')
|
||||||
|
..write('language: $language, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
@@ -4023,6 +4070,7 @@ typedef $$AppSettingsTableTableCreateCompanionBuilder =
|
|||||||
required String id,
|
required String id,
|
||||||
Value<bool> pinRequired,
|
Value<bool> pinRequired,
|
||||||
Value<String> themeMode,
|
Value<String> themeMode,
|
||||||
|
Value<String> language,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $$AppSettingsTableTableUpdateCompanionBuilder =
|
typedef $$AppSettingsTableTableUpdateCompanionBuilder =
|
||||||
@@ -4030,6 +4078,7 @@ typedef $$AppSettingsTableTableUpdateCompanionBuilder =
|
|||||||
Value<String> id,
|
Value<String> id,
|
||||||
Value<bool> pinRequired,
|
Value<bool> pinRequired,
|
||||||
Value<String> themeMode,
|
Value<String> themeMode,
|
||||||
|
Value<String> language,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4056,6 +4105,11 @@ class $$AppSettingsTableTableFilterComposer
|
|||||||
column: $table.themeMode,
|
column: $table.themeMode,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get language => $composableBuilder(
|
||||||
|
column: $table.language,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$AppSettingsTableTableOrderingComposer
|
class $$AppSettingsTableTableOrderingComposer
|
||||||
@@ -4081,6 +4135,11 @@ class $$AppSettingsTableTableOrderingComposer
|
|||||||
column: $table.themeMode,
|
column: $table.themeMode,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get language => $composableBuilder(
|
||||||
|
column: $table.language,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$AppSettingsTableTableAnnotationComposer
|
class $$AppSettingsTableTableAnnotationComposer
|
||||||
@@ -4102,6 +4161,9 @@ class $$AppSettingsTableTableAnnotationComposer
|
|||||||
|
|
||||||
GeneratedColumn<String> get themeMode =>
|
GeneratedColumn<String> get themeMode =>
|
||||||
$composableBuilder(column: $table.themeMode, builder: (column) => column);
|
$composableBuilder(column: $table.themeMode, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get language =>
|
||||||
|
$composableBuilder(column: $table.language, builder: (column) => column);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$AppSettingsTableTableTableManager
|
class $$AppSettingsTableTableTableManager
|
||||||
@@ -4144,11 +4206,13 @@ class $$AppSettingsTableTableTableManager
|
|||||||
Value<String> id = const Value.absent(),
|
Value<String> id = const Value.absent(),
|
||||||
Value<bool> pinRequired = const Value.absent(),
|
Value<bool> pinRequired = const Value.absent(),
|
||||||
Value<String> themeMode = const Value.absent(),
|
Value<String> themeMode = const Value.absent(),
|
||||||
|
Value<String> language = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => AppSettingsTableCompanion(
|
}) => AppSettingsTableCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
pinRequired: pinRequired,
|
pinRequired: pinRequired,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
language: language,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
@@ -4156,11 +4220,13 @@ class $$AppSettingsTableTableTableManager
|
|||||||
required String id,
|
required String id,
|
||||||
Value<bool> pinRequired = const Value.absent(),
|
Value<bool> pinRequired = const Value.absent(),
|
||||||
Value<String> themeMode = const Value.absent(),
|
Value<String> themeMode = const Value.absent(),
|
||||||
|
Value<String> language = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => AppSettingsTableCompanion.insert(
|
}) => AppSettingsTableCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
pinRequired: pinRequired,
|
pinRequired: pinRequired,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
language: language,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
"appTitle": "KoolTab",
|
||||||
|
"barTabs": "Bar Tabs",
|
||||||
|
"products": "Products",
|
||||||
|
"addProduct": "Add product",
|
||||||
|
"noProductsYet": "No products yet.",
|
||||||
|
"addFirstProduct": "Add your first product to start selling.",
|
||||||
|
"manageProducts": "Manage products",
|
||||||
|
"tabHistory": "Tab history",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"settings": "Settings",
|
||||||
|
"logout": "Logout",
|
||||||
|
"openOrSelectTab": "Open or select a tab first.",
|
||||||
|
"openNewTab": "Open new tab",
|
||||||
|
"customerGroupName": "Customer / group name",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"openTab": "Open tab",
|
||||||
|
"couldNotCreateTab": "Could not create tab: {error}",
|
||||||
|
"@couldNotCreateTab": {"placeholders": {"error": {"type": "String"}}},
|
||||||
|
"searchByName": "Search by name…",
|
||||||
|
"allCustomers": "All customers",
|
||||||
|
"customer": "Customer",
|
||||||
|
"noClosedTabs": "No closed tabs yet",
|
||||||
|
"settingsTitle": "Settings",
|
||||||
|
"security": "Security",
|
||||||
|
"pinRequired": "PIN Required",
|
||||||
|
"changePin": "Change PIN",
|
||||||
|
"appearance": "Appearance",
|
||||||
|
"language": "Language",
|
||||||
|
"languageSystem": "System default",
|
||||||
|
"languageEnglish": "English",
|
||||||
|
"languageDutch": "Dutch",
|
||||||
|
"theme": "Theme",
|
||||||
|
"system": "System",
|
||||||
|
"light": "Light",
|
||||||
|
"dark": "Dark",
|
||||||
|
"ugly": "Ugly",
|
||||||
|
"setPinCode": "Set PIN Code",
|
||||||
|
"enterPin": "Enter PIN",
|
||||||
|
"enterCurrentPin": "Enter Current PIN",
|
||||||
|
"enterNewPin": "Enter New PIN",
|
||||||
|
"fourDigits": "4 digits",
|
||||||
|
"enterPinFourDigits": "Enter PIN (4 digits)",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"confirmPin": "Confirm PIN",
|
||||||
|
"pinExactlyFour": "PIN must be exactly 4 digits",
|
||||||
|
"couldNotSavePin": "Could not save PIN setting.",
|
||||||
|
"editProduct": "Edit product",
|
||||||
|
"productName": "Product name",
|
||||||
|
"category": "Category",
|
||||||
|
"price": "Price",
|
||||||
|
"stock": "Stock",
|
||||||
|
"lowStockThreshold": "Low stock threshold",
|
||||||
|
"chooseProductImage": "Choose product image",
|
||||||
|
"changeImage": "Change image",
|
||||||
|
"deleteProduct": "Delete product?",
|
||||||
|
"deleteProductDescription": "This will remove the product from the product list.",
|
||||||
|
"delete": "Delete",
|
||||||
|
"productNotFound": "Product not found.",
|
||||||
|
"chooseImage": "Choose a product image.",
|
||||||
|
"selectCategory": "Please select a category.",
|
||||||
|
"enterProductName": "Enter a product name.",
|
||||||
|
"version": "Version {version}",
|
||||||
|
"@version": {"placeholders": {"version": {"type": "String"}}},
|
||||||
|
"selectTabToAddItems": "Select a tab to add items",
|
||||||
|
"openTabs": "Open tabs",
|
||||||
|
"selectOrOpenTab": "Select or open a tab",
|
||||||
|
"noOpenTabs": "No open tabs",
|
||||||
|
"edit": "Edit",
|
||||||
|
"close": "Close",
|
||||||
|
"tabItemSummary": "{count, plural, =1 {1 item - {total}} other {{count} items - {total}}}",
|
||||||
|
"@tabItemSummary": {"placeholders": {"count": {"type": "int"}, "total": {"type": "String"}}},
|
||||||
|
"tabItemCount": "{count, plural, =1 {1 item} other {{count} items}}",
|
||||||
|
"@tabItemCount": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"tapProductsToAdd": "Tap products to add them",
|
||||||
|
"total": "Total",
|
||||||
|
"closeTab": "Close tab",
|
||||||
|
"productStockSummary": "{category} • {price} • Stock: {stock}",
|
||||||
|
"@productStockSummary": {"placeholders": {"category": {"type": "String"}, "price": {"type": "String"}, "stock": {"type": "int"}}},
|
||||||
|
"outOfStock": "OUT OF STOCK",
|
||||||
|
"tabsYouCloseAppearHere": "Tabs you close will show up here.",
|
||||||
|
"enterPinSubtitle": "You’ll use this to unlock the app",
|
||||||
|
"confirmPinSubtitle": "Enter the same PIN again",
|
||||||
|
"createPin": "Create a PIN",
|
||||||
|
"pinsDidNotMatch": "PINs didn’t match. Try again.",
|
||||||
|
"somethingWentWrong": "Something went wrong.",
|
||||||
|
"incorrectPin": "Incorrect PIN.",
|
||||||
|
"enterPrice": "Enter a price.",
|
||||||
|
"enterValidPrice": "Enter a valid price.",
|
||||||
|
"enterValidStock": "Enter a valid stock amount.",
|
||||||
|
"enterValidThreshold": "Enter a valid threshold.",
|
||||||
|
"couldNotLoadProduct": "Could not load product.",
|
||||||
|
"couldNotSaveProduct": "Could not save product.",
|
||||||
|
"couldNotDeleteProduct": "Could not delete product.",
|
||||||
|
"paymentMethod": "Payment method",
|
||||||
|
"cash": "Cash",
|
||||||
|
"payconiq": "Payconiq",
|
||||||
|
"closeTabForCustomer": "Close tab for {customerName}?",
|
||||||
|
"@closeTabForCustomer": {"placeholders": {"customerName": {"type": "String"}}},
|
||||||
|
"currentTotal": "Current total: {total}",
|
||||||
|
"@currentTotal": {"placeholders": {"total": {"type": "String"}}},
|
||||||
|
"couldNotCloseTab": "Could not close tab.",
|
||||||
|
"closingTab": "Closing tab…",
|
||||||
|
"slideToConfirmClosing": "Slide to confirm closing",
|
||||||
|
"errorScreen": "Error Screen",
|
||||||
|
"unexpectedError": "An unexpected error occurred.\nPlease try restarting the app.",
|
||||||
|
"goToBarScreen": "Go to bar screen",
|
||||||
|
"updates": "Updates",
|
||||||
|
"checkingForUpdates": "Checking for updates…",
|
||||||
|
"checkForUpdates": "Check for updates",
|
||||||
|
"latestVersion": "You are already on the latest version.",
|
||||||
|
"updateCheckFailed": "Update check failed.",
|
||||||
|
"updateAvailable": "Update available",
|
||||||
|
"versionAvailable": "Version {version} is available.",
|
||||||
|
"@versionAvailable": {"placeholders": {"version": {"type": "String"}}},
|
||||||
|
"later": "Later",
|
||||||
|
"update": "Update",
|
||||||
|
"updateReady": "Update ready",
|
||||||
|
"updateReadyDescription": "The update has been downloaded and installed. Restart the app now to apply the changes.",
|
||||||
|
"restartApp": "Restart app",
|
||||||
|
"updateFailed": "Update failed",
|
||||||
|
"retry": "Retry",
|
||||||
|
"updating": "Updating",
|
||||||
|
"installing": "Installing",
|
||||||
|
"doNotCloseDuringUpdate": "Do not close the app during the update",
|
||||||
|
"cancelUpdate": "Cancel update?",
|
||||||
|
"cancelUpdateDescription": "The update is in progress. If you leave now, the app may become unstable.",
|
||||||
|
"continueUpdate": "Continue update",
|
||||||
|
"cancelUpdateAction": "Cancel update",
|
||||||
|
"preparingDownload": "Preparing download…",
|
||||||
|
"simulatedDownloadError": "Simulated download error (network timeout)",
|
||||||
|
"downloadFailed": "Download failed",
|
||||||
|
"downloading": "Downloading {percent}%",
|
||||||
|
"@downloading": {"placeholders": {"percent": {"type": "String"}}},
|
||||||
|
"installingUpdate": "Installing update…",
|
||||||
|
"updateInstalledRestarting": "Update installed! Restarting…",
|
||||||
|
"installationFailed": "Installation failed",
|
||||||
|
"updateAlreadyInProgress": "Update already in progress",
|
||||||
|
"updateAlreadyRunning": "Update already running",
|
||||||
|
"genericError": "An unexpected error occurred.",
|
||||||
|
"couldNotLoadBar": "Could not load bar screen.",
|
||||||
|
"couldNotCreateTabMessage": "Could not create tab.",
|
||||||
|
"productOutOfStock": "Product is out of stock.",
|
||||||
|
"couldNotAddProductToTab": "Could not add product to tab.",
|
||||||
|
"couldNotUpdateQuantity": "Could not update item quantity.",
|
||||||
|
"couldNotReloadTabs": "Could not reload tabs.",
|
||||||
|
"couldNotLoadHistory": "Could not load tab history.",
|
||||||
|
"couldNotLoadMoreTabs": "Could not load more tabs.",
|
||||||
|
"couldNotLoadProducts": "Could not load products.",
|
||||||
|
"couldNotAddProduct": "Could not add product.",
|
||||||
|
"couldNotUpdateProduct": "Could not update product.",
|
||||||
|
"couldNotDeleteProductMessage": "Could not delete product.",
|
||||||
|
"couldNotCheckPinStatus": "Could not check PIN status.",
|
||||||
|
"couldNotVerifyPin": "Could not verify PIN.",
|
||||||
|
"currentPinIncorrect": "Current PIN is incorrect.",
|
||||||
|
"couldNotLoadSettings": "Could not load settings.",
|
||||||
|
"couldNotSaveSettings": "Could not save settings.",
|
||||||
|
"downloadFailedWithDetail": "Download failed.",
|
||||||
|
"installationFailedWithDetail": "Installation failed.",
|
||||||
|
"devMenu": "Dev Menu",
|
||||||
|
"dataManagement": "Data Management",
|
||||||
|
"clearOpenTabs": "Clear all open tabs",
|
||||||
|
"clearClosedHistory": "Clear closed tab history",
|
||||||
|
"seedDefaultProducts": "Seed default products",
|
||||||
|
"onlySeedsWhenEmpty": "Only seeds if table is empty",
|
||||||
|
"clearAndReseedProducts": "Clear & reseed products",
|
||||||
|
"wipesAndReseeds": "Wipes all products, then seeds defaults",
|
||||||
|
"clearAllProducts": "Clear all products",
|
||||||
|
"clearProductImages": "Clear product images",
|
||||||
|
"deletesProductImages": "Deletes images from product_images folder",
|
||||||
|
"debugging": "Debugging",
|
||||||
|
"resetPin": "Reset PIN",
|
||||||
|
"removePinLock": "Remove PIN lock",
|
||||||
|
"toggleDebugOverlay": "Toggle debug overlay",
|
||||||
|
"showFpsMemoryWidgets": "Show FPS, memory, widget count",
|
||||||
|
"errorScreenMenu": "Error screen",
|
||||||
|
"viewErrorScreen": "View the error screen UI",
|
||||||
|
"featureToggles": "Feature Toggles",
|
||||||
|
"simulateUpdateDownload": "Simulate update download",
|
||||||
|
"openDownloadProgress": "Open the download progress screen",
|
||||||
|
"testingHelpers": "Testing Helpers",
|
||||||
|
"createTestTab": "Create test tab",
|
||||||
|
"addRandomItems": "Add tab with random items",
|
||||||
|
"addTestProducts": "Add 100 test products",
|
||||||
|
"stressTestGrid": "Stress test product grid",
|
||||||
|
"simulateLowStock": "Simulate low stock",
|
||||||
|
"setProductsBelowThreshold": "Set all products below threshold",
|
||||||
|
"generateMockOrders": "Generate 100 mock orders",
|
||||||
|
"randomCustomersItemsAmounts": "Random customers, items, and amounts",
|
||||||
|
"performance": "Performance",
|
||||||
|
"clearImageCache": "Clear image cache",
|
||||||
|
"reloadProductImages": "Reload product images",
|
||||||
|
"debugOverlay": "Debug Overlay",
|
||||||
|
"debugOverlayDescription": "The debug overlay shows FPS, memory usage, and widget counts. Enable it via Flutter DevTools in debug mode.",
|
||||||
|
"ok": "OK",
|
||||||
|
"simulateUpdate": "Simulate update",
|
||||||
|
"chooseSimulationMode": "Choose a simulation mode:",
|
||||||
|
"successfulDownload": "Successful download",
|
||||||
|
"progressThenInstall": "Progress 0→100%, then install, then done",
|
||||||
|
"downloadError": "Download error",
|
||||||
|
"failsWithNetworkTimeout": "Fails at 50% with a network timeout",
|
||||||
|
"realDownloadWillFail": "Real download (will fail)",
|
||||||
|
"attemptsFakeUrl": "Attempts real OTA with fake URL",
|
||||||
|
"categoryBeer": "Beer",
|
||||||
|
"categoryWine": "Wine",
|
||||||
|
"categorySoftDrinks": "Soft drinks",
|
||||||
|
"categoryCocktails": "Cocktails",
|
||||||
|
"categorySnacks": "Snacks",
|
||||||
|
"categoryCoffee": "Coffee",
|
||||||
|
"categoryTea": "Tea",
|
||||||
|
"categoryOther": "Other",
|
||||||
|
"devActionClearedOpenTabs": "Cleared all open tabs",
|
||||||
|
"devActionClearedHistory": "Cleared closed tab history",
|
||||||
|
"devActionClearedProducts": "Cleared all products",
|
||||||
|
"devActionSeededProducts": "Seeded {count} products",
|
||||||
|
"@devActionSeededProducts": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionReseededProducts": "Cleared and reseeded {count} products",
|
||||||
|
"@devActionReseededProducts": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionImagesCleared": "Product images cleared",
|
||||||
|
"devActionResetPin": "PIN reset (no PIN required)",
|
||||||
|
"devActionNoProducts": "No products available",
|
||||||
|
"devActionImagesReloaded": "Product images reloaded",
|
||||||
|
"devActionCacheCleared": "Image cache cleared (no-op in debug mode)",
|
||||||
|
"devActionLowStock": "Simulated low stock for {count} products",
|
||||||
|
"@devActionLowStock": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionTestTab": "Created test tab with {count} items",
|
||||||
|
"@devActionTestTab": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionTestProducts": "Added {count} test products",
|
||||||
|
"@devActionTestProducts": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionMockOrders": "Generated {orders} mock orders across {customers} customers",
|
||||||
|
"@devActionMockOrders": {"placeholders": {"orders": {"type": "int"}, "customers": {"type": "int"}}},
|
||||||
|
"devActionFailedImages": "Failed to clear images.",
|
||||||
|
"devActionResetStock": "Reset stock for {count} products",
|
||||||
|
"@devActionResetStock": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"paymentCash": "Cash",
|
||||||
|
"paymentPayconiq": "Payconiq",
|
||||||
|
"simulatedUpdateNotes": "Bug fixes and performance improvements."
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,723 @@
|
|||||||
|
// ignore: unused_import
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// The translations for English (`en`).
|
||||||
|
class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get appTitle => 'KoolTab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get barTabs => 'Bar Tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get products => 'Products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addProduct => 'Add product';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noProductsYet => 'No products yet.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addFirstProduct => 'Add your first product to start selling.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get manageProducts => 'Manage products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tabHistory => 'Tab history';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get refresh => 'Refresh';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings => 'Settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logout => 'Logout';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openOrSelectTab => 'Open or select a tab first.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openNewTab => 'Open new tab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get customerGroupName => 'Customer / group name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancel => 'Cancel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openTab => 'Open tab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String couldNotCreateTab(String error) {
|
||||||
|
return 'Could not create tab: $error';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchByName => 'Search by name…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allCustomers => 'All customers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get customer => 'Customer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noClosedTabs => 'No closed tabs yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsTitle => 'Settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pinRequired => 'PIN Required';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get changePin => 'Change PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get appearance => 'Appearance';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get language => 'Language';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get languageSystem => 'System default';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get languageEnglish => 'English';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get languageDutch => 'Dutch';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get theme => 'Theme';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get system => 'System';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get light => 'Light';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dark => 'Dark';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ugly => 'Ugly';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get setPinCode => 'Set PIN Code';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPin => 'Enter PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterCurrentPin => 'Enter Current PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterNewPin => 'Enter New PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fourDigits => '4 digits';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPinFourDigits => 'Enter PIN (4 digits)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get confirm => 'Confirm';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get confirmPin => 'Confirm PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pinExactlyFour => 'PIN must be exactly 4 digits';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotSavePin => 'Could not save PIN setting.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editProduct => 'Edit product';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productName => 'Product name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get category => 'Category';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get price => 'Price';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get stock => 'Stock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lowStockThreshold => 'Low stock threshold';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get chooseProductImage => 'Choose product image';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get changeImage => 'Change image';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteProduct => 'Delete product?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteProductDescription =>
|
||||||
|
'This will remove the product from the product list.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete => 'Delete';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productNotFound => 'Product not found.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get chooseImage => 'Choose a product image.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectCategory => 'Please select a category.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterProductName => 'Enter a product name.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String version(String version) {
|
||||||
|
return 'Version $version';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectTabToAddItems => 'Select a tab to add items';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openTabs => 'Open tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOrOpenTab => 'Select or open a tab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noOpenTabs => 'No open tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get edit => 'Edit';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get close => 'Close';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String tabItemSummary(int count, String total) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count items - $total',
|
||||||
|
one: '1 item - $total',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String tabItemCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count items',
|
||||||
|
one: '1 item',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapProductsToAdd => 'Tap products to add them';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get total => 'Total';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get closeTab => 'Close tab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String productStockSummary(String category, String price, int stock) {
|
||||||
|
return '$category • $price • Stock: $stock';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get outOfStock => 'OUT OF STOCK';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tabsYouCloseAppearHere => 'Tabs you close will show up here.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPinSubtitle => 'You’ll use this to unlock the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get confirmPinSubtitle => 'Enter the same PIN again';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createPin => 'Create a PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pinsDidNotMatch => 'PINs didn’t match. Try again.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get somethingWentWrong => 'Something went wrong.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get incorrectPin => 'Incorrect PIN.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPrice => 'Enter a price.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterValidPrice => 'Enter a valid price.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterValidStock => 'Enter a valid stock amount.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterValidThreshold => 'Enter a valid threshold.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadProduct => 'Could not load product.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotSaveProduct => 'Could not save product.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotDeleteProduct => 'Could not delete product.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentMethod => 'Payment method';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cash => 'Cash';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get payconiq => 'Payconiq';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String closeTabForCustomer(String customerName) {
|
||||||
|
return 'Close tab for $customerName?';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String currentTotal(String total) {
|
||||||
|
return 'Current total: $total';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotCloseTab => 'Could not close tab.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get closingTab => 'Closing tab…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get slideToConfirmClosing => 'Slide to confirm closing';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorScreen => 'Error Screen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unexpectedError =>
|
||||||
|
'An unexpected error occurred.\nPlease try restarting the app.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get goToBarScreen => 'Go to bar screen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updates => 'Updates';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get checkingForUpdates => 'Checking for updates…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get checkForUpdates => 'Check for updates';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get latestVersion => 'You are already on the latest version.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateCheckFailed => 'Update check failed.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateAvailable => 'Update available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String versionAvailable(String version) {
|
||||||
|
return 'Version $version is available.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get later => 'Later';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get update => 'Update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateReady => 'Update ready';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateReadyDescription =>
|
||||||
|
'The update has been downloaded and installed. Restart the app now to apply the changes.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get restartApp => 'Restart app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateFailed => 'Update failed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get retry => 'Retry';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updating => 'Updating';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installing => 'Installing';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get doNotCloseDuringUpdate => 'Do not close the app during the update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelUpdate => 'Cancel update?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelUpdateDescription =>
|
||||||
|
'The update is in progress. If you leave now, the app may become unstable.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get continueUpdate => 'Continue update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelUpdateAction => 'Cancel update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get preparingDownload => 'Preparing download…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulatedDownloadError =>
|
||||||
|
'Simulated download error (network timeout)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get downloadFailed => 'Download failed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String downloading(String percent) {
|
||||||
|
return 'Downloading $percent%';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installingUpdate => 'Installing update…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateInstalledRestarting => 'Update installed! Restarting…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installationFailed => 'Installation failed';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateAlreadyInProgress => 'Update already in progress';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateAlreadyRunning => 'Update already running';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get genericError => 'An unexpected error occurred.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadBar => 'Could not load bar screen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotCreateTabMessage => 'Could not create tab.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productOutOfStock => 'Product is out of stock.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotAddProductToTab => 'Could not add product to tab.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotUpdateQuantity => 'Could not update item quantity.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotReloadTabs => 'Could not reload tabs.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadHistory => 'Could not load tab history.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadMoreTabs => 'Could not load more tabs.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadProducts => 'Could not load products.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotAddProduct => 'Could not add product.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotUpdateProduct => 'Could not update product.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotDeleteProductMessage => 'Could not delete product.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotCheckPinStatus => 'Could not check PIN status.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotVerifyPin => 'Could not verify PIN.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get currentPinIncorrect => 'Current PIN is incorrect.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadSettings => 'Could not load settings.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotSaveSettings => 'Could not save settings.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get downloadFailedWithDetail => 'Download failed.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installationFailedWithDetail => 'Installation failed.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devMenu => 'Dev Menu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dataManagement => 'Data Management';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearOpenTabs => 'Clear all open tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearClosedHistory => 'Clear closed tab history';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get seedDefaultProducts => 'Seed default products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get onlySeedsWhenEmpty => 'Only seeds if table is empty';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAndReseedProducts => 'Clear & reseed products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wipesAndReseeds => 'Wipes all products, then seeds defaults';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllProducts => 'Clear all products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearProductImages => 'Clear product images';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deletesProductImages =>
|
||||||
|
'Deletes images from product_images folder';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugging => 'Debugging';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resetPin => 'Reset PIN';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get removePinLock => 'Remove PIN lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get toggleDebugOverlay => 'Toggle debug overlay';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showFpsMemoryWidgets => 'Show FPS, memory, widget count';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorScreenMenu => 'Error screen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewErrorScreen => 'View the error screen UI';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get featureToggles => 'Feature Toggles';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulateUpdateDownload => 'Simulate update download';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openDownloadProgress => 'Open the download progress screen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get testingHelpers => 'Testing Helpers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createTestTab => 'Create test tab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addRandomItems => 'Add tab with random items';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addTestProducts => 'Add 100 test products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get stressTestGrid => 'Stress test product grid';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulateLowStock => 'Simulate low stock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get setProductsBelowThreshold => 'Set all products below threshold';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get generateMockOrders => 'Generate 100 mock orders';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get randomCustomersItemsAmounts =>
|
||||||
|
'Random customers, items, and amounts';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get performance => 'Performance';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearImageCache => 'Clear image cache';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reloadProductImages => 'Reload product images';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugOverlay => 'Debug Overlay';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugOverlayDescription =>
|
||||||
|
'The debug overlay shows FPS, memory usage, and widget counts. Enable it via Flutter DevTools in debug mode.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ok => 'OK';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulateUpdate => 'Simulate update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get chooseSimulationMode => 'Choose a simulation mode:';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get successfulDownload => 'Successful download';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get progressThenInstall => 'Progress 0→100%, then install, then done';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get downloadError => 'Download error';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get failsWithNetworkTimeout => 'Fails at 50% with a network timeout';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get realDownloadWillFail => 'Real download (will fail)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attemptsFakeUrl => 'Attempts real OTA with fake URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryBeer => 'Beer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryWine => 'Wine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categorySoftDrinks => 'Soft drinks';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryCocktails => 'Cocktails';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categorySnacks => 'Snacks';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryCoffee => 'Coffee';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryTea => 'Tea';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryOther => 'Other';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionClearedOpenTabs => 'Cleared all open tabs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionClearedHistory => 'Cleared closed tab history';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionClearedProducts => 'Cleared all products';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionSeededProducts(int count) {
|
||||||
|
return 'Seeded $count products';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionReseededProducts(int count) {
|
||||||
|
return 'Cleared and reseeded $count products';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionImagesCleared => 'Product images cleared';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionResetPin => 'PIN reset (no PIN required)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionNoProducts => 'No products available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionImagesReloaded => 'Product images reloaded';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionCacheCleared =>
|
||||||
|
'Image cache cleared (no-op in debug mode)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionLowStock(int count) {
|
||||||
|
return 'Simulated low stock for $count products';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionTestTab(int count) {
|
||||||
|
return 'Created test tab with $count items';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionTestProducts(int count) {
|
||||||
|
return 'Added $count test products';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionMockOrders(int orders, int customers) {
|
||||||
|
return 'Generated $orders mock orders across $customers customers';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionFailedImages => 'Failed to clear images.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionResetStock(int count) {
|
||||||
|
return 'Reset stock for $count products';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentCash => 'Cash';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentPayconiq => 'Payconiq';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulatedUpdateNotes => 'Bug fixes and performance improvements.';
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
extension AppLocalizationsHelpers on AppLocalizations {
|
||||||
|
String categoryLabel(String category) {
|
||||||
|
return switch (category.trim().toLowerCase()) {
|
||||||
|
'bier' || 'beer' => categoryBeer,
|
||||||
|
'wijn' || 'wine' => categoryWine,
|
||||||
|
'frisdrank' || 'soft drinks' => categorySoftDrinks,
|
||||||
|
'cocktails' => categoryCocktails,
|
||||||
|
'snacks' => categorySnacks,
|
||||||
|
'coffee' || 'koffie' => categoryCoffee,
|
||||||
|
'thee' || 'tea' => categoryTea,
|
||||||
|
'other' || 'overig' => categoryOther,
|
||||||
|
_ => category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String localizedError(String? message) {
|
||||||
|
return switch (message) {
|
||||||
|
'Could not load bar screen.' => couldNotLoadBar,
|
||||||
|
'Could not create tab.' => couldNotCreateTabMessage,
|
||||||
|
'Product is out of stock.' => productOutOfStock,
|
||||||
|
'Could not add product to tab.' => couldNotAddProductToTab,
|
||||||
|
'Could not update item quantity.' => couldNotUpdateQuantity,
|
||||||
|
'Could not close tab.' => couldNotCloseTab,
|
||||||
|
'Could not reload tabs.' => couldNotReloadTabs,
|
||||||
|
'Could not load tab history.' => couldNotLoadHistory,
|
||||||
|
'Could not load more tabs.' => couldNotLoadMoreTabs,
|
||||||
|
'Could not load products.' => couldNotLoadProducts,
|
||||||
|
'Could not add product.' => couldNotAddProduct,
|
||||||
|
'Could not update product.' => couldNotUpdateProduct,
|
||||||
|
'Could not check PIN status.' => couldNotCheckPinStatus,
|
||||||
|
'Could not save PIN.' => couldNotSavePin,
|
||||||
|
'Incorrect PIN.' => incorrectPin,
|
||||||
|
'Could not verify PIN.' => couldNotVerifyPin,
|
||||||
|
'Current PIN is incorrect.' => currentPinIncorrect,
|
||||||
|
'Could not load settings.' => couldNotLoadSettings,
|
||||||
|
'Could not save settings.' => couldNotSaveSettings,
|
||||||
|
'Could not load product.' => couldNotLoadProduct,
|
||||||
|
'Could not save product.' => couldNotSaveProduct,
|
||||||
|
'Could not delete product.' => couldNotDeleteProduct,
|
||||||
|
'Update failed' => updateFailed,
|
||||||
|
'Download failed' => downloadFailed,
|
||||||
|
'Installation failed' => installationFailed,
|
||||||
|
'Update already in progress' => updateAlreadyInProgress,
|
||||||
|
_ => message ?? genericError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String updateStatus(String status) {
|
||||||
|
if (status.startsWith('Downloading ')) {
|
||||||
|
return downloading(status.substring('Downloading '.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (status) {
|
||||||
|
'Preparing download…' => preparingDownload,
|
||||||
|
'Download failed' => downloadFailed,
|
||||||
|
'Installing update…' => installingUpdate,
|
||||||
|
'Update installed! Restarting…' => updateInstalledRestarting,
|
||||||
|
'Installation failed' => installationFailed,
|
||||||
|
'Update already running' => updateAlreadyRunning,
|
||||||
|
_ => status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String updateError(String? message) {
|
||||||
|
if (message == null) return updateFailed;
|
||||||
|
if (message.startsWith('Download failed')) return downloadFailedWithDetail;
|
||||||
|
if (message.startsWith('Installation failed')) {
|
||||||
|
return installationFailedWithDetail;
|
||||||
|
}
|
||||||
|
if (message == 'Simulated download error (network timeout)') {
|
||||||
|
return simulatedDownloadError;
|
||||||
|
}
|
||||||
|
if (message == 'Update already in progress') return updateAlreadyInProgress;
|
||||||
|
|
||||||
|
return localizedError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
String devAction(String? message) {
|
||||||
|
if (message == null) return '';
|
||||||
|
|
||||||
|
final seeded = RegExp(
|
||||||
|
r'Seeded (?:default products )?\((\d+) total\)',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (seeded != null) {
|
||||||
|
return devActionSeededProducts(int.parse(seeded.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final reseeded = RegExp(
|
||||||
|
r'Cleared and reseeded \((\d+) products\)',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (reseeded != null) {
|
||||||
|
return devActionReseededProducts(int.parse(reseeded.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final resetStock = RegExp(
|
||||||
|
r'Reset stock for (\d+) products',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (resetStock != null) {
|
||||||
|
return devActionResetStock(int.parse(resetStock.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final seededDemo = RegExp(
|
||||||
|
r'Seeded (\d+) demo products',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (seededDemo != null) {
|
||||||
|
return devActionSeededProducts(int.parse(seededDemo.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final testTab = RegExp(
|
||||||
|
r'Created test tab with (\d+) items',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (testTab != null) {
|
||||||
|
return devActionTestTab(int.parse(testTab.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final testProducts = RegExp(
|
||||||
|
r'Added (\d+) test products',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (testProducts != null) {
|
||||||
|
return devActionTestProducts(int.parse(testProducts.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final lowStock = RegExp(
|
||||||
|
r'Simulated low stock for (\d+) products',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (lowStock != null) {
|
||||||
|
return devActionLowStock(int.parse(lowStock.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
final mockOrders = RegExp(
|
||||||
|
r'Generated (\d+) mock orders across (\d+) customers',
|
||||||
|
).firstMatch(message);
|
||||||
|
if (mockOrders != null) {
|
||||||
|
return devActionMockOrders(
|
||||||
|
int.parse(mockOrders.group(1)!),
|
||||||
|
int.parse(mockOrders.group(2)!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (message) {
|
||||||
|
'Cleared all open tabs' => devActionClearedOpenTabs,
|
||||||
|
'Cleared closed tab history' => devActionClearedHistory,
|
||||||
|
'Cleared all products' => devActionClearedProducts,
|
||||||
|
'Product images cleared' => devActionImagesCleared,
|
||||||
|
'PIN reset (no PIN required)' => devActionResetPin,
|
||||||
|
'No products to add to test tab' => devActionNoProducts,
|
||||||
|
'No products available — seed demo data first' => devActionNoProducts,
|
||||||
|
'Product images reloaded' => devActionImagesReloaded,
|
||||||
|
'Image cache cleared (no-op in debug mode)' => devActionCacheCleared,
|
||||||
|
_ when message.startsWith('Failed to clear images') =>
|
||||||
|
devActionFailedImages,
|
||||||
|
_ => message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,734 @@
|
|||||||
|
// ignore: unused_import
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// The translations for Dutch Flemish (`nl`).
|
||||||
|
class AppLocalizationsNl extends AppLocalizations {
|
||||||
|
AppLocalizationsNl([String locale = 'nl']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get appTitle => 'KoolTab';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get barTabs => 'Poefs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get products => 'Producten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addProduct => 'Product toevoegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noProductsYet => 'Nog geen producten.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addFirstProduct => 'Voeg je eerste product toe om te beginnen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get manageProducts => 'Producten beheren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tabHistory => 'Geschiedenis';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get refresh => 'Vernieuwen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings => 'Instellingen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get logout => 'Uitloggen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openOrSelectTab => 'Open of selecteer eerst een poef.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openNewTab => 'Nieuw poef openen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get customerGroupName => 'Naam klant / groep';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancel => 'Annuleren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openTab => 'Poef openen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String couldNotCreateTab(String error) {
|
||||||
|
return 'Poef kon niet worden aangemaakt: $error';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchByName => 'Zoeken op naam…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allCustomers => 'Alle klanten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get customer => 'Klant';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noClosedTabs => 'Nog geen gesloten poefs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsTitle => 'Instellingen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Beveiliging';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pinRequired => 'PIN vereist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get changePin => 'PIN wijzigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get appearance => 'Uiterlijk';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get language => 'Taal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get languageSystem => 'Systeemstandaard';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get languageEnglish => 'Engels';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get languageDutch => 'Nederlands';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get theme => 'Thema';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get system => 'Systeem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get light => 'Licht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dark => 'Donker';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ugly => 'Lelijk';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get setPinCode => 'PIN instellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPin => 'PIN invoeren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterCurrentPin => 'Huidige PIN invoeren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterNewPin => 'Nieuwe PIN invoeren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fourDigits => '4 cijfers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPinFourDigits => 'PIN invoeren (4 cijfers)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get confirm => 'Bevestigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get confirmPin => 'PIN bevestigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pinExactlyFour => 'PIN moet precies 4 cijfers bevatten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotSavePin => 'PIN-instelling kon niet worden opgeslagen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editProduct => 'Product bewerken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productName => 'Productnaam';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get category => 'Categorie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get price => 'Prijs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get stock => 'Voorraad';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lowStockThreshold => 'Drempel lage voorraad';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get chooseProductImage => 'Productafbeelding kiezen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get changeImage => 'Afbeelding wijzigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteProduct => 'Product verwijderen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteProductDescription =>
|
||||||
|
'Dit verwijdert het product uit de productlijst.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete => 'Verwijderen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productNotFound => 'Product niet gevonden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get chooseImage => 'Kies een productafbeelding.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectCategory => 'Selecteer een categorie.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterProductName => 'Voer een productnaam in.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String version(String version) {
|
||||||
|
return 'Versie $version';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectTabToAddItems => 'Selecteer een poef om items toe te voegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openTabs => 'Open poefs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOrOpenTab => 'Selecteer of open een poef';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noOpenTabs => 'Geen open poefs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get edit => 'Bewerken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get close => 'Sluiten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String tabItemSummary(int count, String total) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count items - $total',
|
||||||
|
one: '1 item - $total',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String tabItemCount(int count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count items',
|
||||||
|
one: '1 item',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tapProductsToAdd => 'Tik op producten om ze toe te voegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get total => 'Totaal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get closeTab => 'Poef sluiten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String productStockSummary(String category, String price, int stock) {
|
||||||
|
return '$category • $price • Voorraad: $stock';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get outOfStock => 'UITVERKOCHT';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tabsYouCloseAppearHere => 'Gesloten poefs verschijnen hier.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPinSubtitle => 'Je gebruikt dit om de app te ontgrendelen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get confirmPinSubtitle => 'Voer dezelfde PIN opnieuw in';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createPin => 'PIN aanmaken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pinsDidNotMatch =>
|
||||||
|
'PINs kwamen niet overeen. Probeer het opnieuw.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get somethingWentWrong => 'Er is iets misgegaan.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get incorrectPin => 'Onjuiste PIN.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterPrice => 'Voer een prijs in.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterValidPrice => 'Voer een geldige prijs in.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterValidStock => 'Voer een geldige voorraad in.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enterValidThreshold => 'Voer een geldige drempel in.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadProduct => 'Product kon niet worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotSaveProduct => 'Product kon niet worden opgeslagen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotDeleteProduct => 'Product kon niet worden verwijderd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentMethod => 'Betaalmethode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cash => 'Contant';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get payconiq => 'Payconiq';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String closeTabForCustomer(String customerName) {
|
||||||
|
return 'Poef van $customerName sluiten?';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String currentTotal(String total) {
|
||||||
|
return 'Huidig totaal: $total';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotCloseTab => 'Poef kon niet worden gesloten.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get closingTab => 'Poef sluiten…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get slideToConfirmClosing => 'Schuif om het sluiten te bevestigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorScreen => 'Foutscherm';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unexpectedError =>
|
||||||
|
'Er is een onverwachte fout opgetreden.\nProbeer de app opnieuw te starten.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get goToBarScreen => 'Naar barkassa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updates => 'Updates';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get checkingForUpdates => 'Controleren op updates…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get checkForUpdates => 'Controleren op updates';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get latestVersion => 'Je gebruikt al de nieuwste versie.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateCheckFailed => 'Controleren op updates mislukt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateAvailable => 'Update beschikbaar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String versionAvailable(String version) {
|
||||||
|
return 'Versie $version is beschikbaar.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get later => 'Later';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get update => 'Updaten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateReady => 'Update klaar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateReadyDescription =>
|
||||||
|
'De update is gedownload en geïnstalleerd. Start de app nu opnieuw om de wijzigingen toe te passen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get restartApp => 'App opnieuw starten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateFailed => 'Update mislukt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get retry => 'Opnieuw proberen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updating => 'Updaten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installing => 'Installeren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get doNotCloseDuringUpdate => 'Sluit de app niet tijdens de update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelUpdate => 'Update annuleren?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelUpdateDescription =>
|
||||||
|
'De update is bezig. Als je nu weggaat, kan de app instabiel worden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get continueUpdate => 'Update voortzetten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelUpdateAction => 'Update annuleren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get preparingDownload => 'Download voorbereiden…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulatedDownloadError =>
|
||||||
|
'Gesimuleerde downloadfout (netwerktime-out)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get downloadFailed => 'Download mislukt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String downloading(String percent) {
|
||||||
|
return 'Downloaden $percent%';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installingUpdate => 'Update installeren…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateInstalledRestarting =>
|
||||||
|
'Update geïnstalleerd! Opnieuw starten…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installationFailed => 'Installatie mislukt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateAlreadyInProgress => 'Update is al bezig';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateAlreadyRunning => 'Update wordt al uitgevoerd';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get genericError => 'Er is een onverwachte fout opgetreden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadBar => 'Barkassa kon niet worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotCreateTabMessage => 'Poef kon niet worden aangemaakt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productOutOfStock => 'Product is niet op voorraad.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotAddProductToTab =>
|
||||||
|
'Product kon niet aan de poef worden toegevoegd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotUpdateQuantity => 'Aantal kon niet worden bijgewerkt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotReloadTabs => 'Poefs konden niet opnieuw worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadHistory => 'Geschiedenis kon niet worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadMoreTabs => 'Meer poefs konden niet worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadProducts => 'Producten konden niet worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotAddProduct => 'Product kon niet worden toegevoegd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotUpdateProduct => 'Product kon niet worden bijgewerkt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotDeleteProductMessage =>
|
||||||
|
'Product kon niet worden verwijderd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotCheckPinStatus =>
|
||||||
|
'PIN-status kon niet worden gecontroleerd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotVerifyPin => 'PIN kon niet worden gecontroleerd.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get currentPinIncorrect => 'Huidige PIN is onjuist.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotLoadSettings => 'Instellingen konden niet worden geladen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get couldNotSaveSettings =>
|
||||||
|
'Instellingen konden niet worden opgeslagen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get downloadFailedWithDetail => 'Download mislukt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get installationFailedWithDetail => 'Installatie mislukt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devMenu => 'Ontwikkelaarsmenu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dataManagement => 'Databeheer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearOpenTabs => 'Alle open poefs wissen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearClosedHistory => 'Geschiedenis van gesloten poefs wissen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get seedDefaultProducts => 'Standaardproducten vullen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get onlySeedsWhenEmpty => 'Alleen vullen als de tabel leeg is';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAndReseedProducts => 'Producten wissen en opnieuw vullen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wipesAndReseeds =>
|
||||||
|
'Wist alle producten en vult de standaardproducten opnieuw';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearAllProducts => 'Alle producten wissen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearProductImages => 'Productafbeeldingen wissen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deletesProductImages =>
|
||||||
|
'Verwijdert afbeeldingen uit de map product_images';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugging => 'Debuggen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resetPin => 'PIN resetten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get removePinLock => 'PIN-vergrendeling verwijderen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get toggleDebugOverlay => 'Debug-overlay in-/uitschakelen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showFpsMemoryWidgets => 'FPS, geheugen en widgets tonen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorScreenMenu => 'Foutscherm';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewErrorScreen => 'Het foutscherm bekijken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get featureToggles => 'Functieschakelaars';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulateUpdateDownload => 'Updatedownload simuleren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openDownloadProgress => 'Downloadvoortgang openen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get testingHelpers => 'Testhelpers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createTestTab => 'Testtabblad maken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addRandomItems => 'Tabblad met willekeurige items toevoegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addTestProducts => '100 testproducten toevoegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get stressTestGrid => 'Productraster stresstesten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulateLowStock => 'Lage voorraad simuleren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get setProductsBelowThreshold =>
|
||||||
|
'Alle producten onder de drempel instellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get generateMockOrders => '100 testbestellingen genereren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get randomCustomersItemsAmounts =>
|
||||||
|
'Willekeurige klanten, items en bedragen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get performance => 'Prestaties';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get clearImageCache => 'Afbeeldingencache wissen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reloadProductImages => 'Productafbeeldingen opnieuw laden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugOverlay => 'Debug-overlay';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debugOverlayDescription =>
|
||||||
|
'De debug-overlay toont FPS, geheugengebruik en het aantal widgets. Schakel deze in via Flutter DevTools in debugmodus.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ok => 'OK';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulateUpdate => 'Update simuleren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get chooseSimulationMode => 'Kies een simulatiemodus:';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get successfulDownload => 'Geslaagde download';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get progressThenInstall =>
|
||||||
|
'Voortgang 0→100%, daarna installeren en voltooien';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get downloadError => 'Downloadfout';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get failsWithNetworkTimeout =>
|
||||||
|
'Mislukt bij 50% door een netwerktime-out';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get realDownloadWillFail => 'Echte download (mislukt)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get attemptsFakeUrl => 'Probeert OTA met een valse URL';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryBeer => 'Bier';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryWine => 'Wijn';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categorySoftDrinks => 'Frisdrank';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryCocktails => 'Cocktails';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categorySnacks => 'Snacks';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryCoffee => 'Koffie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryTea => 'Thee';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get categoryOther => 'Overig';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionClearedOpenTabs => 'Alle open tabbladen gewist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionClearedHistory =>
|
||||||
|
'Geschiedenis van gesloten tabbladen gewist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionClearedProducts => 'Alle producten gewist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionSeededProducts(int count) {
|
||||||
|
return '$count producten toegevoegd';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionReseededProducts(int count) {
|
||||||
|
return '$count producten gewist en opnieuw toegevoegd';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionImagesCleared => 'Productafbeeldingen gewist';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionResetPin => 'PIN gereset (geen PIN vereist)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionNoProducts => 'Geen producten beschikbaar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionImagesReloaded => 'Productafbeeldingen opnieuw geladen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionCacheCleared =>
|
||||||
|
'Afbeeldingencache gewist (geen actie in debugmodus)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionLowStock(int count) {
|
||||||
|
return 'Lage voorraad gesimuleerd voor $count producten';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionTestTab(int count) {
|
||||||
|
return 'Testtabblad gemaakt met $count items';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionTestProducts(int count) {
|
||||||
|
return '$count testproducten toegevoegd';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionMockOrders(int orders, int customers) {
|
||||||
|
return '$orders testbestellingen gegenereerd voor $customers klanten';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get devActionFailedImages => 'Afbeeldingen konden niet worden gewist.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String devActionResetStock(int count) {
|
||||||
|
return 'Voorraad gereset voor $count producten';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentCash => 'Contant';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get paymentPayconiq => 'Payconiq';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get simulatedUpdateNotes => 'Bugfixes en prestatieverbeteringen.';
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "nl",
|
||||||
|
"appTitle": "KoolTab",
|
||||||
|
"barTabs": "Poefs",
|
||||||
|
"products": "Producten",
|
||||||
|
"addProduct": "Product toevoegen",
|
||||||
|
"noProductsYet": "Nog geen producten.",
|
||||||
|
"addFirstProduct": "Voeg je eerste product toe om te beginnen.",
|
||||||
|
"manageProducts": "Producten beheren",
|
||||||
|
"tabHistory": "Geschiedenis",
|
||||||
|
"refresh": "Vernieuwen",
|
||||||
|
"settings": "Instellingen",
|
||||||
|
"logout": "Uitloggen",
|
||||||
|
"openOrSelectTab": "Open of selecteer eerst een poef.",
|
||||||
|
"openNewTab": "Nieuw poef openen",
|
||||||
|
"customerGroupName": "Naam klant / groep",
|
||||||
|
"cancel": "Annuleren",
|
||||||
|
"openTab": "Poef openen",
|
||||||
|
"couldNotCreateTab": "Poef kon niet worden aangemaakt: {error}",
|
||||||
|
"@couldNotCreateTab": {"placeholders": {"error": {"type": "String"}}},
|
||||||
|
"searchByName": "Zoeken op naam…",
|
||||||
|
"allCustomers": "Alle klanten",
|
||||||
|
"customer": "Klant",
|
||||||
|
"noClosedTabs": "Nog geen gesloten poefs",
|
||||||
|
"settingsTitle": "Instellingen",
|
||||||
|
"security": "Beveiliging",
|
||||||
|
"pinRequired": "PIN vereist",
|
||||||
|
"changePin": "PIN wijzigen",
|
||||||
|
"appearance": "Uiterlijk",
|
||||||
|
"language": "Taal",
|
||||||
|
"languageSystem": "Systeemstandaard",
|
||||||
|
"languageEnglish": "Engels",
|
||||||
|
"languageDutch": "Nederlands",
|
||||||
|
"theme": "Thema",
|
||||||
|
"system": "Systeem",
|
||||||
|
"light": "Licht",
|
||||||
|
"dark": "Donker",
|
||||||
|
"ugly": "Lelijk",
|
||||||
|
"setPinCode": "PIN instellen",
|
||||||
|
"enterPin": "PIN invoeren",
|
||||||
|
"enterCurrentPin": "Huidige PIN invoeren",
|
||||||
|
"enterNewPin": "Nieuwe PIN invoeren",
|
||||||
|
"fourDigits": "4 cijfers",
|
||||||
|
"enterPinFourDigits": "PIN invoeren (4 cijfers)",
|
||||||
|
"confirm": "Bevestigen",
|
||||||
|
"confirmPin": "PIN bevestigen",
|
||||||
|
"pinExactlyFour": "PIN moet precies 4 cijfers bevatten",
|
||||||
|
"couldNotSavePin": "PIN-instelling kon niet worden opgeslagen.",
|
||||||
|
"editProduct": "Product bewerken",
|
||||||
|
"productName": "Productnaam",
|
||||||
|
"category": "Categorie",
|
||||||
|
"price": "Prijs",
|
||||||
|
"stock": "Voorraad",
|
||||||
|
"lowStockThreshold": "Drempel lage voorraad",
|
||||||
|
"chooseProductImage": "Productafbeelding kiezen",
|
||||||
|
"changeImage": "Afbeelding wijzigen",
|
||||||
|
"deleteProduct": "Product verwijderen?",
|
||||||
|
"deleteProductDescription": "Dit verwijdert het product uit de productlijst.",
|
||||||
|
"delete": "Verwijderen",
|
||||||
|
"productNotFound": "Product niet gevonden.",
|
||||||
|
"chooseImage": "Kies een productafbeelding.",
|
||||||
|
"selectCategory": "Selecteer een categorie.",
|
||||||
|
"enterProductName": "Voer een productnaam in.",
|
||||||
|
"version": "Versie {version}",
|
||||||
|
"@version": {"placeholders": {"version": {"type": "String"}}},
|
||||||
|
"selectTabToAddItems": "Selecteer een poef om items toe te voegen",
|
||||||
|
"openTabs": "Open poefs",
|
||||||
|
"selectOrOpenTab": "Selecteer of open een poef",
|
||||||
|
"noOpenTabs": "Geen open poefs",
|
||||||
|
"edit": "Bewerken",
|
||||||
|
"close": "Sluiten",
|
||||||
|
"tabItemSummary": "{count, plural, =1 {1 item - {total}} other {{count} items - {total}}}",
|
||||||
|
"@tabItemSummary": {"placeholders": {"count": {"type": "int"}, "total": {"type": "String"}}},
|
||||||
|
"tabItemCount": "{count, plural, =1 {1 item} other {{count} items}}",
|
||||||
|
"@tabItemCount": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"tapProductsToAdd": "Tik op producten om ze toe te voegen",
|
||||||
|
"total": "Totaal",
|
||||||
|
"closeTab": "Poef sluiten",
|
||||||
|
"productStockSummary": "{category} • {price} • Voorraad: {stock}",
|
||||||
|
"@productStockSummary": {"placeholders": {"category": {"type": "String"}, "price": {"type": "String"}, "stock": {"type": "int"}}},
|
||||||
|
"outOfStock": "UITVERKOCHT",
|
||||||
|
"tabsYouCloseAppearHere": "Gesloten poefs verschijnen hier.",
|
||||||
|
"enterPinSubtitle": "Je gebruikt dit om de app te ontgrendelen",
|
||||||
|
"confirmPinSubtitle": "Voer dezelfde PIN opnieuw in",
|
||||||
|
"createPin": "PIN aanmaken",
|
||||||
|
"pinsDidNotMatch": "PINs kwamen niet overeen. Probeer het opnieuw.",
|
||||||
|
"somethingWentWrong": "Er is iets misgegaan.",
|
||||||
|
"incorrectPin": "Onjuiste PIN.",
|
||||||
|
"enterPrice": "Voer een prijs in.",
|
||||||
|
"enterValidPrice": "Voer een geldige prijs in.",
|
||||||
|
"enterValidStock": "Voer een geldige voorraad in.",
|
||||||
|
"enterValidThreshold": "Voer een geldige drempel in.",
|
||||||
|
"couldNotLoadProduct": "Product kon niet worden geladen.",
|
||||||
|
"couldNotSaveProduct": "Product kon niet worden opgeslagen.",
|
||||||
|
"couldNotDeleteProduct": "Product kon niet worden verwijderd.",
|
||||||
|
"paymentMethod": "Betaalmethode",
|
||||||
|
"cash": "Contant",
|
||||||
|
"payconiq": "Payconiq",
|
||||||
|
"closeTabForCustomer": "Poef van {customerName} sluiten?",
|
||||||
|
"@closeTabForCustomer": {"placeholders": {"customerName": {"type": "String"}}},
|
||||||
|
"currentTotal": "Huidig totaal: {total}",
|
||||||
|
"@currentTotal": {"placeholders": {"total": {"type": "String"}}},
|
||||||
|
"couldNotCloseTab": "Poef kon niet worden gesloten.",
|
||||||
|
"closingTab": "Poef sluiten…",
|
||||||
|
"slideToConfirmClosing": "Schuif om het sluiten te bevestigen",
|
||||||
|
"errorScreen": "Foutscherm",
|
||||||
|
"unexpectedError": "Er is een onverwachte fout opgetreden.\nProbeer de app opnieuw te starten.",
|
||||||
|
"goToBarScreen": "Naar barkassa",
|
||||||
|
"updates": "Updates",
|
||||||
|
"checkingForUpdates": "Controleren op updates…",
|
||||||
|
"checkForUpdates": "Controleren op updates",
|
||||||
|
"latestVersion": "Je gebruikt al de nieuwste versie.",
|
||||||
|
"updateCheckFailed": "Controleren op updates mislukt.",
|
||||||
|
"updateAvailable": "Update beschikbaar",
|
||||||
|
"versionAvailable": "Versie {version} is beschikbaar.",
|
||||||
|
"@versionAvailable": {"placeholders": {"version": {"type": "String"}}},
|
||||||
|
"later": "Later",
|
||||||
|
"update": "Updaten",
|
||||||
|
"updateReady": "Update klaar",
|
||||||
|
"updateReadyDescription": "De update is gedownload en geïnstalleerd. Start de app nu opnieuw om de wijzigingen toe te passen.",
|
||||||
|
"restartApp": "App opnieuw starten",
|
||||||
|
"updateFailed": "Update mislukt",
|
||||||
|
"retry": "Opnieuw proberen",
|
||||||
|
"updating": "Updaten",
|
||||||
|
"installing": "Installeren",
|
||||||
|
"doNotCloseDuringUpdate": "Sluit de app niet tijdens de update",
|
||||||
|
"cancelUpdate": "Update annuleren?",
|
||||||
|
"cancelUpdateDescription": "De update is bezig. Als je nu weggaat, kan de app instabiel worden.",
|
||||||
|
"continueUpdate": "Update voortzetten",
|
||||||
|
"cancelUpdateAction": "Update annuleren",
|
||||||
|
"preparingDownload": "Download voorbereiden…",
|
||||||
|
"simulatedDownloadError": "Gesimuleerde downloadfout (netwerktime-out)",
|
||||||
|
"downloadFailed": "Download mislukt",
|
||||||
|
"downloading": "Downloaden {percent}%",
|
||||||
|
"@downloading": {"placeholders": {"percent": {"type": "String"}}},
|
||||||
|
"installingUpdate": "Update installeren…",
|
||||||
|
"updateInstalledRestarting": "Update geïnstalleerd! Opnieuw starten…",
|
||||||
|
"installationFailed": "Installatie mislukt",
|
||||||
|
"updateAlreadyInProgress": "Update is al bezig",
|
||||||
|
"updateAlreadyRunning": "Update wordt al uitgevoerd",
|
||||||
|
"genericError": "Er is een onverwachte fout opgetreden.",
|
||||||
|
"couldNotLoadBar": "Barkassa kon niet worden geladen.",
|
||||||
|
"couldNotCreateTabMessage": "Poef kon niet worden aangemaakt.",
|
||||||
|
"productOutOfStock": "Product is niet op voorraad.",
|
||||||
|
"couldNotAddProductToTab": "Product kon niet aan de poef worden toegevoegd.",
|
||||||
|
"couldNotUpdateQuantity": "Aantal kon niet worden bijgewerkt.",
|
||||||
|
"couldNotReloadTabs": "Poefs konden niet opnieuw worden geladen.",
|
||||||
|
"couldNotLoadHistory": "Geschiedenis kon niet worden geladen.",
|
||||||
|
"couldNotLoadMoreTabs": "Meer poefs konden niet worden geladen.",
|
||||||
|
"couldNotLoadProducts": "Producten konden niet worden geladen.",
|
||||||
|
"couldNotAddProduct": "Product kon niet worden toegevoegd.",
|
||||||
|
"couldNotUpdateProduct": "Product kon niet worden bijgewerkt.",
|
||||||
|
"couldNotDeleteProductMessage": "Product kon niet worden verwijderd.",
|
||||||
|
"couldNotCheckPinStatus": "PIN-status kon niet worden gecontroleerd.",
|
||||||
|
"couldNotVerifyPin": "PIN kon niet worden gecontroleerd.",
|
||||||
|
"currentPinIncorrect": "Huidige PIN is onjuist.",
|
||||||
|
"couldNotLoadSettings": "Instellingen konden niet worden geladen.",
|
||||||
|
"couldNotSaveSettings": "Instellingen konden niet worden opgeslagen.",
|
||||||
|
"downloadFailedWithDetail": "Download mislukt.",
|
||||||
|
"installationFailedWithDetail": "Installatie mislukt.",
|
||||||
|
"devMenu": "Ontwikkelaarsmenu",
|
||||||
|
"dataManagement": "Databeheer",
|
||||||
|
"clearOpenTabs": "Alle open poefs wissen",
|
||||||
|
"clearClosedHistory": "Geschiedenis van gesloten poefs wissen",
|
||||||
|
"seedDefaultProducts": "Standaardproducten vullen",
|
||||||
|
"onlySeedsWhenEmpty": "Alleen vullen als de tabel leeg is",
|
||||||
|
"clearAndReseedProducts": "Producten wissen en opnieuw vullen",
|
||||||
|
"wipesAndReseeds": "Wist alle producten en vult de standaardproducten opnieuw",
|
||||||
|
"clearAllProducts": "Alle producten wissen",
|
||||||
|
"clearProductImages": "Productafbeeldingen wissen",
|
||||||
|
"deletesProductImages": "Verwijdert afbeeldingen uit de map product_images",
|
||||||
|
"debugging": "Debuggen",
|
||||||
|
"resetPin": "PIN resetten",
|
||||||
|
"removePinLock": "PIN-vergrendeling verwijderen",
|
||||||
|
"toggleDebugOverlay": "Debug-overlay in-/uitschakelen",
|
||||||
|
"showFpsMemoryWidgets": "FPS, geheugen en widgets tonen",
|
||||||
|
"errorScreenMenu": "Foutscherm",
|
||||||
|
"viewErrorScreen": "Het foutscherm bekijken",
|
||||||
|
"featureToggles": "Functieschakelaars",
|
||||||
|
"simulateUpdateDownload": "Updatedownload simuleren",
|
||||||
|
"openDownloadProgress": "Downloadvoortgang openen",
|
||||||
|
"testingHelpers": "Testhelpers",
|
||||||
|
"createTestTab": "Testtabblad maken",
|
||||||
|
"addRandomItems": "Tabblad met willekeurige items toevoegen",
|
||||||
|
"addTestProducts": "100 testproducten toevoegen",
|
||||||
|
"stressTestGrid": "Productraster stresstesten",
|
||||||
|
"simulateLowStock": "Lage voorraad simuleren",
|
||||||
|
"setProductsBelowThreshold": "Alle producten onder de drempel instellen",
|
||||||
|
"generateMockOrders": "100 testbestellingen genereren",
|
||||||
|
"randomCustomersItemsAmounts": "Willekeurige klanten, items en bedragen",
|
||||||
|
"performance": "Prestaties",
|
||||||
|
"clearImageCache": "Afbeeldingencache wissen",
|
||||||
|
"reloadProductImages": "Productafbeeldingen opnieuw laden",
|
||||||
|
"debugOverlay": "Debug-overlay",
|
||||||
|
"debugOverlayDescription": "De debug-overlay toont FPS, geheugengebruik en het aantal widgets. Schakel deze in via Flutter DevTools in debugmodus.",
|
||||||
|
"ok": "OK",
|
||||||
|
"simulateUpdate": "Update simuleren",
|
||||||
|
"chooseSimulationMode": "Kies een simulatiemodus:",
|
||||||
|
"successfulDownload": "Geslaagde download",
|
||||||
|
"progressThenInstall": "Voortgang 0→100%, daarna installeren en voltooien",
|
||||||
|
"downloadError": "Downloadfout",
|
||||||
|
"failsWithNetworkTimeout": "Mislukt bij 50% door een netwerktime-out",
|
||||||
|
"realDownloadWillFail": "Echte download (mislukt)",
|
||||||
|
"attemptsFakeUrl": "Probeert OTA met een valse URL",
|
||||||
|
"categoryBeer": "Bier",
|
||||||
|
"categoryWine": "Wijn",
|
||||||
|
"categorySoftDrinks": "Frisdrank",
|
||||||
|
"categoryCocktails": "Cocktails",
|
||||||
|
"categorySnacks": "Snacks",
|
||||||
|
"categoryCoffee": "Koffie",
|
||||||
|
"categoryTea": "Thee",
|
||||||
|
"categoryOther": "Overig",
|
||||||
|
"devActionClearedOpenTabs": "Alle open tabbladen gewist",
|
||||||
|
"devActionClearedHistory": "Geschiedenis van gesloten tabbladen gewist",
|
||||||
|
"devActionClearedProducts": "Alle producten gewist",
|
||||||
|
"devActionSeededProducts": "{count} producten toegevoegd",
|
||||||
|
"@devActionSeededProducts": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionReseededProducts": "{count} producten gewist en opnieuw toegevoegd",
|
||||||
|
"@devActionReseededProducts": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionImagesCleared": "Productafbeeldingen gewist",
|
||||||
|
"devActionResetPin": "PIN gereset (geen PIN vereist)",
|
||||||
|
"devActionNoProducts": "Geen producten beschikbaar",
|
||||||
|
"devActionImagesReloaded": "Productafbeeldingen opnieuw geladen",
|
||||||
|
"devActionCacheCleared": "Afbeeldingencache gewist (geen actie in debugmodus)",
|
||||||
|
"devActionLowStock": "Lage voorraad gesimuleerd voor {count} producten",
|
||||||
|
"@devActionLowStock": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionTestTab": "Testtabblad gemaakt met {count} items",
|
||||||
|
"@devActionTestTab": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionTestProducts": "{count} testproducten toegevoegd",
|
||||||
|
"@devActionTestProducts": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"devActionMockOrders": "{orders} testbestellingen gegenereerd voor {customers} klanten",
|
||||||
|
"@devActionMockOrders": {"placeholders": {"orders": {"type": "int"}, "customers": {"type": "int"}}},
|
||||||
|
"devActionFailedImages": "Afbeeldingen konden niet worden gewist.",
|
||||||
|
"devActionResetStock": "Voorraad gereset voor {count} producten",
|
||||||
|
"@devActionResetStock": {"placeholders": {"count": {"type": "int"}}},
|
||||||
|
"paymentCash": "Contant",
|
||||||
|
"paymentPayconiq": "Payconiq",
|
||||||
|
"simulatedUpdateNotes": "Bugfixes en prestatieverbeteringen."
|
||||||
|
}
|
||||||
@@ -1,15 +1,26 @@
|
|||||||
enum AppThemeMode { system, light, dark, ugly }
|
enum AppThemeMode { system, light, dark, ugly }
|
||||||
|
enum AppLanguage { system, english, dutch }
|
||||||
|
|
||||||
class AppSettings {
|
class AppSettings {
|
||||||
final bool pinRequired;
|
final bool pinRequired;
|
||||||
final AppThemeMode themeMode;
|
final AppThemeMode themeMode;
|
||||||
|
final AppLanguage language;
|
||||||
|
|
||||||
const AppSettings({required this.pinRequired, required this.themeMode});
|
const AppSettings({
|
||||||
|
required this.pinRequired,
|
||||||
|
required this.themeMode,
|
||||||
|
this.language = AppLanguage.system,
|
||||||
|
});
|
||||||
|
|
||||||
AppSettings copyWith({bool? pinRequired, AppThemeMode? themeMode}) {
|
AppSettings copyWith({
|
||||||
|
bool? pinRequired,
|
||||||
|
AppThemeMode? themeMode,
|
||||||
|
AppLanguage? language,
|
||||||
|
}) {
|
||||||
return AppSettings(
|
return AppSettings(
|
||||||
pinRequired: pinRequired ?? this.pinRequired,
|
pinRequired: pinRequired ?? this.pinRequired,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
|
language: language ?? this.language,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +34,9 @@ class AppSettings {
|
|||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is AppSettings &&
|
other is AppSettings &&
|
||||||
pinRequired == other.pinRequired &&
|
pinRequired == other.pinRequired &&
|
||||||
themeMode == other.themeMode;
|
themeMode == other.themeMode &&
|
||||||
|
language == other.language;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(pinRequired, themeMode);
|
int get hashCode => Object.hash(pinRequired, themeMode, language);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ class DriftSettingsService implements SettingsService {
|
|||||||
(mode) => mode.name == row.themeMode,
|
(mode) => mode.name == row.themeMode,
|
||||||
orElse: () => AppThemeMode.system,
|
orElse: () => AppThemeMode.system,
|
||||||
),
|
),
|
||||||
|
language: AppLanguage.values.firstWhere(
|
||||||
|
(language) => language.name == row.language,
|
||||||
|
orElse: () => AppLanguage.system,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +50,7 @@ class DriftSettingsService implements SettingsService {
|
|||||||
id: _settingsId,
|
id: _settingsId,
|
||||||
pinRequired: Value(settings.pinRequired),
|
pinRequired: Value(settings.pinRequired),
|
||||||
themeMode: Value(settings.themeMode.name),
|
themeMode: Value(settings.themeMode.name),
|
||||||
|
language: Value(settings.language.name),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:kooltab2/utils/app_update_util.dart';
|
import 'package:kooltab2/utils/app_update_util.dart';
|
||||||
|
|
||||||
import 'app_config.dart';
|
import 'app_config.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class UpdateChecker {
|
class UpdateChecker {
|
||||||
static bool _hasChecked = false;
|
static bool _hasChecked = false;
|
||||||
@@ -39,16 +40,18 @@ class UpdateChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _showUpdateDialog(BuildContext context, UpdateInfo update) {
|
static void _showUpdateDialog(BuildContext context, UpdateInfo update) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: !update.mandatory,
|
barrierDismissible: !update.mandatory,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: const Text("Update available"),
|
title: Text(l10n.updateAvailable),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Version ${update.version} is available."),
|
Text(l10n.versionAvailable(update.version)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(update.notes),
|
Text(update.notes),
|
||||||
],
|
],
|
||||||
@@ -57,14 +60,14 @@ class UpdateChecker {
|
|||||||
if (!update.mandatory)
|
if (!update.mandatory)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
child: const Text("Later"),
|
child: Text(l10n.later),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
context.push('/update-progress', extra: update);
|
context.push('/update-progress', extra: update);
|
||||||
},
|
},
|
||||||
child: const Text("Update"),
|
child: Text(l10n.update),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ class SettingsViewModel extends ChangeNotifier {
|
|||||||
Future<void> updateThemeMode(AppThemeMode mode) =>
|
Future<void> updateThemeMode(AppThemeMode mode) =>
|
||||||
_save(_settings.copyWith(themeMode: mode));
|
_save(_settings.copyWith(themeMode: mode));
|
||||||
|
|
||||||
|
Future<void> updateLanguage(AppLanguage language) =>
|
||||||
|
_save(_settings.copyWith(language: language));
|
||||||
|
|
||||||
Future<void> _save(AppSettings updated) async {
|
Future<void> _save(AppSettings updated) async {
|
||||||
final previous = _settings;
|
final previous = _settings;
|
||||||
_settings = updated;
|
_settings = updated;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import '../models/product.dart';
|
|||||||
import '../models/tab_item.dart';
|
import '../models/tab_item.dart';
|
||||||
import '../utils/app_updater.dart';
|
import '../utils/app_updater.dart';
|
||||||
import '../viewmodels/bar_screen_view_model.dart';
|
import '../viewmodels/bar_screen_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class BarScreenView extends StatefulWidget {
|
class BarScreenView extends StatefulWidget {
|
||||||
const BarScreenView({super.key});
|
const BarScreenView({super.key});
|
||||||
@@ -33,40 +35,41 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
final viewModel = context.watch<BarScreenViewModel>();
|
final viewModel = context.watch<BarScreenViewModel>();
|
||||||
final productsViewModel = context.watch<ProductListViewModel>();
|
final productsViewModel = context.watch<ProductListViewModel>();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Bar Tabs'),
|
title: Text(l10n.barTabs),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Manage products',
|
tooltip: l10n.manageProducts,
|
||||||
onPressed: () => context.go('/products'),
|
onPressed: () => context.go('/products'),
|
||||||
icon: const Icon(Icons.inventory_2_outlined),
|
icon: const Icon(Icons.inventory_2_outlined),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Tab history',
|
tooltip: l10n.tabHistory,
|
||||||
onPressed: () => context.go('/history'),
|
onPressed: () => context.go('/history'),
|
||||||
icon: const Icon(Icons.history_rounded),
|
icon: const Icon(Icons.history_rounded),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Refresh',
|
tooltip: l10n.refresh,
|
||||||
onPressed: () => viewModel.load(),
|
onPressed: () => viewModel.load(),
|
||||||
icon: const Icon(Icons.refresh_rounded),
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Settings',
|
tooltip: l10n.settings,
|
||||||
onPressed: () => context.go('/settings'),
|
onPressed: () => context.go('/settings'),
|
||||||
icon: const Icon(Icons.settings),
|
icon: const Icon(Icons.settings),
|
||||||
),
|
),
|
||||||
if (context.watch<PinLockViewModel>().isPinSet) ...[
|
if (context.watch<PinLockViewModel>().isPinSet) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Logout',
|
tooltip: l10n.logout,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Provider.of<PinLockViewModel>(context, listen: false).lock();
|
Provider.of<PinLockViewModel>(context, listen: false).lock();
|
||||||
},
|
},
|
||||||
@@ -99,7 +102,7 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
viewModel.errorMessage!,
|
l10n.localizedError(viewModel.errorMessage),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
@@ -119,9 +122,7 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
onProductTap: (product) async {
|
onProductTap: (product) async {
|
||||||
if (viewModel.selectedTab == null) {
|
if (viewModel.selectedTab == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
SnackBar(content: Text(l10n.openOrSelectTab)),
|
||||||
content: Text('Open or select a tab first.'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,9 +132,9 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
context,
|
SnackBar(content: Text(l10n.couldNotAddProductToTab)),
|
||||||
).showSnackBar(SnackBar(content: Text('$e')));
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -177,6 +178,7 @@ class _ProductGrid extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
if (products.isEmpty) {
|
if (products.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
@@ -197,19 +199,19 @@ class _ProductGrid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'No products yet',
|
l10n.noProductsYet,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Add your first product to start selling.',
|
l10n.addFirstProduct,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: () => context.go('/products/new'),
|
onPressed: () => context.go('/products/new'),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('Add product'),
|
label: Text(l10n.addProduct),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -223,7 +225,10 @@ class _ProductGrid extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 4),
|
padding: const EdgeInsets.fromLTRB(20, 18, 20, 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text('Products', style: Theme.of(context).textTheme.titleLarge),
|
Text(
|
||||||
|
l10n.products,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
if (!hasSelectedTab)
|
if (!hasSelectedTab)
|
||||||
Flexible(
|
Flexible(
|
||||||
@@ -247,7 +252,7 @@ class _ProductGrid extends StatelessWidget {
|
|||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Select a tab to add items',
|
l10n.selectTabToAddItems,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
@@ -335,6 +340,7 @@ class _TabPanelState extends State<_TabPanel> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
final filteredTabs = widget.tabs.where((tab) {
|
final filteredTabs = widget.tabs.where((tab) {
|
||||||
return tab.customerName.toLowerCase().contains(
|
return tab.customerName.toLowerCase().contains(
|
||||||
@@ -362,8 +368,8 @@ class _TabPanelState extends State<_TabPanel> {
|
|||||||
_searchQuery = value;
|
_searchQuery = value;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Search by name…',
|
hintText: l10n.searchByName,
|
||||||
prefixIcon: Icon(Icons.search_rounded, size: 20),
|
prefixIcon: Icon(Icons.search_rounded, size: 20),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
@@ -375,7 +381,7 @@ class _TabPanelState extends State<_TabPanel> {
|
|||||||
IconButton.filled(
|
IconButton.filled(
|
||||||
onPressed: widget.onNewTabPressed,
|
onPressed: widget.onNewTabPressed,
|
||||||
icon: const Icon(Icons.add_rounded),
|
icon: const Icon(Icons.add_rounded),
|
||||||
tooltip: 'Open new tab',
|
tooltip: l10n.openNewTab,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -385,7 +391,7 @@ class _TabPanelState extends State<_TabPanel> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'OPEN TABS',
|
l10n.openTabs.toUpperCase(),
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
letterSpacing: 0.8,
|
letterSpacing: 0.8,
|
||||||
@@ -438,7 +444,7 @@ class _TabPanelState extends State<_TabPanel> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'Select or open a tab',
|
l10n.selectOrOpenTab,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -472,10 +478,11 @@ class _OpenTabsList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
if (tabs.isEmpty) {
|
if (tabs.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'No open tabs',
|
l10n.noOpenTabs,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -501,7 +508,7 @@ class _OpenTabsList extends StatelessWidget {
|
|||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) => onTabSelected(tab.id),
|
onPressed: (_) => onTabSelected(tab.id),
|
||||||
icon: Icons.edit_outlined,
|
icon: Icons.edit_outlined,
|
||||||
label: 'Edit',
|
label: l10n.edit,
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onSecondary,
|
foregroundColor: Theme.of(context).colorScheme.onSecondary,
|
||||||
),
|
),
|
||||||
@@ -513,13 +520,13 @@ class _OpenTabsList extends StatelessWidget {
|
|||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Could not close tab: $e')),
|
SnackBar(content: Text(l10n.couldNotCloseTab)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: Icons.close_rounded,
|
icon: Icons.close_rounded,
|
||||||
label: 'Close',
|
label: l10n.close,
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onError,
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
||||||
),
|
),
|
||||||
@@ -567,7 +574,10 @@ class _OpenTabsList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'${tab.itemCount} items - ${tab.formattedTotal}',
|
l10n.tabItemSummary(
|
||||||
|
tab.itemCount,
|
||||||
|
tab.formattedTotal,
|
||||||
|
),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -598,6 +608,7 @@ class _SelectedTabDetails extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -629,7 +640,7 @@ class _SelectedTabDetails extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Tap products to add them',
|
l10n.tapProductsToAdd,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -668,7 +679,10 @@ class _SelectedTabDetails extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Total', style: Theme.of(context).textTheme.bodySmall),
|
Text(
|
||||||
|
l10n.total,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
tab.formattedTotal,
|
tab.formattedTotal,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
@@ -680,7 +694,7 @@ class _SelectedTabDetails extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: tab.items.isEmpty ? null : onCloseTabPressed,
|
onPressed: tab.items.isEmpty ? null : onCloseTabPressed,
|
||||||
child: const Text('Close tab'),
|
child: Text(l10n.closeTab),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -699,6 +713,7 @@ class _TabItemRow extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
@@ -739,9 +754,7 @@ class _TabItemRow extends StatelessWidget {
|
|||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(l10n.couldNotUpdateQuantity)),
|
||||||
content: Text('Could not update quantity: $e'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -765,9 +778,7 @@ class _TabItemRow extends StatelessWidget {
|
|||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(l10n.couldNotUpdateQuantity)),
|
||||||
content: Text('Could not update quantity: $e'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import '../utils/app_update_util.dart';
|
import '../utils/app_update_util.dart';
|
||||||
import '../viewmodels/dev_menu_view_model.dart';
|
import '../viewmodels/dev_menu_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class DevMenuView extends StatefulWidget {
|
class DevMenuView extends StatefulWidget {
|
||||||
const DevMenuView({super.key});
|
const DevMenuView({super.key});
|
||||||
@@ -35,10 +37,11 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
|
|
||||||
final message = vm.lastAction;
|
final message = vm.lastAction;
|
||||||
if (message == null) return;
|
if (message == null) return;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(message)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.devAction(message))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -50,6 +53,7 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final vm = context.watch<DevMenuViewModel>();
|
final vm = context.watch<DevMenuViewModel>();
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -57,109 +61,109 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
onPressed: () => context.pop(),
|
onPressed: () => context.pop(),
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
title: const Text('Dev Menu'),
|
title: Text(l10n.devMenu),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
children: [
|
children: [
|
||||||
_Section(
|
_Section(
|
||||||
title: 'Data Management',
|
title: l10n.dataManagement,
|
||||||
children: [
|
children: [
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.delete_sweep_rounded,
|
icon: Icons.delete_sweep_rounded,
|
||||||
title: 'Clear all open tabs',
|
title: l10n.clearOpenTabs,
|
||||||
onTap: vm.isLoading ? null : () => vm.clearOpenTabs(),
|
onTap: vm.isLoading ? null : () => vm.clearOpenTabs(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.history_rounded,
|
icon: Icons.history_rounded,
|
||||||
title: 'Clear closed tab history',
|
title: l10n.clearClosedHistory,
|
||||||
onTap: vm.isLoading ? null : () => vm.clearClosedTabHistory(),
|
onTap: vm.isLoading ? null : () => vm.clearClosedTabHistory(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.add_box_rounded,
|
icon: Icons.add_box_rounded,
|
||||||
title: 'Seed default products',
|
title: l10n.seedDefaultProducts,
|
||||||
subtitle: 'Only seeds if table is empty',
|
subtitle: l10n.onlySeedsWhenEmpty,
|
||||||
onTap: vm.isLoading ? null : () => vm.seedDefaultProducts(),
|
onTap: vm.isLoading ? null : () => vm.seedDefaultProducts(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.restart_alt_rounded,
|
icon: Icons.restart_alt_rounded,
|
||||||
title: 'Clear & reseed products',
|
title: l10n.clearAndReseedProducts,
|
||||||
subtitle: 'Wipes all products, then seeds defaults',
|
subtitle: l10n.wipesAndReseeds,
|
||||||
onTap: vm.isLoading ? null : () => vm.clearAndReseedProducts(),
|
onTap: vm.isLoading ? null : () => vm.clearAndReseedProducts(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.delete_forever_rounded,
|
icon: Icons.delete_forever_rounded,
|
||||||
title: 'Clear all products',
|
title: l10n.clearAllProducts,
|
||||||
onTap: vm.isLoading ? null : () => vm.clearAllProducts(),
|
onTap: vm.isLoading ? null : () => vm.clearAllProducts(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.image_rounded,
|
icon: Icons.image_rounded,
|
||||||
title: 'Clear product images',
|
title: l10n.clearProductImages,
|
||||||
subtitle: 'Deletes images from product_images folder',
|
subtitle: l10n.deletesProductImages,
|
||||||
onTap: vm.isLoading ? null : () => vm.clearProductImages(),
|
onTap: vm.isLoading ? null : () => vm.clearProductImages(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_Section(
|
_Section(
|
||||||
title: 'Debugging',
|
title: l10n.debugging,
|
||||||
children: [
|
children: [
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.lock_reset_rounded,
|
icon: Icons.lock_reset_rounded,
|
||||||
title: 'Reset PIN',
|
title: l10n.resetPin,
|
||||||
subtitle: 'Remove PIN lock',
|
subtitle: l10n.removePinLock,
|
||||||
onTap: vm.isLoading ? null : () => vm.resetPin(),
|
onTap: vm.isLoading ? null : () => vm.resetPin(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.bug_report_rounded,
|
icon: Icons.bug_report_rounded,
|
||||||
title: 'Toggle debug overlay',
|
title: l10n.toggleDebugOverlay,
|
||||||
subtitle: 'Show FPS, memory, widget count',
|
subtitle: l10n.showFpsMemoryWidgets,
|
||||||
onTap: () => _showDebugOverlayInfo(),
|
onTap: () => _showDebugOverlayInfo(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.error_outline_rounded,
|
icon: Icons.error_outline_rounded,
|
||||||
title: 'Error screen',
|
title: l10n.errorScreenMenu,
|
||||||
subtitle: 'View the error screen UI',
|
subtitle: l10n.viewErrorScreen,
|
||||||
onTap: () => context.push('/error'),
|
onTap: () => context.push('/error'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_Section(
|
_Section(
|
||||||
title: 'Feature Toggles',
|
title: l10n.featureToggles,
|
||||||
children: [
|
children: [
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.update_rounded,
|
icon: Icons.update_rounded,
|
||||||
title: 'Simulate update download',
|
title: l10n.simulateUpdateDownload,
|
||||||
subtitle: 'Open the download progress screen',
|
subtitle: l10n.openDownloadProgress,
|
||||||
onTap: () => _showSimulateUpdateDialog(),
|
onTap: () => _showSimulateUpdateDialog(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_Section(
|
_Section(
|
||||||
title: 'Testing Helpers',
|
title: l10n.testingHelpers,
|
||||||
children: [
|
children: [
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.receipt_long_rounded,
|
icon: Icons.receipt_long_rounded,
|
||||||
title: 'Create test tab',
|
title: l10n.createTestTab,
|
||||||
subtitle: 'Add tab with random items',
|
subtitle: l10n.addRandomItems,
|
||||||
onTap: vm.isLoading ? null : () => vm.createTestTab(),
|
onTap: vm.isLoading ? null : () => vm.createTestTab(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.grid_view_rounded,
|
icon: Icons.grid_view_rounded,
|
||||||
title: 'Add 100 test products',
|
title: l10n.addTestProducts,
|
||||||
subtitle: 'Stress test product grid',
|
subtitle: l10n.stressTestGrid,
|
||||||
onTap: vm.isLoading ? null : () => vm.addTestProducts(),
|
onTap: vm.isLoading ? null : () => vm.addTestProducts(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.warning_rounded,
|
icon: Icons.warning_rounded,
|
||||||
title: 'Simulate low stock',
|
title: l10n.simulateLowStock,
|
||||||
subtitle: 'Set all products below threshold',
|
subtitle: l10n.setProductsBelowThreshold,
|
||||||
onTap: vm.isLoading ? null : () => vm.simulateLowStock(),
|
onTap: vm.isLoading ? null : () => vm.simulateLowStock(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.history_rounded,
|
icon: Icons.history_rounded,
|
||||||
title: 'Generate 100 mock orders',
|
title: l10n.generateMockOrders,
|
||||||
subtitle: 'Random customers, items, and amounts',
|
subtitle: l10n.randomCustomersItemsAmounts,
|
||||||
onTap: vm.isLoading
|
onTap: vm.isLoading
|
||||||
? null
|
? null
|
||||||
: () => vm.generateMockOrders(count: 100),
|
: () => vm.generateMockOrders(count: 100),
|
||||||
@@ -167,16 +171,16 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
_Section(
|
_Section(
|
||||||
title: 'Performance',
|
title: l10n.performance,
|
||||||
children: [
|
children: [
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.image_rounded,
|
icon: Icons.image_rounded,
|
||||||
title: 'Clear image cache',
|
title: l10n.clearImageCache,
|
||||||
onTap: vm.isLoading ? null : () => vm.clearImageCache(),
|
onTap: vm.isLoading ? null : () => vm.clearImageCache(),
|
||||||
),
|
),
|
||||||
_Tile(
|
_Tile(
|
||||||
icon: Icons.refresh_rounded,
|
icon: Icons.refresh_rounded,
|
||||||
title: 'Reload product images',
|
title: l10n.reloadProductImages,
|
||||||
onTap: vm.isLoading ? null : () => vm.reloadProductImages(),
|
onTap: vm.isLoading ? null : () => vm.reloadProductImages(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -192,18 +196,17 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showDebugOverlayInfo() {
|
void _showDebugOverlayInfo() {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Debug Overlay'),
|
title: Text(l10n.debugOverlay),
|
||||||
content: const Text(
|
content: Text(l10n.debugOverlayDescription),
|
||||||
'The debug overlay shows FPS, memory usage, and widget counts. '
|
|
||||||
'Enable it via Flutter DevTools in debug mode.',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('OK'),
|
child: Text(l10n.ok),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -211,10 +214,12 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showSimulateUpdateDialog() {
|
void _showSimulateUpdateDialog() {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
final fakeUpdate = UpdateInfo(
|
final fakeUpdate = UpdateInfo(
|
||||||
update: true,
|
update: true,
|
||||||
version: '99.0.0',
|
version: '99.0.0',
|
||||||
notes: 'Bug fixes and performance improvements.',
|
notes: l10n.simulatedUpdateNotes,
|
||||||
mandatory: false,
|
mandatory: false,
|
||||||
sha256: 'abc123',
|
sha256: 'abc123',
|
||||||
download: 'https://example.com/fake-update.apk',
|
download: 'https://example.com/fake-update.apk',
|
||||||
@@ -223,16 +228,16 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: const Text('Simulate update'),
|
title: Text(l10n.simulateUpdate),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Text('Choose a simulation mode:'),
|
Text(l10n.chooseSimulationMode),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_SimOption(
|
_SimOption(
|
||||||
icon: Icons.download_rounded,
|
icon: Icons.download_rounded,
|
||||||
label: 'Successful download',
|
label: l10n.successfulDownload,
|
||||||
description: 'Progress 0→100%, then install, then done',
|
description: l10n.progressThenInstall,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
context.push(
|
context.push(
|
||||||
@@ -244,8 +249,8 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_SimOption(
|
_SimOption(
|
||||||
icon: Icons.error_outline_rounded,
|
icon: Icons.error_outline_rounded,
|
||||||
label: 'Download error',
|
label: l10n.downloadError,
|
||||||
description: 'Fails at 50% with a network timeout',
|
description: l10n.failsWithNetworkTimeout,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
context.push(
|
context.push(
|
||||||
@@ -257,8 +262,8 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_SimOption(
|
_SimOption(
|
||||||
icon: Icons.wifi_off_rounded,
|
icon: Icons.wifi_off_rounded,
|
||||||
label: 'Real download (will fail)',
|
label: l10n.realDownloadWillFail,
|
||||||
description: 'Attempts real OTA with fake URL',
|
description: l10n.attemptsFakeUrl,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
context.push('/update-progress', extra: fakeUpdate);
|
context.push('/update-progress', extra: fakeUpdate);
|
||||||
@@ -269,7 +274,7 @@ class _DevMenuViewState extends State<DevMenuView> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
child: const Text('Cancel'),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -312,7 +317,9 @@ class _SimOption extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
description,
|
description,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -415,8 +422,8 @@ class _Tile extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
subtitle!,
|
subtitle!,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: scheme.onSurface.withValues(alpha: 0.5),
|
color: scheme.onSurface.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||||||
import '../../models/payment_method.dart';
|
import '../../models/payment_method.dart';
|
||||||
import '../../viewmodels/bar_screen_view_model.dart';
|
import '../../viewmodels/bar_screen_view_model.dart';
|
||||||
import '../widgets/slide_confirm.dart';
|
import '../widgets/slide_confirm.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
Future<void> confirmCloseTab(BuildContext context) async {
|
Future<void> confirmCloseTab(BuildContext context) async {
|
||||||
final viewModel = context.read<BarScreenViewModel>();
|
final viewModel = context.read<BarScreenViewModel>();
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
final tab = viewModel.selectedTab;
|
final tab = viewModel.selectedTab;
|
||||||
|
|
||||||
if (tab == null) return;
|
if (tab == null) return;
|
||||||
@@ -21,16 +23,16 @@ Future<void> confirmCloseTab(BuildContext context) async {
|
|||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (context, setDialogState) {
|
builder: (context, setDialogState) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Close ${tab.customerName}ʼs tab?'),
|
title: Text(l10n.closeTabForCustomer(tab.customerName)),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 360,
|
width: 360,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text('Current total: ${tab.formattedTotal}'),
|
Text(l10n.currentTotal(tab.formattedTotal)),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
'Payment method',
|
l10n.paymentMethod,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -53,7 +55,7 @@ Future<void> confirmCloseTab(BuildContext context) async {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||||
child: const Text('Cancel'),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -69,9 +71,9 @@ Future<void> confirmCloseTab(BuildContext context) async {
|
|||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text('Could not close tab: $e')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.couldNotCloseTab)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,22 +81,19 @@ class _PaymentPicker extends StatelessWidget {
|
|||||||
final PaymentMethod selected;
|
final PaymentMethod selected;
|
||||||
final ValueChanged<PaymentMethod> onChanged;
|
final ValueChanged<PaymentMethod> onChanged;
|
||||||
|
|
||||||
const _PaymentPicker({
|
const _PaymentPicker({required this.selected, required this.onChanged});
|
||||||
required this.selected,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const _options = [
|
static const _options = [PaymentMethod.cash, PaymentMethod.payconiq];
|
||||||
(PaymentMethod.cash, 'Cash'),
|
|
||||||
(PaymentMethod.payconiq, 'Payconiq'),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: _options.map((option) {
|
children: _options.map((option) {
|
||||||
final (value, label) = option;
|
final value = option;
|
||||||
|
final label = value == PaymentMethod.cash ? l10n.cash : l10n.payconiq;
|
||||||
final isSelected = selected == value;
|
final isSelected = selected == value;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -117,7 +116,10 @@ class _PaymentPicker extends StatelessWidget {
|
|||||||
'assets/icons/payconic.svg',
|
'assets/icons/payconic.svg',
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
colorFilter: ColorFilter.mode(Colors.pinkAccent, BlendMode.srcIn),
|
colorFilter: ColorFilter.mode(
|
||||||
|
Colors.pinkAccent,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(label),
|
Text(label),
|
||||||
@@ -128,4 +130,4 @@ class _PaymentPicker extends StatelessWidget {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,21 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:kooltab2/viewmodels/bar_screen_view_model.dart';
|
import 'package:kooltab2/viewmodels/bar_screen_view_model.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
Future<void> showNewTabDialog(BuildContext context) async {
|
Future<void> showNewTabDialog(BuildContext context) async {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
|
|
||||||
final name = await showDialog<String>(
|
final name = await showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Open new tab'),
|
title: Text(l10n.openNewTab),
|
||||||
content: TextField(
|
content: TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
decoration: const InputDecoration(labelText: 'Customer / group name'),
|
decoration: InputDecoration(labelText: l10n.customerGroupName),
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
Navigator.of(dialogContext).pop(value);
|
Navigator.of(dialogContext).pop(value);
|
||||||
},
|
},
|
||||||
@@ -23,13 +25,13 @@ Future<void> showNewTabDialog(BuildContext context) async {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||||
child: const Text('Cancel'),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(dialogContext).pop(controller.text);
|
Navigator.of(dialogContext).pop(controller.text);
|
||||||
},
|
},
|
||||||
child: const Text('Open tab'),
|
child: Text(l10n.openTab),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -45,8 +47,8 @@ Future<void> showNewTabDialog(BuildContext context) async {
|
|||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text('Could not create tab: $e')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.couldNotCreateTabMessage)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ErrorScreenView extends StatelessWidget {
|
class ErrorScreenView extends StatelessWidget {
|
||||||
const ErrorScreenView({super.key});
|
const ErrorScreenView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -14,7 +17,7 @@ class ErrorScreenView extends StatelessWidget {
|
|||||||
onPressed: () => context.pop(),
|
onPressed: () => context.pop(),
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
title: const Text('Error Screen'),
|
title: Text(l10n.errorScreen),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
@@ -37,24 +40,24 @@ class ErrorScreenView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text(
|
Text(
|
||||||
'Something went wrong',
|
l10n.somethingWentWrong,
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
style: Theme.of(
|
||||||
color: scheme.error,
|
context,
|
||||||
),
|
).textTheme.headlineMedium?.copyWith(color: scheme.error),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'An unexpected error occurred.\nPlease try restarting the app.',
|
l10n.unexpectedError,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
color: scheme.onSurface.withValues(alpha: 0.6),
|
color: scheme.onSurface.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: () => context.go('/bar'),
|
onPressed: () => context.go('/bar'),
|
||||||
icon: const Icon(Icons.home_rounded),
|
icon: const Icon(Icons.home_rounded),
|
||||||
label: const Text('Go to bar screen'),
|
label: Text(l10n.goToBarScreen),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -62,4 +65,4 @@ class ErrorScreenView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import '../app/router.dart';
|
import '../app/router.dart';
|
||||||
import '../viewmodels/history_view_model.dart';
|
import '../viewmodels/history_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class HistoryScreenView extends StatefulWidget {
|
class HistoryScreenView extends StatefulWidget {
|
||||||
const HistoryScreenView({super.key});
|
const HistoryScreenView({super.key});
|
||||||
@@ -55,6 +57,7 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final viewModel = context.watch<HistoryViewModel>();
|
final viewModel = context.watch<HistoryViewModel>();
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -65,13 +68,13 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
|
|||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
const Text('Tab History'),
|
Text(l10n.tabHistory),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
|
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Refresh',
|
tooltip: l10n.refresh,
|
||||||
onPressed: viewModel.load,
|
onPressed: viewModel.load,
|
||||||
icon: const Icon(Icons.refresh_rounded),
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
),
|
),
|
||||||
@@ -100,7 +103,7 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
viewModel.errorMessage!,
|
l10n.localizedError(viewModel.errorMessage),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
@@ -119,8 +122,8 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: viewModel.search,
|
onChanged: viewModel.search,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Search by name…',
|
hintText: l10n.searchByName,
|
||||||
prefixIcon: Icon(Icons.search_rounded, size: 20),
|
prefixIcon: Icon(Icons.search_rounded, size: 20),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
@@ -141,12 +144,12 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
|
|||||||
: ListView.separated(
|
: ListView.separated(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
||||||
itemCount: viewModel.closedTabs.length +
|
itemCount:
|
||||||
|
viewModel.closedTabs.length +
|
||||||
(viewModel.hasMore || viewModel.isLoadingMore
|
(viewModel.hasMore || viewModel.isLoadingMore
|
||||||
? 1
|
? 1
|
||||||
: 0),
|
: 0),
|
||||||
separatorBuilder: (_, _) =>
|
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||||
const SizedBox(height: 10),
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == viewModel.closedTabs.length) {
|
if (index == viewModel.closedTabs.length) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
@@ -189,6 +192,7 @@ class _CustomerDropdown extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
final isFiltered = selectedCustomer != null;
|
final isFiltered = selectedCustomer != null;
|
||||||
|
|
||||||
return PopupMenuButton<String>(
|
return PopupMenuButton<String>(
|
||||||
@@ -204,17 +208,13 @@ class _CustomerDropdown extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
isFiltered
|
isFiltered ? Icons.people_outline : Icons.people_rounded,
|
||||||
? Icons.people_outline
|
|
||||||
: Icons.people_rounded,
|
|
||||||
size: 18,
|
size: 18,
|
||||||
color: isFiltered
|
color: isFiltered ? null : scheme.primary,
|
||||||
? null
|
|
||||||
: scheme.primary,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
'All customers',
|
l10n.allCustomers,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: isFiltered ? FontWeight.w400 : FontWeight.w700,
|
fontWeight: isFiltered ? FontWeight.w400 : FontWeight.w700,
|
||||||
color: isFiltered ? null : scheme.primary,
|
color: isFiltered ? null : scheme.primary,
|
||||||
@@ -223,8 +223,7 @@ class _CustomerDropdown extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (customerNames.isNotEmpty)
|
if (customerNames.isNotEmpty) const PopupMenuDivider(height: 1),
|
||||||
const PopupMenuDivider(height: 1),
|
|
||||||
...customerNames.map(
|
...customerNames.map(
|
||||||
(name) => PopupMenuItem<String>(
|
(name) => PopupMenuItem<String>(
|
||||||
value: name,
|
value: name,
|
||||||
@@ -237,9 +236,7 @@ class _CustomerDropdown extends StatelessWidget {
|
|||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(child: Text(name, overflow: TextOverflow.ellipsis)),
|
||||||
child: Text(name, overflow: TextOverflow.ellipsis),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -250,9 +247,7 @@ class _CustomerDropdown extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
border: Border.all(color: scheme.onSurface.withValues(alpha: 0.12)),
|
border: Border.all(color: scheme.onSurface.withValues(alpha: 0.12)),
|
||||||
color: isFiltered
|
color: isFiltered ? scheme.primary.withValues(alpha: 0.08) : null,
|
||||||
? scheme.primary.withValues(alpha: 0.08)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -267,7 +262,7 @@ class _CustomerDropdown extends StatelessWidget {
|
|||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
selectedCustomer ?? 'Customer',
|
selectedCustomer ?? l10n.customer,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -297,6 +292,7 @@ class _EmptyState extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -316,16 +312,16 @@ class _EmptyState extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'No closed tabs yet',
|
l10n.noClosedTabs,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Tabs you close will show up here.',
|
l10n.tabsYouCloseAppearHere,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../viewmodels/pin_lock_view_model.dart';
|
import '../viewmodels/pin_lock_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
enum PinEntryMode { unlock, create }
|
enum PinEntryMode { unlock, create }
|
||||||
|
|
||||||
@@ -30,16 +32,14 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
|
|
||||||
bool get _isCreateFlow => widget.mode == PinEntryMode.create;
|
bool get _isCreateFlow => widget.mode == PinEntryMode.create;
|
||||||
|
|
||||||
String get _title {
|
String _title(AppLocalizations l10n) {
|
||||||
if (!_isCreateFlow) return 'Enter PIN';
|
if (!_isCreateFlow) return l10n.enterPin;
|
||||||
return _isConfirmStep ? 'Confirm PIN' : 'Create a PIN';
|
return _isConfirmStep ? l10n.confirmPin : l10n.createPin;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get _subtitle {
|
String? _subtitle(AppLocalizations l10n) {
|
||||||
if (!_isCreateFlow) return null;
|
if (!_isCreateFlow) return null;
|
||||||
return _isConfirmStep
|
return _isConfirmStep ? l10n.confirmPinSubtitle : l10n.enterPinSubtitle;
|
||||||
? 'Enter the same PIN again'
|
|
||||||
: 'Youʼll use this to unlock the app';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDigitPressed(String digit) {
|
void _onDigitPressed(String digit) {
|
||||||
@@ -65,6 +65,7 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
|
|
||||||
Future<void> _handleComplete() async {
|
Future<void> _handleComplete() async {
|
||||||
final viewModel = context.read<PinLockViewModel>();
|
final viewModel = context.read<PinLockViewModel>();
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
if (_isCreateFlow && !_isConfirmStep) {
|
if (_isCreateFlow && !_isConfirmStep) {
|
||||||
// First entry of a new PIN — stash it, then ask for confirmation.
|
// First entry of a new PIN — stash it, then ask for confirmation.
|
||||||
@@ -84,7 +85,7 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
_firstEntry = null;
|
_firstEntry = null;
|
||||||
_isConfirmStep = false;
|
_isConfirmStep = false;
|
||||||
});
|
});
|
||||||
_fail('PINs didnʼt match. Try again.');
|
_fail(l10n.pinsDidNotMatch);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
_firstEntry = null;
|
_firstEntry = null;
|
||||||
_isConfirmStep = false;
|
_isConfirmStep = false;
|
||||||
});
|
});
|
||||||
_fail(viewModel.errorMessage ?? 'Something went wrong.');
|
_fail(l10n.localizedError(viewModel.errorMessage));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,7 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
if (ok) {
|
if (ok) {
|
||||||
widget.onSuccess?.call();
|
widget.onSuccess?.call();
|
||||||
} else {
|
} else {
|
||||||
_fail(viewModel.errorMessage ?? 'Incorrect PIN.');
|
_fail(l10n.localizedError(viewModel.errorMessage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +134,7 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
@@ -143,11 +145,14 @@ class _PinEntryViewState extends State<PinEntryView> {
|
|||||||
const Spacer(flex: 2),
|
const Spacer(flex: 2),
|
||||||
Icon(Icons.lock_outline_rounded, size: 36, color: scheme.primary),
|
Icon(Icons.lock_outline_rounded, size: 36, color: scheme.primary),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(_title, style: Theme.of(context).textTheme.headlineMedium),
|
Text(
|
||||||
if (_subtitle != null) ...[
|
_title(l10n),
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
),
|
||||||
|
if (_subtitle(l10n) != null) ...[
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(
|
Text(
|
||||||
_subtitle!,
|
_subtitle(l10n)!,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||||||
|
|
||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
import '../viewmodels/product_list_view_model.dart';
|
import '../viewmodels/product_list_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class ProductFormView extends StatefulWidget {
|
class ProductFormView extends StatefulWidget {
|
||||||
final String? productId;
|
final String? productId;
|
||||||
@@ -48,6 +50,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadProductIfNeeded() async {
|
Future<void> _loadProductIfNeeded() async {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
if (!widget.isEditing) {
|
if (!widget.isEditing) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
return;
|
return;
|
||||||
@@ -62,7 +65,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
if (product == null) {
|
if (product == null) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('Product not found.')));
|
).showSnackBar(SnackBar(content: Text(l10n.productNotFound)));
|
||||||
|
|
||||||
context.go('/products');
|
context.go('/products');
|
||||||
return;
|
return;
|
||||||
@@ -83,7 +86,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text('Could not load product: $e')));
|
).showSnackBar(SnackBar(content: Text(l10n.couldNotLoadProduct)));
|
||||||
context.go('/products');
|
context.go('/products');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,11 +123,11 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
|
|
||||||
Future<void> _pickImage() async {
|
Future<void> _pickImage() async {
|
||||||
final pickedFile = await _imagePicker.pickImage(
|
final pickedFile = await _imagePicker.pickImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
imageQuality: 80,
|
imageQuality: 80,
|
||||||
maxWidth: 1000,
|
maxWidth: 1000,
|
||||||
maxHeight: 1000,
|
maxHeight: 1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pickedFile == null) return;
|
if (pickedFile == null) return;
|
||||||
|
|
||||||
@@ -146,19 +149,20 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _save() async {
|
Future<void> _save() async {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
if (_imagePath == null || _imagePath!.isEmpty) {
|
if (_imagePath == null || _imagePath!.isEmpty) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('Choose a product image.')));
|
).showSnackBar(SnackBar(content: Text(l10n.chooseImage)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_selectedCategory == null) {
|
if (_selectedCategory == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
const SnackBar(content: Text('Please select a category.')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.selectCategory)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,9 +202,9 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _isSaving = false);
|
setState(() => _isSaving = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text('Could not save product: $e')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.couldNotSaveProduct)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,23 +216,22 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
|
|
||||||
Future<void> _delete() async {
|
Future<void> _delete() async {
|
||||||
if (!widget.isEditing) return;
|
if (!widget.isEditing) return;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Delete product?'),
|
title: Text(l10n.deleteProduct),
|
||||||
content: const Text(
|
content: Text(l10n.deleteProductDescription),
|
||||||
'This will remove the product from the product list.',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||||
child: const Text('Cancel'),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||||
child: const Text('Delete'),
|
child: Text(l10n.delete),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -244,9 +247,9 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text('Could not delete product: $e')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.couldNotDeleteProduct)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +259,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildImagePicker(BuildContext context) {
|
Widget _buildImagePicker(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: _pickImage,
|
onTap: _pickImage,
|
||||||
@@ -289,7 +293,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: _pickImage,
|
onPressed: _pickImage,
|
||||||
icon: const Icon(Icons.image),
|
icon: const Icon(Icons.image),
|
||||||
label: const Text('Change image'),
|
label: Text(l10n.changeImage),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -301,7 +305,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
const Icon(Icons.add_photo_alternate_outlined, size: 48),
|
const Icon(Icons.add_photo_alternate_outlined, size: 48),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Choose product image',
|
l10n.chooseProductImage,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -313,7 +317,8 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final title = widget.isEditing ? 'Edit product' : 'Add product';
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final title = widget.isEditing ? l10n.editProduct : l10n.addProduct;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -345,14 +350,14 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Product name',
|
labelText: l10n.productName,
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.trim().isEmpty) {
|
if (value == null || value.trim().isEmpty) {
|
||||||
return 'Enter a product name.';
|
return l10n.enterProductName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -370,13 +375,13 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
enableSearch: true,
|
enableSearch: true,
|
||||||
controller: _categoryController,
|
controller: _categoryController,
|
||||||
requestFocusOnTap: true,
|
requestFocusOnTap: true,
|
||||||
label: const Text('Category'),
|
label: Text(l10n.category),
|
||||||
hintText: 'Select a category',
|
hintText: l10n.selectCategory,
|
||||||
dropdownMenuEntries: viewModel.categories
|
dropdownMenuEntries: viewModel.categories
|
||||||
.map(
|
.map(
|
||||||
(category) => DropdownMenuEntry<String>(
|
(category) => DropdownMenuEntry<String>(
|
||||||
value: category,
|
value: category,
|
||||||
label: category,
|
label: l10n.categoryLabel(category),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -393,8 +398,8 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _priceController,
|
controller: _priceController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Price',
|
labelText: l10n.price,
|
||||||
prefixText: '€ ',
|
prefixText: '€ ',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
@@ -404,14 +409,14 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.trim().isEmpty) {
|
if (value == null || value.trim().isEmpty) {
|
||||||
return 'Enter a price.';
|
return l10n.enterPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
final normalized = value.replaceAll(',', '.');
|
final normalized = value.replaceAll(',', '.');
|
||||||
final price = double.tryParse(normalized);
|
final price = double.tryParse(normalized);
|
||||||
|
|
||||||
if (price == null || price < 0) {
|
if (price == null || price < 0) {
|
||||||
return 'Enter a valid price.';
|
return l10n.enterValidPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -420,8 +425,8 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _stockController,
|
controller: _stockController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Current stock',
|
labelText: l10n.stock,
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
@@ -430,7 +435,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
final number = int.tryParse(value ?? '');
|
final number = int.tryParse(value ?? '');
|
||||||
|
|
||||||
if (number == null || number < 0) {
|
if (number == null || number < 0) {
|
||||||
return 'Enter a valid stock amount.';
|
return l10n.enterValidStock;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -439,8 +444,8 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _lowStockController,
|
controller: _lowStockController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Low stock warning threshold',
|
labelText: l10n.lowStockThreshold,
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
@@ -448,7 +453,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
final number = int.tryParse(value ?? '');
|
final number = int.tryParse(value ?? '');
|
||||||
|
|
||||||
if (number == null || number < 0) {
|
if (number == null || number < 0) {
|
||||||
return 'Enter a valid threshold.';
|
return l10n.enterValidThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -467,7 +472,7 @@ class _ProductFormViewState extends State<ProductFormView> {
|
|||||||
)
|
)
|
||||||
: const Icon(Icons.save),
|
: const Icon(Icons.save),
|
||||||
label: Text(
|
label: Text(
|
||||||
widget.isEditing ? 'Save changes' : 'Add product',
|
widget.isEditing ? l10n.confirm : l10n.addProduct,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../viewmodels/product_list_view_model.dart';
|
import '../viewmodels/product_list_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class ProductListView extends StatefulWidget {
|
class ProductListView extends StatefulWidget {
|
||||||
const ProductListView({super.key});
|
const ProductListView({super.key});
|
||||||
@@ -26,6 +28,7 @@ class _ProductListViewState extends State<ProductListView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final viewModel = context.watch<ProductListViewModel>();
|
final viewModel = context.watch<ProductListViewModel>();
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -36,7 +39,7 @@ class _ProductListViewState extends State<ProductListView> {
|
|||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Text('Products'),
|
Text(l10n.products),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -44,7 +47,7 @@ class _ProductListViewState extends State<ProductListView> {
|
|||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () => context.go('/products/new'),
|
onPressed: () => context.go('/products/new'),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('Add product'),
|
label: Text(l10n.addProduct),
|
||||||
),
|
),
|
||||||
body: Builder(
|
body: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -53,11 +56,13 @@ class _ProductListViewState extends State<ProductListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.errorMessage != null) {
|
if (viewModel.errorMessage != null) {
|
||||||
return Center(child: Text(viewModel.errorMessage!));
|
return Center(
|
||||||
|
child: Text(l10n.localizedError(viewModel.errorMessage)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.products.isEmpty) {
|
if (viewModel.products.isEmpty) {
|
||||||
return const Center(child: Text('No products yet.'));
|
return Center(child: Text(l10n.noProductsYet));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
@@ -73,22 +78,28 @@ class _ProductListViewState extends State<ProductListView> {
|
|||||||
width: 56,
|
width: 56,
|
||||||
height: 56,
|
height: 56,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: product.imagePath != null &&
|
child:
|
||||||
|
product.imagePath != null &&
|
||||||
product.imagePath!.isNotEmpty
|
product.imagePath!.isNotEmpty
|
||||||
? Image.file(
|
? Image.file(
|
||||||
File(product.imagePath!),
|
File(product.imagePath!),
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
cacheWidth: 112,
|
cacheWidth: 112,
|
||||||
errorBuilder:
|
errorBuilder: (context, error, stackTrace) =>
|
||||||
(context, error, stackTrace) =>
|
const Icon(
|
||||||
const Icon(Icons.image_not_supported_outlined),
|
Icons.image_not_supported_outlined,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.image_not_supported_outlined),
|
: const Icon(Icons.image_not_supported_outlined),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(product.name),
|
title: Text(product.name),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${product.category} • ${product.formattedPrice} • Stock: ${product.stockQuantity}',
|
l10n.productStockSummary(
|
||||||
|
l10n.categoryLabel(product.category),
|
||||||
|
product.formattedPrice,
|
||||||
|
product.stockQuantity,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () => context.go('/products/${product.id}/edit'),
|
onTap: () => context.go('/products/${product.id}/edit'),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import '../models/settings.dart';
|
|||||||
import '../utils/app_update_util.dart';
|
import '../utils/app_update_util.dart';
|
||||||
import '../viewmodels/pin_lock_view_model.dart';
|
import '../viewmodels/pin_lock_view_model.dart';
|
||||||
import '../viewmodels/settings_view_model.dart';
|
import '../viewmodels/settings_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class SettingsScreenView extends StatefulWidget {
|
class SettingsScreenView extends StatefulWidget {
|
||||||
const SettingsScreenView({super.key});
|
const SettingsScreenView({super.key});
|
||||||
@@ -58,8 +60,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _promptPin(String title, {String hint = 'Enter PIN (4 digits)'}) {
|
Future<String?> _promptPin(String title, {String? hint}) {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return showDialog<String>(
|
return showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -81,12 +84,12 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Cancel'),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => Navigator.pop(context, controller.text),
|
onPressed: () => Navigator.pop(context, controller.text),
|
||||||
child: const Text('Confirm'),
|
child: Text(l10n.confirm),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -101,11 +104,12 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _enablePin() async {
|
Future<void> _enablePin() async {
|
||||||
final pin = await _promptPin('Set PIN Code', hint: '4 digits');
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final pin = await _promptPin(l10n.setPinCode, hint: l10n.fourDigits);
|
||||||
if (pin == null) return;
|
if (pin == null) return;
|
||||||
|
|
||||||
if (pin.length != 4) {
|
if (pin.length != 4) {
|
||||||
_showError('PIN must be exactly 4 digits');
|
_showError(l10n.pinExactlyFour);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +117,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
final success = await pinLockViewModel.setPin(pin);
|
final success = await pinLockViewModel.setPin(pin);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
_showError(pinLockViewModel.errorMessage ?? 'Could not set PIN.');
|
_showError(l10n.localizedError(pinLockViewModel.errorMessage));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,19 +127,20 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
await context.read<SettingsViewModel>().updatePinRequired(true);
|
await context.read<SettingsViewModel>().updatePinRequired(true);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
_showError('Could not save PIN setting.');
|
_showError(l10n.couldNotSavePin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _changePin() async {
|
Future<void> _changePin() async {
|
||||||
final current = await _promptPin('Enter Current PIN');
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final current = await _promptPin(l10n.enterCurrentPin);
|
||||||
if (current == null) return;
|
if (current == null) return;
|
||||||
|
|
||||||
final newPin = await _promptPin('Enter New PIN', hint: '4 digits');
|
final newPin = await _promptPin(l10n.enterNewPin, hint: l10n.fourDigits);
|
||||||
if (newPin == null) return;
|
if (newPin == null) return;
|
||||||
|
|
||||||
if (newPin.length != 4) {
|
if (newPin.length != 4) {
|
||||||
_showError('PIN must be exactly 4 digits');
|
_showError(l10n.pinExactlyFour);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,19 +151,20 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
_showError(pinLockViewModel.errorMessage ?? 'Could not change PIN.');
|
_showError(l10n.localizedError(pinLockViewModel.errorMessage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _disablePin() async {
|
Future<void> _disablePin() async {
|
||||||
final current = await _promptPin('Enter Current PIN to Disable');
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final current = await _promptPin(l10n.enterCurrentPin);
|
||||||
if (current == null) return;
|
if (current == null) return;
|
||||||
|
|
||||||
final pinLockViewModel = context.read<PinLockViewModel>();
|
final pinLockViewModel = context.read<PinLockViewModel>();
|
||||||
final success = await pinLockViewModel.disablePin(current);
|
final success = await pinLockViewModel.disablePin(current);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
_showError(pinLockViewModel.errorMessage ?? 'Could not disable PIN.');
|
_showError(l10n.localizedError(pinLockViewModel.errorMessage));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,12 +174,13 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
await context.read<SettingsViewModel>().updatePinRequired(false);
|
await context.read<SettingsViewModel>().updatePinRequired(false);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
_showError('Could not save PIN setting.');
|
_showError(l10n.couldNotSavePin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
final settingsViewModel = context.watch<SettingsViewModel>();
|
final settingsViewModel = context.watch<SettingsViewModel>();
|
||||||
final pinLockViewModel = context.watch<PinLockViewModel>();
|
final pinLockViewModel = context.watch<PinLockViewModel>();
|
||||||
|
|
||||||
@@ -186,7 +193,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
const Text('Settings'),
|
Text(l10n.settingsTitle),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -209,11 +216,11 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
children: [
|
children: [
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Security',
|
title: l10n.security,
|
||||||
children: [
|
children: [
|
||||||
_SettingsSwitchTile(
|
_SettingsSwitchTile(
|
||||||
icon: Icons.lock_outline_rounded,
|
icon: Icons.lock_outline_rounded,
|
||||||
title: 'PIN Required',
|
title: l10n.pinRequired,
|
||||||
value: settings.pinRequired,
|
value: settings.pinRequired,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -226,22 +233,58 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
if (settings.pinRequired)
|
if (settings.pinRequired)
|
||||||
_SettingsTile(
|
_SettingsTile(
|
||||||
icon: Icons.pin_rounded,
|
icon: Icons.pin_rounded,
|
||||||
title: 'Change PIN',
|
title: l10n.changePin,
|
||||||
onTap: _changePin,
|
onTap: _changePin,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Appearance',
|
title: l10n.appearance,
|
||||||
children: [
|
children: [
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.language_rounded,
|
||||||
|
title: l10n.language,
|
||||||
|
subtitle: switch (settings.language) {
|
||||||
|
AppLanguage.system => l10n.languageSystem,
|
||||||
|
AppLanguage.english => l10n.languageEnglish,
|
||||||
|
AppLanguage.dutch => l10n.languageDutch,
|
||||||
|
},
|
||||||
|
onTap: () async {
|
||||||
|
final selected = await showModalBottomSheet<AppLanguage>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: AppLanguage.values.map((language) {
|
||||||
|
return RadioListTile<AppLanguage>(
|
||||||
|
value: language,
|
||||||
|
groupValue: settings.language,
|
||||||
|
title: Text(switch (language) {
|
||||||
|
AppLanguage.system => l10n.languageSystem,
|
||||||
|
AppLanguage.english => l10n.languageEnglish,
|
||||||
|
AppLanguage.dutch => l10n.languageDutch,
|
||||||
|
}),
|
||||||
|
onChanged: (value) =>
|
||||||
|
Navigator.pop(context, value),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
await settingsViewModel.updateLanguage(selected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
_SettingsTile(
|
_SettingsTile(
|
||||||
icon: Icons.brightness_6_rounded,
|
icon: Icons.brightness_6_rounded,
|
||||||
title: 'Theme',
|
title: l10n.theme,
|
||||||
subtitle: switch (settings.themeMode) {
|
subtitle: switch (settings.themeMode) {
|
||||||
AppThemeMode.system => 'System',
|
AppThemeMode.system => l10n.system,
|
||||||
AppThemeMode.light => 'Light',
|
AppThemeMode.light => l10n.light,
|
||||||
AppThemeMode.dark => 'Dark',
|
AppThemeMode.dark => l10n.dark,
|
||||||
AppThemeMode.ugly => 'Ugly',
|
AppThemeMode.ugly => l10n.ugly,
|
||||||
},
|
},
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final selected = await showModalBottomSheet<AppThemeMode>(
|
final selected = await showModalBottomSheet<AppThemeMode>(
|
||||||
@@ -254,10 +297,10 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
value: mode,
|
value: mode,
|
||||||
groupValue: settings.themeMode,
|
groupValue: settings.themeMode,
|
||||||
title: Text(switch (mode) {
|
title: Text(switch (mode) {
|
||||||
AppThemeMode.system => 'System',
|
AppThemeMode.system => l10n.system,
|
||||||
AppThemeMode.light => 'Light',
|
AppThemeMode.light => l10n.light,
|
||||||
AppThemeMode.dark => 'Dark',
|
AppThemeMode.dark => l10n.dark,
|
||||||
AppThemeMode.ugly => 'Ugly',
|
AppThemeMode.ugly => l10n.ugly,
|
||||||
}),
|
}),
|
||||||
onChanged: (value) =>
|
onChanged: (value) =>
|
||||||
Navigator.pop(context, value),
|
Navigator.pop(context, value),
|
||||||
@@ -276,13 +319,13 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
),
|
),
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
_SettingsSection(
|
_SettingsSection(
|
||||||
title: 'Updates',
|
title: l10n.updates,
|
||||||
children: [
|
children: [
|
||||||
_SettingsTile(
|
_SettingsTile(
|
||||||
icon: Icons.system_update_alt_rounded,
|
icon: Icons.system_update_alt_rounded,
|
||||||
title: settingsViewModel.checkingForUpdates
|
title: settingsViewModel.checkingForUpdates
|
||||||
? 'Checking for updates...'
|
? l10n.checkingForUpdates
|
||||||
: 'Check for updates',
|
: l10n.checkForUpdates,
|
||||||
onTap: settingsViewModel.checkingForUpdates
|
onTap: settingsViewModel.checkingForUpdates
|
||||||
? null
|
? null
|
||||||
: _checkForUpdates,
|
: _checkForUpdates,
|
||||||
@@ -294,7 +337,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
onTap: _onVersionTap,
|
onTap: _onVersionTap,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Version $_appVersion',
|
l10n.version(_appVersion),
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -313,6 +356,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
|
|
||||||
Future<void> _checkForUpdates() async {
|
Future<void> _checkForUpdates() async {
|
||||||
if (!Platform.isAndroid) return;
|
if (!Platform.isAndroid) return;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
final vm = context.read<SettingsViewModel>();
|
final vm = context.read<SettingsViewModel>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -321,11 +365,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (update == null) {
|
if (update == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text('You are already on the latest version.'),
|
).showSnackBar(SnackBar(content: Text(l10n.latestVersion)));
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,21 +378,23 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text('Update check failed: $e')));
|
).showSnackBar(SnackBar(content: Text(l10n.updateCheckFailed)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showUpdateDialog(UpdateInfo update) {
|
void _showUpdateDialog(UpdateInfo update) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: !update.mandatory,
|
barrierDismissible: !update.mandatory,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: const Text("Update available"),
|
title: Text(l10n.updateAvailable),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Version ${update.version} is available."),
|
Text(l10n.versionAvailable(update.version)),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(update.notes),
|
Text(update.notes),
|
||||||
],
|
],
|
||||||
@@ -359,14 +403,14 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
if (!update.mandatory)
|
if (!update.mandatory)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
child: const Text("Later"),
|
child: Text(l10n.later),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
context.push('/update-progress', extra: update);
|
context.push('/update-progress', extra: update);
|
||||||
},
|
},
|
||||||
child: const Text("Update"),
|
child: Text(l10n.update),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import '../utils/app_update_util.dart';
|
import '../utils/app_update_util.dart';
|
||||||
import '../viewmodels/update_progress_view_model.dart';
|
import '../viewmodels/update_progress_view_model.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
class UpdateProgressView extends StatefulWidget {
|
class UpdateProgressView extends StatefulWidget {
|
||||||
final UpdateInfo update;
|
final UpdateInfo update;
|
||||||
@@ -46,28 +48,27 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showRestartDialog() {
|
void _showRestartDialog() {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Update ready'),
|
title: Text(l10n.updateReady),
|
||||||
content: const Text(
|
content: Text(l10n.updateReadyDescription),
|
||||||
'The update has been downloaded and installed. '
|
|
||||||
'Restart the app now to apply the changes.',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
this.context.go('/bar');
|
this.context.go('/bar');
|
||||||
},
|
},
|
||||||
child: const Text('Later'),
|
child: Text(l10n.later),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
child: const Text('Restart app'),
|
child: Text(l10n.restartApp),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -75,12 +76,14 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showErrorSnackBar() {
|
void _showErrorSnackBar() {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(_vm.errorMessage ?? 'Update failed'),
|
content: Text(l10n.updateError(_vm.errorMessage)),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: 'Retry',
|
label: l10n.retry,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_vm.cancel();
|
_vm.cancel();
|
||||||
_vm.start();
|
_vm.start();
|
||||||
@@ -99,13 +102,15 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: () => _showCancelDialog(),
|
onPressed: () => _showCancelDialog(),
|
||||||
),
|
),
|
||||||
title: const Text('Updating'),
|
title: Text(l10n.updating),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
),
|
),
|
||||||
@@ -138,6 +143,8 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
@@ -153,13 +160,10 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text(
|
Text('KoolTab', style: Theme.of(context).textTheme.headlineMedium),
|
||||||
'KoolTab',
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Version ${widget.update.version}',
|
l10n.version(widget.update.version),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
@@ -170,6 +174,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
|
|
||||||
Widget _buildProgress(UpdateProgressViewModel vm) {
|
Widget _buildProgress(UpdateProgressViewModel vm) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -211,7 +216,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
vm.phase == OtaPhase.installing ? 'Installing' : '',
|
vm.phase == OtaPhase.installing ? l10n.installing : '',
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: scheme.onSurface.withValues(alpha: 0.6),
|
color: scheme.onSurface.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
@@ -234,6 +239,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
|
|
||||||
Widget _buildStatus(UpdateProgressViewModel vm) {
|
Widget _buildStatus(UpdateProgressViewModel vm) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
final color = switch (vm.phase) {
|
final color = switch (vm.phase) {
|
||||||
OtaPhase.error => scheme.error,
|
OtaPhase.error => scheme.error,
|
||||||
@@ -242,13 +248,15 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
vm.statusText,
|
l10n.updateStatus(vm.statusText),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: color),
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: color),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildVersionInfo(UpdateProgressViewModel vm) {
|
Widget _buildVersionInfo(UpdateProgressViewModel vm) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -259,9 +267,11 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'Do not close the app during the update',
|
l10n.doNotCloseDuringUpdate,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.4),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurface.withValues(alpha: 0.4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -269,18 +279,17 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showCancelDialog() {
|
void _showCancelDialog() {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Cancel update?'),
|
title: Text(l10n.cancelUpdate),
|
||||||
content: const Text(
|
content: Text(l10n.cancelUpdateDescription),
|
||||||
'The update is in progress. If you leave now, '
|
|
||||||
'the app may become unstable.',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Continue update'),
|
child: Text(l10n.continueUpdate),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
@@ -291,7 +300,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
|
|||||||
_vm.cancel();
|
_vm.cancel();
|
||||||
this.context.go('/bar');
|
this.context.go('/bar');
|
||||||
},
|
},
|
||||||
child: const Text('Cancel update'),
|
child: Text(l10n.cancelUpdateAction),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:kooltab2/models/closed_tab.dart';
|
|||||||
import 'package:kooltab2/models/closed_tab_item.dart';
|
import 'package:kooltab2/models/closed_tab_item.dart';
|
||||||
import 'package:kooltab2/models/payment_method.dart';
|
import 'package:kooltab2/models/payment_method.dart';
|
||||||
|
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ClosedTabCard extends StatefulWidget {
|
class ClosedTabCard extends StatefulWidget {
|
||||||
final ClosedTab closedTab;
|
final ClosedTab closedTab;
|
||||||
|
|
||||||
@@ -26,7 +28,9 @@ class _ClosedTabCardState extends State<ClosedTabCard> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final closedTab = widget.closedTab;
|
final closedTab = widget.closedTab;
|
||||||
final dateFormat = DateFormat('MMM d, y · h:mm a');
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final locale = Localizations.localeOf(context).toLanguageTag();
|
||||||
|
final dateFormat = DateFormat.yMMMd(locale).add_jm();
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@@ -91,7 +95,10 @@ class _ClosedTabCardState extends State<ClosedTabCard> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
closedTab.formattedPaymentMethod,
|
switch (closedTab.paymentMethod) {
|
||||||
|
PaymentMethod.cash => l10n.cash,
|
||||||
|
PaymentMethod.payconiq => l10n.payconiq,
|
||||||
|
},
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -105,7 +112,7 @@ class _ClosedTabCardState extends State<ClosedTabCard> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
'${dateFormat.format(closedTab.closedAt)} · ${closedTab.itemCount} items',
|
'${dateFormat.format(closedTab.closedAt)} · ${l10n.tabItemCount(closedTab.itemCount)}',
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -164,12 +171,13 @@ class _ClosedTabItemRow extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final unitPrice = NumberFormat.simpleCurrency().format(
|
final locale = Localizations.localeOf(context).toLanguageTag();
|
||||||
item.unitPriceInCents / 100,
|
final unitPrice = NumberFormat.simpleCurrency(
|
||||||
);
|
locale: locale,
|
||||||
final lineTotal = NumberFormat.simpleCurrency().format(
|
).format(item.unitPriceInCents / 100);
|
||||||
item.lineTotalInCents / 100,
|
final lineTotal = NumberFormat.simpleCurrency(
|
||||||
);
|
locale: locale,
|
||||||
|
).format(item.lineTotalInCents / 100);
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -207,4 +215,4 @@ class _ClosedTabItemRow extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kooltab2/models/product.dart';
|
import 'package:kooltab2/models/product.dart';
|
||||||
|
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ProductTile extends StatelessWidget {
|
class ProductTile extends StatelessWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
@@ -20,6 +22,7 @@ class ProductTile extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scheme = theme.colorScheme;
|
final scheme = theme.colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
final outOfStock = product.stockQuantity <= 0;
|
final outOfStock = product.stockQuantity <= 0;
|
||||||
final lowStock =
|
final lowStock =
|
||||||
@@ -101,7 +104,7 @@ class ProductTile extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'OUT OF STOCK',
|
l10n.outOfStock,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: scheme.onError,
|
color: scheme.onError,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
class SlideConfirm extends StatefulWidget {
|
class SlideConfirm extends StatefulWidget {
|
||||||
final VoidCallback onConfirmed;
|
final VoidCallback onConfirmed;
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ class _SlideConfirmState extends State<SlideConfirm> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@@ -37,7 +40,7 @@ class _SlideConfirmState extends State<SlideConfirm> {
|
|||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
_confirmed ? 'Closing tab...' : 'Slide to confirm closing',
|
_confirmed ? l10n.closingTab : l10n.slideToConfirmClosing,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: scheme.error,
|
color: scheme.error,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
|
|||||||
+7
-2
@@ -318,6 +318,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -532,10 +537,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.3"
|
version: "0.20.2"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+4
-1
@@ -30,6 +30,8 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
@@ -43,7 +45,7 @@ dependencies:
|
|||||||
image_picker: ^1.2.3
|
image_picker: ^1.2.3
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
flutter_slidable: ^4.0.3
|
flutter_slidable: ^4.0.3
|
||||||
intl: ^0.20.3
|
intl: ^0.20.2
|
||||||
flutter_secure_storage: 10.3.1
|
flutter_secure_storage: 10.3.1
|
||||||
crypto: ^3.0.0
|
crypto: ^3.0.0
|
||||||
http: ^1.6.0
|
http: ^1.6.0
|
||||||
@@ -75,6 +77,7 @@ dev_dependencies:
|
|||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
|
generate: true
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
# The following line ensures that the Material Icons font is
|
||||||
# included with your application, so that you can use the icons in
|
# included with your application, so that you can use the icons in
|
||||||
|
|||||||
Reference in New Issue
Block a user