feat: add rows count (broken), fix: bug with low stock items
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../database/app_database.dart';
|
import '../database/app_database.dart';
|
||||||
import '../services/default_product_seeder.dart';
|
import '../services/default_product_seeder.dart';
|
||||||
@@ -31,6 +32,9 @@ class _AppBootstrapState extends State<AppBootstrap> {
|
|||||||
context.read<BarScreenViewModel>().ensureLoaded();
|
context.read<BarScreenViewModel>().ensureLoaded();
|
||||||
|
|
||||||
DefaultExportService(database: database).uploadToServer();
|
DefaultExportService(database: database).uploadToServer();
|
||||||
|
|
||||||
|
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
|
||||||
part 'app_database.g.dart';
|
part 'app_database.g.dart';
|
||||||
|
|
||||||
@DataClassName('ProductRow')
|
@DataClassName('ProductRow')
|
||||||
@@ -111,6 +110,8 @@ class AppSettingsTable extends Table {
|
|||||||
|
|
||||||
TextColumn get language => text().withDefault(const Constant('system'))();
|
TextColumn get language => text().withDefault(const Constant('system'))();
|
||||||
|
|
||||||
|
IntColumn get barGridRows => integer().withDefault(const Constant(3))();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
@@ -131,7 +132,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 7;
|
int get schemaVersion => 8;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration {
|
MigrationStrategy get migration {
|
||||||
@@ -140,6 +141,14 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
await migrator.createAll();
|
await migrator.createAll();
|
||||||
},
|
},
|
||||||
onUpgrade: (migrator, from, to) async {
|
onUpgrade: (migrator, from, to) async {
|
||||||
|
Future<bool> hasSettingsColumn(String columnName) async {
|
||||||
|
final columns = await migrator.database
|
||||||
|
.customSelect('PRAGMA table_info(app_settings)')
|
||||||
|
.get();
|
||||||
|
|
||||||
|
return columns.any((column) => column.data['name'] == columnName);
|
||||||
|
}
|
||||||
|
|
||||||
if (from < 2) {
|
if (from < 2) {
|
||||||
await migrator.createTable(barTabs);
|
await migrator.createTable(barTabs);
|
||||||
await migrator.createTable(tabItems);
|
await migrator.createTable(tabItems);
|
||||||
@@ -162,9 +171,16 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
await migrator.addColumn(closedTabs, closedTabs.paymentMethod);
|
await migrator.addColumn(closedTabs, closedTabs.paymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from < 7) {
|
if (from < 7 && !await hasSettingsColumn('language')) {
|
||||||
await migrator.addColumn(appSettingsTable, appSettingsTable.language);
|
await migrator.addColumn(appSettingsTable, appSettingsTable.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (from < 8 && !await hasSettingsColumn('bar_grid_rows')) {
|
||||||
|
await migrator.addColumn(
|
||||||
|
appSettingsTable,
|
||||||
|
appSettingsTable.barGridRows,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2231,8 +2231,26 @@ class $AppSettingsTableTable extends AppSettingsTable
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: const Constant('system'),
|
defaultValue: const Constant('system'),
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _barGridRowsMeta = const VerificationMeta(
|
||||||
|
'barGridRows',
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [id, pinRequired, themeMode, language];
|
late final GeneratedColumn<int> barGridRows = GeneratedColumn<int>(
|
||||||
|
'bar_grid_rows',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const Constant(3),
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [
|
||||||
|
id,
|
||||||
|
pinRequired,
|
||||||
|
themeMode,
|
||||||
|
language,
|
||||||
|
barGridRows,
|
||||||
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
@@ -2271,6 +2289,15 @@ class $AppSettingsTableTable extends AppSettingsTable
|
|||||||
language.isAcceptableOrUnknown(data['language']!, _languageMeta),
|
language.isAcceptableOrUnknown(data['language']!, _languageMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('bar_grid_rows')) {
|
||||||
|
context.handle(
|
||||||
|
_barGridRowsMeta,
|
||||||
|
barGridRows.isAcceptableOrUnknown(
|
||||||
|
data['bar_grid_rows']!,
|
||||||
|
_barGridRowsMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2296,6 +2323,10 @@ class $AppSettingsTableTable extends AppSettingsTable
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}language'],
|
data['${effectivePrefix}language'],
|
||||||
)!,
|
)!,
|
||||||
|
barGridRows: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}bar_grid_rows'],
|
||||||
|
)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2310,11 +2341,13 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
final bool pinRequired;
|
final bool pinRequired;
|
||||||
final String themeMode;
|
final String themeMode;
|
||||||
final String language;
|
final String language;
|
||||||
|
final int barGridRows;
|
||||||
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,
|
required this.language,
|
||||||
|
required this.barGridRows,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -2323,6 +2356,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
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);
|
map['language'] = Variable<String>(language);
|
||||||
|
map['bar_grid_rows'] = Variable<int>(barGridRows);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2332,6 +2366,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
pinRequired: Value(pinRequired),
|
pinRequired: Value(pinRequired),
|
||||||
themeMode: Value(themeMode),
|
themeMode: Value(themeMode),
|
||||||
language: Value(language),
|
language: Value(language),
|
||||||
|
barGridRows: Value(barGridRows),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2345,6 +2380,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
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']),
|
language: serializer.fromJson<String>(json['language']),
|
||||||
|
barGridRows: serializer.fromJson<int>(json['barGridRows']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -2355,6 +2391,7 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
'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),
|
'language': serializer.toJson<String>(language),
|
||||||
|
'barGridRows': serializer.toJson<int>(barGridRows),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2363,11 +2400,13 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
bool? pinRequired,
|
bool? pinRequired,
|
||||||
String? themeMode,
|
String? themeMode,
|
||||||
String? language,
|
String? language,
|
||||||
|
int? barGridRows,
|
||||||
}) => AppSettingsRow(
|
}) => AppSettingsRow(
|
||||||
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,
|
language: language ?? this.language,
|
||||||
|
barGridRows: barGridRows ?? this.barGridRows,
|
||||||
);
|
);
|
||||||
AppSettingsRow copyWithCompanion(AppSettingsTableCompanion data) {
|
AppSettingsRow copyWithCompanion(AppSettingsTableCompanion data) {
|
||||||
return AppSettingsRow(
|
return AppSettingsRow(
|
||||||
@@ -2377,6 +2416,9 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
: 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,
|
language: data.language.present ? data.language.value : this.language,
|
||||||
|
barGridRows: data.barGridRows.present
|
||||||
|
? data.barGridRows.value
|
||||||
|
: this.barGridRows,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2386,13 +2428,15 @@ class AppSettingsRow extends DataClass implements Insertable<AppSettingsRow> {
|
|||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('pinRequired: $pinRequired, ')
|
..write('pinRequired: $pinRequired, ')
|
||||||
..write('themeMode: $themeMode, ')
|
..write('themeMode: $themeMode, ')
|
||||||
..write('language: $language')
|
..write('language: $language, ')
|
||||||
|
..write('barGridRows: $barGridRows')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(id, pinRequired, themeMode, language);
|
int get hashCode =>
|
||||||
|
Object.hash(id, pinRequired, themeMode, language, barGridRows);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@@ -2400,7 +2444,8 @@ class AppSettingsRow extends DataClass implements Insertable<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);
|
other.language == this.language &&
|
||||||
|
other.barGridRows == this.barGridRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
||||||
@@ -2408,12 +2453,14 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
final Value<bool> pinRequired;
|
final Value<bool> pinRequired;
|
||||||
final Value<String> themeMode;
|
final Value<String> themeMode;
|
||||||
final Value<String> language;
|
final Value<String> language;
|
||||||
|
final Value<int> barGridRows;
|
||||||
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.language = const Value.absent(),
|
||||||
|
this.barGridRows = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
AppSettingsTableCompanion.insert({
|
AppSettingsTableCompanion.insert({
|
||||||
@@ -2421,6 +2468,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
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.language = const Value.absent(),
|
||||||
|
this.barGridRows = 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({
|
||||||
@@ -2428,6 +2476,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
Expression<bool>? pinRequired,
|
Expression<bool>? pinRequired,
|
||||||
Expression<String>? themeMode,
|
Expression<String>? themeMode,
|
||||||
Expression<String>? language,
|
Expression<String>? language,
|
||||||
|
Expression<int>? barGridRows,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
@@ -2435,6 +2484,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
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 (language != null) 'language': language,
|
||||||
|
if (barGridRows != null) 'bar_grid_rows': barGridRows,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2444,6 +2494,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
Value<bool>? pinRequired,
|
Value<bool>? pinRequired,
|
||||||
Value<String>? themeMode,
|
Value<String>? themeMode,
|
||||||
Value<String>? language,
|
Value<String>? language,
|
||||||
|
Value<int>? barGridRows,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return AppSettingsTableCompanion(
|
return AppSettingsTableCompanion(
|
||||||
@@ -2451,6 +2502,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
pinRequired: pinRequired ?? this.pinRequired,
|
pinRequired: pinRequired ?? this.pinRequired,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
language: language ?? this.language,
|
language: language ?? this.language,
|
||||||
|
barGridRows: barGridRows ?? this.barGridRows,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2470,6 +2522,9 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
if (language.present) {
|
if (language.present) {
|
||||||
map['language'] = Variable<String>(language.value);
|
map['language'] = Variable<String>(language.value);
|
||||||
}
|
}
|
||||||
|
if (barGridRows.present) {
|
||||||
|
map['bar_grid_rows'] = Variable<int>(barGridRows.value);
|
||||||
|
}
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
@@ -2483,6 +2538,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsRow> {
|
|||||||
..write('pinRequired: $pinRequired, ')
|
..write('pinRequired: $pinRequired, ')
|
||||||
..write('themeMode: $themeMode, ')
|
..write('themeMode: $themeMode, ')
|
||||||
..write('language: $language, ')
|
..write('language: $language, ')
|
||||||
|
..write('barGridRows: $barGridRows, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
@@ -4071,6 +4127,7 @@ typedef $$AppSettingsTableTableCreateCompanionBuilder =
|
|||||||
Value<bool> pinRequired,
|
Value<bool> pinRequired,
|
||||||
Value<String> themeMode,
|
Value<String> themeMode,
|
||||||
Value<String> language,
|
Value<String> language,
|
||||||
|
Value<int> barGridRows,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $$AppSettingsTableTableUpdateCompanionBuilder =
|
typedef $$AppSettingsTableTableUpdateCompanionBuilder =
|
||||||
@@ -4079,6 +4136,7 @@ typedef $$AppSettingsTableTableUpdateCompanionBuilder =
|
|||||||
Value<bool> pinRequired,
|
Value<bool> pinRequired,
|
||||||
Value<String> themeMode,
|
Value<String> themeMode,
|
||||||
Value<String> language,
|
Value<String> language,
|
||||||
|
Value<int> barGridRows,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4110,6 +4168,11 @@ class $$AppSettingsTableTableFilterComposer
|
|||||||
column: $table.language,
|
column: $table.language,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<int> get barGridRows => $composableBuilder(
|
||||||
|
column: $table.barGridRows,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$AppSettingsTableTableOrderingComposer
|
class $$AppSettingsTableTableOrderingComposer
|
||||||
@@ -4140,6 +4203,11 @@ class $$AppSettingsTableTableOrderingComposer
|
|||||||
column: $table.language,
|
column: $table.language,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<int> get barGridRows => $composableBuilder(
|
||||||
|
column: $table.barGridRows,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$AppSettingsTableTableAnnotationComposer
|
class $$AppSettingsTableTableAnnotationComposer
|
||||||
@@ -4164,6 +4232,11 @@ class $$AppSettingsTableTableAnnotationComposer
|
|||||||
|
|
||||||
GeneratedColumn<String> get language =>
|
GeneratedColumn<String> get language =>
|
||||||
$composableBuilder(column: $table.language, builder: (column) => column);
|
$composableBuilder(column: $table.language, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get barGridRows => $composableBuilder(
|
||||||
|
column: $table.barGridRows,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$AppSettingsTableTableTableManager
|
class $$AppSettingsTableTableTableManager
|
||||||
@@ -4207,12 +4280,14 @@ class $$AppSettingsTableTableTableManager
|
|||||||
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<String> language = const Value.absent(),
|
||||||
|
Value<int> barGridRows = 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,
|
language: language,
|
||||||
|
barGridRows: barGridRows,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
@@ -4221,12 +4296,14 @@ class $$AppSettingsTableTableTableManager
|
|||||||
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<String> language = const Value.absent(),
|
||||||
|
Value<int> barGridRows = 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,
|
language: language,
|
||||||
|
barGridRows: barGridRows,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
"languageSystem": "System default",
|
"languageSystem": "System default",
|
||||||
"languageEnglish": "English",
|
"languageEnglish": "English",
|
||||||
"languageDutch": "Dutch",
|
"languageDutch": "Dutch",
|
||||||
|
"productGridRows": "Product grid rows",
|
||||||
|
"rowsCount": "{count} rows",
|
||||||
|
"@rowsCount": {"placeholders": {"count": {"type": "int"}}},
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
|
|||||||
@@ -278,6 +278,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Dutch'**
|
/// **'Dutch'**
|
||||||
String get languageDutch;
|
String get languageDutch;
|
||||||
|
|
||||||
|
/// No description provided for @productGridRows.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Product grid rows'**
|
||||||
|
String get productGridRows;
|
||||||
|
|
||||||
|
/// No description provided for @rowsCount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} rows'**
|
||||||
|
String rowsCount(int count);
|
||||||
|
|
||||||
/// No description provided for @theme.
|
/// No description provided for @theme.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get languageDutch => 'Dutch';
|
String get languageDutch => 'Dutch';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productGridRows => 'Product grid rows';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String rowsCount(int count) {
|
||||||
|
return '$count rows';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get theme => 'Theme';
|
String get theme => 'Theme';
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get languageDutch => 'Nederlands';
|
String get languageDutch => 'Nederlands';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get productGridRows => 'Rijen in productraster';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String rowsCount(int count) {
|
||||||
|
return '$count rijen';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get theme => 'Thema';
|
String get theme => 'Thema';
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
"languageSystem": "Systeemstandaard",
|
"languageSystem": "Systeemstandaard",
|
||||||
"languageEnglish": "Engels",
|
"languageEnglish": "Engels",
|
||||||
"languageDutch": "Nederlands",
|
"languageDutch": "Nederlands",
|
||||||
|
"productGridRows": "Rijen in productraster",
|
||||||
|
"rowsCount": "{count} rijen",
|
||||||
|
"@rowsCount": {"placeholders": {"count": {"type": "int"}}},
|
||||||
"theme": "Thema",
|
"theme": "Thema",
|
||||||
"system": "Systeem",
|
"system": "Systeem",
|
||||||
"light": "Licht",
|
"light": "Licht",
|
||||||
|
|||||||
@@ -1,26 +1,31 @@
|
|||||||
enum AppThemeMode { system, light, dark, ugly }
|
enum AppThemeMode { system, light, dark, ugly }
|
||||||
|
|
||||||
enum AppLanguage { system, english, dutch }
|
enum AppLanguage { system, english, dutch }
|
||||||
|
|
||||||
class AppSettings {
|
class AppSettings {
|
||||||
final bool pinRequired;
|
final bool pinRequired;
|
||||||
final AppThemeMode themeMode;
|
final AppThemeMode themeMode;
|
||||||
final AppLanguage language;
|
final AppLanguage language;
|
||||||
|
final int barGridRows;
|
||||||
|
|
||||||
const AppSettings({
|
const AppSettings({
|
||||||
required this.pinRequired,
|
required this.pinRequired,
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
this.language = AppLanguage.system,
|
this.language = AppLanguage.system,
|
||||||
|
this.barGridRows = 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
AppSettings copyWith({
|
AppSettings copyWith({
|
||||||
bool? pinRequired,
|
bool? pinRequired,
|
||||||
AppThemeMode? themeMode,
|
AppThemeMode? themeMode,
|
||||||
AppLanguage? language,
|
AppLanguage? language,
|
||||||
|
int? barGridRows,
|
||||||
}) {
|
}) {
|
||||||
return AppSettings(
|
return AppSettings(
|
||||||
pinRequired: pinRequired ?? this.pinRequired,
|
pinRequired: pinRequired ?? this.pinRequired,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
language: language ?? this.language,
|
language: language ?? this.language,
|
||||||
|
barGridRows: barGridRows ?? this.barGridRows,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,8 +40,10 @@ class AppSettings {
|
|||||||
other is AppSettings &&
|
other is AppSettings &&
|
||||||
pinRequired == other.pinRequired &&
|
pinRequired == other.pinRequired &&
|
||||||
themeMode == other.themeMode &&
|
themeMode == other.themeMode &&
|
||||||
language == other.language;
|
language == other.language &&
|
||||||
|
barGridRows == other.barGridRows;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(pinRequired, themeMode, language);
|
int get hashCode =>
|
||||||
|
Object.hash(pinRequired, themeMode, language, barGridRows);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ import 'package:uuid/uuid.dart';
|
|||||||
import '../database/app_database.dart';
|
import '../database/app_database.dart';
|
||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
|
|
||||||
|
class InsufficientStockException implements Exception {
|
||||||
|
const InsufficientStockException();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Not enough stock';
|
||||||
|
}
|
||||||
|
|
||||||
abstract class ProductService {
|
abstract class ProductService {
|
||||||
Future<List<Product>> getProducts();
|
Future<List<Product>> getProducts();
|
||||||
|
|
||||||
@@ -130,7 +137,7 @@ class DriftProductService implements ProductService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (product.stockQuantity < amount) {
|
if (product.stockQuantity < amount) {
|
||||||
throw Exception('Not enough stock');
|
throw const InsufficientStockException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateProduct(
|
await updateProduct(
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class DriftSettingsService implements SettingsService {
|
|||||||
(language) => language.name == row.language,
|
(language) => language.name == row.language,
|
||||||
orElse: () => AppLanguage.system,
|
orElse: () => AppLanguage.system,
|
||||||
),
|
),
|
||||||
|
barGridRows: row.barGridRows.clamp(2, 6).toInt(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ class DriftSettingsService implements SettingsService {
|
|||||||
pinRequired: Value(settings.pinRequired),
|
pinRequired: Value(settings.pinRequired),
|
||||||
themeMode: Value(settings.themeMode.name),
|
themeMode: Value(settings.themeMode.name),
|
||||||
language: Value(settings.language.name),
|
language: Value(settings.language.name),
|
||||||
|
barGridRows: Value(settings.barGridRows),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../models/bar_tab.dart';
|
|||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
import '../models/tab_item.dart';
|
import '../models/tab_item.dart';
|
||||||
import '../services/bar_tab_service.dart';
|
import '../services/bar_tab_service.dart';
|
||||||
|
import '../services/product_service.dart';
|
||||||
import 'inventory_view_model.dart';
|
import 'inventory_view_model.dart';
|
||||||
|
|
||||||
class BarScreenViewModel extends ChangeNotifier {
|
class BarScreenViewModel extends ChangeNotifier {
|
||||||
@@ -95,9 +96,7 @@ class BarScreenViewModel extends ChangeNotifier {
|
|||||||
if (tab == null) return;
|
if (tab == null) return;
|
||||||
|
|
||||||
if (product.stockQuantity <= 0) {
|
if (product.stockQuantity <= 0) {
|
||||||
_errorMessage = 'Product is out of stock.';
|
throw const InsufficientStockException();
|
||||||
notifyListeners();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -109,6 +108,8 @@ class BarScreenViewModel extends ChangeNotifier {
|
|||||||
|
|
||||||
await _reloadTabs();
|
await _reloadTabs();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
|
if (e is InsufficientStockException) rethrow;
|
||||||
|
|
||||||
debugPrint('BarScreenViewModel: addProductToSelectedTab error: $e');
|
debugPrint('BarScreenViewModel: addProductToSelectedTab error: $e');
|
||||||
_errorMessage = 'Could not add product to tab.';
|
_errorMessage = 'Could not add product to tab.';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -120,6 +121,20 @@ class BarScreenViewModel extends ChangeNotifier {
|
|||||||
Future<void> changeItemQuantity(TabItem item, int quantity) async {
|
Future<void> changeItemQuantity(TabItem item, int quantity) async {
|
||||||
final difference = quantity - item.quantity;
|
final difference = quantity - item.quantity;
|
||||||
|
|
||||||
|
if (difference > 0) {
|
||||||
|
Product? product;
|
||||||
|
for (final candidate in inventory.products) {
|
||||||
|
if (candidate.id == item.productId) {
|
||||||
|
product = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product != null && product.stockQuantity < difference) {
|
||||||
|
throw const InsufficientStockException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await barTabService.updateTabItemQuantity(
|
await barTabService.updateTabItemQuantity(
|
||||||
tabItemId: item.id,
|
tabItemId: item.id,
|
||||||
@@ -135,6 +150,8 @@ class BarScreenViewModel extends ChangeNotifier {
|
|||||||
|
|
||||||
await _reloadTabs();
|
await _reloadTabs();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
|
if (e is InsufficientStockException) rethrow;
|
||||||
|
|
||||||
debugPrint('BarScreenViewModel: changeItemQuantity error: $e');
|
debugPrint('BarScreenViewModel: changeItemQuantity error: $e');
|
||||||
_errorMessage = 'Could not update item quantity.';
|
_errorMessage = 'Could not update item quantity.';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class InventoryViewModel extends ChangeNotifier {
|
|||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
|
if (e is InsufficientStockException) rethrow;
|
||||||
|
|
||||||
debugPrint('InventoryViewModel: decreaseStock error: $e');
|
debugPrint('InventoryViewModel: decreaseStock error: $e');
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ class SettingsViewModel extends ChangeNotifier {
|
|||||||
Future<void> updateLanguage(AppLanguage language) =>
|
Future<void> updateLanguage(AppLanguage language) =>
|
||||||
_save(_settings.copyWith(language: language));
|
_save(_settings.copyWith(language: language));
|
||||||
|
|
||||||
|
Future<void> updateBarGridRows(int rows) =>
|
||||||
|
_save(_settings.copyWith(barGridRows: rows.clamp(2, 6).toInt()));
|
||||||
|
|
||||||
Future<void> _save(AppSettings updated) async {
|
Future<void> _save(AppSettings updated) async {
|
||||||
final previous = _settings;
|
final previous = _settings;
|
||||||
_settings = updated;
|
_settings = updated;
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import 'package:flutter_slidable/flutter_slidable.dart';
|
|||||||
import '../models/bar_tab.dart';
|
import '../models/bar_tab.dart';
|
||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
import '../models/tab_item.dart';
|
import '../models/tab_item.dart';
|
||||||
|
import '../services/product_service.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 '../viewmodels/settings_view_model.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
import '../l10n/app_localizations_helpers.dart';
|
import '../l10n/app_localizations_helpers.dart';
|
||||||
|
|
||||||
@@ -38,6 +40,11 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
final l10n = AppLocalizations.of(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>();
|
||||||
|
final settings = context.watch<SettingsViewModel>().settings;
|
||||||
|
final stockByProductId = {
|
||||||
|
for (final product in productsViewModel.products)
|
||||||
|
product.id: product.stockQuantity,
|
||||||
|
};
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -118,6 +125,7 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: _ProductGrid(
|
child: _ProductGrid(
|
||||||
products: productsViewModel.products,
|
products: productsViewModel.products,
|
||||||
|
rows: settings.barGridRows,
|
||||||
hasSelectedTab: viewModel.selectedTab != null,
|
hasSelectedTab: viewModel.selectedTab != null,
|
||||||
onProductTap: (product) async {
|
onProductTap: (product) async {
|
||||||
if (viewModel.selectedTab == null) {
|
if (viewModel.selectedTab == null) {
|
||||||
@@ -130,10 +138,18 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
try {
|
try {
|
||||||
await viewModel.addProductToSelectedTab(product);
|
await viewModel.addProductToSelectedTab(product);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
if (e is! InsufficientStockException) {
|
||||||
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(l10n.couldNotAddProductToTab)),
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
e is InsufficientStockException
|
||||||
|
? l10n.productOutOfStock
|
||||||
|
: l10n.couldNotAddProductToTab,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -146,6 +162,7 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
tabs: viewModel.tabs,
|
tabs: viewModel.tabs,
|
||||||
selectedTab: viewModel.selectedTab,
|
selectedTab: viewModel.selectedTab,
|
||||||
selectedTabId: viewModel.selectedTabId,
|
selectedTabId: viewModel.selectedTabId,
|
||||||
|
stockByProductId: stockByProductId,
|
||||||
onNewTabPressed: () => showNewTabDialog(context),
|
onNewTabPressed: () => showNewTabDialog(context),
|
||||||
onTabSelected: viewModel.selectTab,
|
onTabSelected: viewModel.selectTab,
|
||||||
onItemQuantityChanged: viewModel.changeItemQuantity,
|
onItemQuantityChanged: viewModel.changeItemQuantity,
|
||||||
@@ -166,11 +183,13 @@ class _BarScreenViewState extends State<BarScreenView> {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
class _ProductGrid extends StatelessWidget {
|
class _ProductGrid extends StatelessWidget {
|
||||||
final List<Product> products;
|
final List<Product> products;
|
||||||
|
final int rows;
|
||||||
final bool hasSelectedTab;
|
final bool hasSelectedTab;
|
||||||
final ValueChanged<Product> onProductTap;
|
final ValueChanged<Product> onProductTap;
|
||||||
|
|
||||||
const _ProductGrid({
|
const _ProductGrid({
|
||||||
required this.products,
|
required this.products,
|
||||||
|
required this.rows,
|
||||||
required this.hasSelectedTab,
|
required this.hasSelectedTab,
|
||||||
required this.onProductTap,
|
required this.onProductTap,
|
||||||
});
|
});
|
||||||
@@ -267,11 +286,26 @@ class _ProductGrid extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final columns = ((constraints.maxWidth / 170).floor())
|
const horizontalPadding = 32.0;
|
||||||
.clamp(3, 5)
|
const verticalPadding = 28.0;
|
||||||
.toInt();
|
const spacing = 12.0;
|
||||||
|
final rowCount = rows.clamp(2, 6).toInt();
|
||||||
|
final rowHeight =
|
||||||
|
((constraints.maxHeight -
|
||||||
|
verticalPadding -
|
||||||
|
(rowCount - 1) * spacing) /
|
||||||
|
rowCount)
|
||||||
|
.clamp(96.0, 320.0)
|
||||||
|
.toDouble();
|
||||||
|
final columns =
|
||||||
|
(((constraints.maxWidth - horizontalPadding + spacing) /
|
||||||
|
(rowHeight + spacing))
|
||||||
|
.ceil())
|
||||||
|
.clamp(2, 8)
|
||||||
|
.toInt();
|
||||||
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
|
key: ValueKey('product-grid-$rows'),
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
|
||||||
itemCount: products.length,
|
itemCount: products.length,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
@@ -306,6 +340,7 @@ class _TabPanel extends StatefulWidget {
|
|||||||
final List<BarTab> tabs;
|
final List<BarTab> tabs;
|
||||||
final BarTab? selectedTab;
|
final BarTab? selectedTab;
|
||||||
final String? selectedTabId;
|
final String? selectedTabId;
|
||||||
|
final Map<String, int> stockByProductId;
|
||||||
final VoidCallback onNewTabPressed;
|
final VoidCallback onNewTabPressed;
|
||||||
final ValueChanged<String> onTabSelected;
|
final ValueChanged<String> onTabSelected;
|
||||||
final Future<void> Function(TabItem item, int quantity) onItemQuantityChanged;
|
final Future<void> Function(TabItem item, int quantity) onItemQuantityChanged;
|
||||||
@@ -316,6 +351,7 @@ class _TabPanel extends StatefulWidget {
|
|||||||
required this.tabs,
|
required this.tabs,
|
||||||
required this.selectedTab,
|
required this.selectedTab,
|
||||||
required this.selectedTabId,
|
required this.selectedTabId,
|
||||||
|
required this.stockByProductId,
|
||||||
required this.onNewTabPressed,
|
required this.onNewTabPressed,
|
||||||
required this.onTabSelected,
|
required this.onTabSelected,
|
||||||
required this.onItemQuantityChanged,
|
required this.onItemQuantityChanged,
|
||||||
@@ -452,6 +488,7 @@ class _TabPanelState extends State<_TabPanel> {
|
|||||||
)
|
)
|
||||||
: _SelectedTabDetails(
|
: _SelectedTabDetails(
|
||||||
tab: widget.selectedTab!,
|
tab: widget.selectedTab!,
|
||||||
|
stockByProductId: widget.stockByProductId,
|
||||||
onItemQuantityChanged: widget.onItemQuantityChanged,
|
onItemQuantityChanged: widget.onItemQuantityChanged,
|
||||||
onCloseTabPressed: widget.onCloseTabPressed,
|
onCloseTabPressed: widget.onCloseTabPressed,
|
||||||
),
|
),
|
||||||
@@ -596,11 +633,13 @@ class _OpenTabsList extends StatelessWidget {
|
|||||||
|
|
||||||
class _SelectedTabDetails extends StatelessWidget {
|
class _SelectedTabDetails extends StatelessWidget {
|
||||||
final BarTab tab;
|
final BarTab tab;
|
||||||
|
final Map<String, int> stockByProductId;
|
||||||
final Future<void> Function(TabItem item, int quantity) onItemQuantityChanged;
|
final Future<void> Function(TabItem item, int quantity) onItemQuantityChanged;
|
||||||
final VoidCallback onCloseTabPressed;
|
final VoidCallback onCloseTabPressed;
|
||||||
|
|
||||||
const _SelectedTabDetails({
|
const _SelectedTabDetails({
|
||||||
required this.tab,
|
required this.tab,
|
||||||
|
required this.stockByProductId,
|
||||||
required this.onItemQuantityChanged,
|
required this.onItemQuantityChanged,
|
||||||
required this.onCloseTabPressed,
|
required this.onCloseTabPressed,
|
||||||
});
|
});
|
||||||
@@ -654,6 +693,7 @@ class _SelectedTabDetails extends StatelessWidget {
|
|||||||
|
|
||||||
return _TabItemRow(
|
return _TabItemRow(
|
||||||
item: item,
|
item: item,
|
||||||
|
stockByProductId: stockByProductId,
|
||||||
onQuantityChanged: onItemQuantityChanged,
|
onQuantityChanged: onItemQuantityChanged,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -706,14 +746,21 @@ class _SelectedTabDetails extends StatelessWidget {
|
|||||||
|
|
||||||
class _TabItemRow extends StatelessWidget {
|
class _TabItemRow extends StatelessWidget {
|
||||||
final TabItem item;
|
final TabItem item;
|
||||||
|
final Map<String, int> stockByProductId;
|
||||||
final Future<void> Function(TabItem item, int quantity) onQuantityChanged;
|
final Future<void> Function(TabItem item, int quantity) onQuantityChanged;
|
||||||
|
|
||||||
const _TabItemRow({required this.item, required this.onQuantityChanged});
|
const _TabItemRow({
|
||||||
|
required this.item,
|
||||||
|
required this.stockByProductId,
|
||||||
|
required this.onQuantityChanged,
|
||||||
|
});
|
||||||
|
|
||||||
@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 l10n = AppLocalizations.of(context);
|
||||||
|
final availableStock = stockByProductId[item.productId];
|
||||||
|
final canIncrease = availableStock == null || availableStock > 0;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
@@ -771,18 +818,28 @@ class _TabItemRow extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
onPressed: () async {
|
onPressed: canIncrease
|
||||||
try {
|
? () async {
|
||||||
await onQuantityChanged(item, item.quantity + 1);
|
try {
|
||||||
} catch (e, stack) {
|
await onQuantityChanged(item, item.quantity + 1);
|
||||||
Sentry.captureException(e, stackTrace: stack);
|
} catch (e, stack) {
|
||||||
if (context.mounted) {
|
if (e is! InsufficientStockException) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
Sentry.captureException(e, stackTrace: stack);
|
||||||
SnackBar(content: Text(l10n.couldNotUpdateQuantity)),
|
}
|
||||||
);
|
if (context.mounted) {
|
||||||
}
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
}
|
SnackBar(
|
||||||
},
|
content: Text(
|
||||||
|
e is InsufficientStockException
|
||||||
|
? l10n.productOutOfStock
|
||||||
|
: l10n.couldNotUpdateQuantity,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
icon: const Icon(Icons.add_rounded, size: 18),
|
icon: const Icon(Icons.add_rounded, size: 18),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -22,7 +23,7 @@ class SettingsScreenView extends StatefulWidget {
|
|||||||
|
|
||||||
class _SettingsScreenViewState extends State<SettingsScreenView> {
|
class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||||
String _appVersion = '';
|
String _appVersion = '';
|
||||||
int _versionTaps = 0;
|
Timer? _versionHoldTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -41,13 +42,23 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
return info.version;
|
return info.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onVersionTap() {
|
void _startVersionHold() {
|
||||||
_versionTaps++;
|
_versionHoldTimer?.cancel();
|
||||||
|
_versionHoldTimer = Timer(const Duration(seconds: 3), () {
|
||||||
|
_versionHoldTimer = null;
|
||||||
|
if (mounted) context.push('/dev');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (_versionTaps >= 7) {
|
void _cancelVersionHold() {
|
||||||
_versionTaps = 0;
|
_versionHoldTimer?.cancel();
|
||||||
context.push('/dev');
|
_versionHoldTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_cancelVersionHold();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadVersion() async {
|
Future<void> _loadVersion() async {
|
||||||
@@ -277,6 +288,35 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
_SettingsTile(
|
||||||
|
icon: Icons.grid_view_rounded,
|
||||||
|
title: l10n.productGridRows,
|
||||||
|
subtitle: l10n.rowsCount(settings.barGridRows),
|
||||||
|
onTap: () async {
|
||||||
|
final selected = await showModalBottomSheet<int>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
for (final rows in [2, 3, 4, 5, 6])
|
||||||
|
RadioListTile<int>(
|
||||||
|
value: rows,
|
||||||
|
groupValue: settings.barGridRows,
|
||||||
|
title: Text(l10n.rowsCount(rows)),
|
||||||
|
onChanged: (value) =>
|
||||||
|
Navigator.pop(context, value),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
await settingsViewModel.updateBarGridRows(selected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
_SettingsTile(
|
_SettingsTile(
|
||||||
icon: Icons.brightness_6_rounded,
|
icon: Icons.brightness_6_rounded,
|
||||||
title: l10n.theme,
|
title: l10n.theme,
|
||||||
@@ -334,7 +374,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: _onVersionTap,
|
onTapDown: (_) => _startVersionHold(),
|
||||||
|
onTapUp: (_) => _cancelVersionHold(),
|
||||||
|
onTapCancel: _cancelVersionHold,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.version(_appVersion),
|
l10n.version(_appVersion),
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.7+1
|
version: 1.0.9+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.12.2
|
sdk: ^3.12.2
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:kooltab2/models/product.dart';
|
||||||
|
import 'package:kooltab2/models/tab_item.dart';
|
||||||
|
import 'package:kooltab2/services/bar_tab_service.dart';
|
||||||
|
import 'package:kooltab2/services/product_service.dart';
|
||||||
|
import 'package:kooltab2/viewmodels/bar_screen_view_model.dart';
|
||||||
|
import 'package:kooltab2/viewmodels/inventory_view_model.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockBarTabService extends Mock implements BarTabService {}
|
||||||
|
|
||||||
|
class MockProductService extends Mock implements ProductService {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MockBarTabService barTabService;
|
||||||
|
late MockProductService productService;
|
||||||
|
late InventoryViewModel inventory;
|
||||||
|
late BarScreenViewModel viewModel;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
barTabService = MockBarTabService();
|
||||||
|
productService = MockProductService();
|
||||||
|
inventory = InventoryViewModel(productService: productService);
|
||||||
|
viewModel = BarScreenViewModel(
|
||||||
|
barTabService: barTabService,
|
||||||
|
inventory: inventory,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'does not increase a tab item when its product is out of stock',
|
||||||
|
() async {
|
||||||
|
const product = Product(
|
||||||
|
id: 'prod-1',
|
||||||
|
name: 'Chips',
|
||||||
|
category: 'Snacks',
|
||||||
|
stockQuantity: 0,
|
||||||
|
lowStockThreshold: 5,
|
||||||
|
priceInCents: 150,
|
||||||
|
);
|
||||||
|
const item = TabItem(
|
||||||
|
id: 'item-1',
|
||||||
|
tabId: 'tab-1',
|
||||||
|
productId: 'prod-1',
|
||||||
|
productName: 'Chips',
|
||||||
|
quantity: 20,
|
||||||
|
unitPriceInCents: 150,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(
|
||||||
|
() => productService.getProducts(),
|
||||||
|
).thenAnswer((_) async => [product]);
|
||||||
|
await inventory.load();
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
viewModel.changeItemQuantity(item, 21),
|
||||||
|
throwsA(isA<InsufficientStockException>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(viewModel.errorMessage, isNull);
|
||||||
|
verifyNoMoreInteractions(barTabService);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -218,7 +218,7 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
() => service.decreaseStock('low-stock', 5),
|
() => service.decreaseStock('low-stock', 5),
|
||||||
throwsA(isA<Exception>()),
|
throwsA(isA<InsufficientStockException>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user