feat: add payment method

This commit is contained in:
2026-07-29 21:25:56 +02:00
parent cd75d3a474
commit 3cf4787e8c
14 changed files with 652 additions and 50 deletions
+10 -1
View File
@@ -34,7 +34,7 @@ class _KoolTabAppState extends State<KoolTabApp> {
debugShowCheckedModeBanner: false,
routerConfig: widget.router,
theme: lightTheme,
darkTheme: darkTheme,
darkTheme: _themeFor(settings.themeMode),
themeMode: _mapThemeMode(settings.themeMode, context),
);
}
@@ -45,8 +45,17 @@ class _KoolTabAppState extends State<KoolTabApp> {
return switch (mode) {
AppThemeMode.dark => ThemeMode.dark,
AppThemeMode.light => ThemeMode.light,
AppThemeMode.ugly => ThemeMode.dark,
AppThemeMode.system =>
hasLoaded ? ThemeMode.system : ThemeMode.dark,
};
}
ThemeData _themeFor(AppThemeMode mode) {
return switch (mode) {
AppThemeMode.ugly => uglyTheme,
_ => darkTheme,
};
}
}
+7 -1
View File
@@ -73,6 +73,8 @@ class ClosedTabs extends Table {
DateTimeColumn get closedAt => dateTime()();
TextColumn get paymentMethod => text().withDefault(const Constant('cash'))();
@override
Set<Column> get primaryKey => {id};
}
@@ -126,7 +128,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 5;
int get schemaVersion => 6;
@override
MigrationStrategy get migration {
@@ -152,6 +154,10 @@ class AppDatabase extends _$AppDatabase {
if (from < 5) {
await migrator.createTable(appSettingsTable);
}
if (from < 6) {
await migrator.addColumn(closedTabs, closedTabs.paymentMethod);
}
},
);
}
+75 -3
View File
@@ -1417,12 +1417,25 @@ class $ClosedTabsTable extends ClosedTabs
type: DriftSqlType.dateTime,
requiredDuringInsert: true,
);
static const VerificationMeta _paymentMethodMeta = const VerificationMeta(
'paymentMethod',
);
@override
late final GeneratedColumn<String> paymentMethod = GeneratedColumn<String>(
'payment_method',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('cash'),
);
@override
List<GeneratedColumn> get $columns => [
id,
originalTabId,
customerName,
closedAt,
paymentMethod,
];
@override
String get aliasedName => _alias ?? actualTableName;
@@ -1471,6 +1484,15 @@ class $ClosedTabsTable extends ClosedTabs
} else if (isInserting) {
context.missing(_closedAtMeta);
}
if (data.containsKey('payment_method')) {
context.handle(
_paymentMethodMeta,
paymentMethod.isAcceptableOrUnknown(
data['payment_method']!,
_paymentMethodMeta,
),
);
}
return context;
}
@@ -1496,6 +1518,10 @@ class $ClosedTabsTable extends ClosedTabs
DriftSqlType.dateTime,
data['${effectivePrefix}closed_at'],
)!,
paymentMethod: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}payment_method'],
)!,
);
}
@@ -1510,11 +1536,13 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
final String originalTabId;
final String customerName;
final DateTime closedAt;
final String paymentMethod;
const ClosedTabsRow({
required this.id,
required this.originalTabId,
required this.customerName,
required this.closedAt,
required this.paymentMethod,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@@ -1523,6 +1551,7 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
map['original_tab_id'] = Variable<String>(originalTabId);
map['customer_name'] = Variable<String>(customerName);
map['closed_at'] = Variable<DateTime>(closedAt);
map['payment_method'] = Variable<String>(paymentMethod);
return map;
}
@@ -1532,6 +1561,7 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
originalTabId: Value(originalTabId),
customerName: Value(customerName),
closedAt: Value(closedAt),
paymentMethod: Value(paymentMethod),
);
}
@@ -1545,6 +1575,7 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
originalTabId: serializer.fromJson<String>(json['originalTabId']),
customerName: serializer.fromJson<String>(json['customerName']),
closedAt: serializer.fromJson<DateTime>(json['closedAt']),
paymentMethod: serializer.fromJson<String>(json['paymentMethod']),
);
}
@override
@@ -1555,6 +1586,7 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
'originalTabId': serializer.toJson<String>(originalTabId),
'customerName': serializer.toJson<String>(customerName),
'closedAt': serializer.toJson<DateTime>(closedAt),
'paymentMethod': serializer.toJson<String>(paymentMethod),
};
}
@@ -1563,11 +1595,13 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
String? originalTabId,
String? customerName,
DateTime? closedAt,
String? paymentMethod,
}) => ClosedTabsRow(
id: id ?? this.id,
originalTabId: originalTabId ?? this.originalTabId,
customerName: customerName ?? this.customerName,
closedAt: closedAt ?? this.closedAt,
paymentMethod: paymentMethod ?? this.paymentMethod,
);
ClosedTabsRow copyWithCompanion(ClosedTabsCompanion data) {
return ClosedTabsRow(
@@ -1579,6 +1613,9 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
? data.customerName.value
: this.customerName,
closedAt: data.closedAt.present ? data.closedAt.value : this.closedAt,
paymentMethod: data.paymentMethod.present
? data.paymentMethod.value
: this.paymentMethod,
);
}
@@ -1588,13 +1625,15 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
..write('id: $id, ')
..write('originalTabId: $originalTabId, ')
..write('customerName: $customerName, ')
..write('closedAt: $closedAt')
..write('closedAt: $closedAt, ')
..write('paymentMethod: $paymentMethod')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, originalTabId, customerName, closedAt);
int get hashCode =>
Object.hash(id, originalTabId, customerName, closedAt, paymentMethod);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@@ -1602,7 +1641,8 @@ class ClosedTabsRow extends DataClass implements Insertable<ClosedTabsRow> {
other.id == this.id &&
other.originalTabId == this.originalTabId &&
other.customerName == this.customerName &&
other.closedAt == this.closedAt);
other.closedAt == this.closedAt &&
other.paymentMethod == this.paymentMethod);
}
class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
@@ -1610,12 +1650,14 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
final Value<String> originalTabId;
final Value<String> customerName;
final Value<DateTime> closedAt;
final Value<String> paymentMethod;
final Value<int> rowid;
const ClosedTabsCompanion({
this.id = const Value.absent(),
this.originalTabId = const Value.absent(),
this.customerName = const Value.absent(),
this.closedAt = const Value.absent(),
this.paymentMethod = const Value.absent(),
this.rowid = const Value.absent(),
});
ClosedTabsCompanion.insert({
@@ -1623,6 +1665,7 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
required String originalTabId,
required String customerName,
required DateTime closedAt,
this.paymentMethod = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
originalTabId = Value(originalTabId),
@@ -1633,6 +1676,7 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
Expression<String>? originalTabId,
Expression<String>? customerName,
Expression<DateTime>? closedAt,
Expression<String>? paymentMethod,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
@@ -1640,6 +1684,7 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
if (originalTabId != null) 'original_tab_id': originalTabId,
if (customerName != null) 'customer_name': customerName,
if (closedAt != null) 'closed_at': closedAt,
if (paymentMethod != null) 'payment_method': paymentMethod,
if (rowid != null) 'rowid': rowid,
});
}
@@ -1649,6 +1694,7 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
Value<String>? originalTabId,
Value<String>? customerName,
Value<DateTime>? closedAt,
Value<String>? paymentMethod,
Value<int>? rowid,
}) {
return ClosedTabsCompanion(
@@ -1656,6 +1702,7 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
originalTabId: originalTabId ?? this.originalTabId,
customerName: customerName ?? this.customerName,
closedAt: closedAt ?? this.closedAt,
paymentMethod: paymentMethod ?? this.paymentMethod,
rowid: rowid ?? this.rowid,
);
}
@@ -1675,6 +1722,9 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
if (closedAt.present) {
map['closed_at'] = Variable<DateTime>(closedAt.value);
}
if (paymentMethod.present) {
map['payment_method'] = Variable<String>(paymentMethod.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
@@ -1688,6 +1738,7 @@ class ClosedTabsCompanion extends UpdateCompanion<ClosedTabsRow> {
..write('originalTabId: $originalTabId, ')
..write('customerName: $customerName, ')
..write('closedAt: $closedAt, ')
..write('paymentMethod: $paymentMethod, ')
..write('rowid: $rowid')
..write(')'))
.toString();
@@ -3536,6 +3587,7 @@ typedef $$ClosedTabsTableCreateCompanionBuilder =
required String originalTabId,
required String customerName,
required DateTime closedAt,
Value<String> paymentMethod,
Value<int> rowid,
});
typedef $$ClosedTabsTableUpdateCompanionBuilder =
@@ -3544,6 +3596,7 @@ typedef $$ClosedTabsTableUpdateCompanionBuilder =
Value<String> originalTabId,
Value<String> customerName,
Value<DateTime> closedAt,
Value<String> paymentMethod,
Value<int> rowid,
});
@@ -3575,6 +3628,11 @@ class $$ClosedTabsTableFilterComposer
column: $table.closedAt,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get paymentMethod => $composableBuilder(
column: $table.paymentMethod,
builder: (column) => ColumnFilters(column),
);
}
class $$ClosedTabsTableOrderingComposer
@@ -3605,6 +3663,11 @@ class $$ClosedTabsTableOrderingComposer
column: $table.closedAt,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get paymentMethod => $composableBuilder(
column: $table.paymentMethod,
builder: (column) => ColumnOrderings(column),
);
}
class $$ClosedTabsTableAnnotationComposer
@@ -3631,6 +3694,11 @@ class $$ClosedTabsTableAnnotationComposer
GeneratedColumn<DateTime> get closedAt =>
$composableBuilder(column: $table.closedAt, builder: (column) => column);
GeneratedColumn<String> get paymentMethod => $composableBuilder(
column: $table.paymentMethod,
builder: (column) => column,
);
}
class $$ClosedTabsTableTableManager
@@ -3668,12 +3736,14 @@ class $$ClosedTabsTableTableManager
Value<String> originalTabId = const Value.absent(),
Value<String> customerName = const Value.absent(),
Value<DateTime> closedAt = const Value.absent(),
Value<String> paymentMethod = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => ClosedTabsCompanion(
id: id,
originalTabId: originalTabId,
customerName: customerName,
closedAt: closedAt,
paymentMethod: paymentMethod,
rowid: rowid,
),
createCompanionCallback:
@@ -3682,12 +3752,14 @@ class $$ClosedTabsTableTableManager
required String originalTabId,
required String customerName,
required DateTime closedAt,
Value<String> paymentMethod = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => ClosedTabsCompanion.insert(
id: id,
originalTabId: originalTabId,
customerName: customerName,
closedAt: closedAt,
paymentMethod: paymentMethod,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
+12
View File
@@ -7,6 +7,7 @@ class ClosedTab {
final String originalTabId;
final String customerName;
final DateTime closedAt;
final String paymentMethod;
final List<ClosedTabItem> items;
ClosedTab({
@@ -14,6 +15,7 @@ class ClosedTab {
required this.originalTabId,
required this.customerName,
required this.closedAt,
required this.paymentMethod,
required this.items,
});
@@ -25,6 +27,14 @@ class ClosedTab {
String get formattedTotal =>
NumberFormat.simpleCurrency().format(totalInCents / 100);
String get formattedPaymentMethod {
return switch (paymentMethod) {
'cash' => 'Cash',
'payconiq' => 'Payconiq',
_ => 'Cash',
};
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
@@ -33,6 +43,7 @@ class ClosedTab {
originalTabId == other.originalTabId &&
customerName == other.customerName &&
closedAt == other.closedAt &&
paymentMethod == other.paymentMethod &&
_listEquals(items, other.items);
@override
@@ -41,6 +52,7 @@ class ClosedTab {
originalTabId,
customerName,
closedAt,
paymentMethod,
Object.hashAll(items),
);
}
+1 -1
View File
@@ -1,4 +1,4 @@
enum AppThemeMode { system, light, dark }
enum AppThemeMode { system, light, dark, ugly }
class AppSettings {
final bool pinRequired;
+4 -2
View File
@@ -29,7 +29,7 @@ abstract class BarTabService {
/// Archives the tab's current items into history and clears them.
/// The tab itself stays open under the same customer name.
Future<void> closeTab(String tabId);
Future<void> closeTab(String tabId, {String paymentMethod = 'cash'});
Future<List<ClosedTab>> getClosedTabs();
@@ -234,7 +234,7 @@ class DriftBarTabService implements BarTabService {
}
@override
Future<void> closeTab(String tabId) async {
Future<void> closeTab(String tabId, {String paymentMethod = 'cash'}) async {
await database.transaction(() async {
final tabQuery = database.select(database.barTabs)
..where((tab) => tab.id.equals(tabId));
@@ -261,6 +261,7 @@ class DriftBarTabService implements BarTabService {
originalTabId: tabId,
customerName: tabRow.customerName,
closedAt: DateTime.now(),
paymentMethod: Value(paymentMethod),
),
);
@@ -325,6 +326,7 @@ class DriftBarTabService implements BarTabService {
originalTabId: row.originalTabId,
customerName: row.customerName,
closedAt: row.closedAt,
paymentMethod: row.paymentMethod,
items: itemRows.map<ClosedTabItem>(_mapClosedItemRow).toList(),
),
);
+319
View File
@@ -727,3 +727,322 @@ final ThemeData lightTheme = ThemeData(
),
),
);
// ---------------------------------------------------------------------------
// Ugly theme — intentionally repulsive
// ---------------------------------------------------------------------------
const _uglyFg = Color(0xFFFFF700);
const _uglyBg = Color(0xFF00FFFF);
const _uglySurface = Color(0xFFFF00FF);
const _uglyPrimary = Color(0xFFCCFF00);
const _uglySecondary = Color(0xFFFF1493);
const _uglyError = Color(0xFFFF6600);
final ThemeData uglyTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: const ColorScheme.light(
primary: _uglyPrimary,
secondary: _uglySecondary,
surface: _uglySurface,
error: _uglyError,
onPrimary: Color(0xFF0000FF),
onSecondary: _uglyFg,
onSurface: _uglyFg,
onError: Color(0xFF000000),
onSurfaceVariant: Color(0xFFFF0000),
),
scaffoldBackgroundColor: _uglyBg,
canvasColor: _uglyBg,
appBarTheme: const AppBarTheme(
elevation: 8,
shadowColor: Color(0xFFFF0000),
centerTitle: true,
backgroundColor: _uglySecondary,
foregroundColor: _uglyFg,
surfaceTintColor: _uglyError,
titleTextStyle: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w900,
letterSpacing: 4,
color: _uglyFg,
fontFamily: 'monospace',
),
iconTheme: IconThemeData(color: _uglyBg, size: 30),
actionsIconTheme: IconThemeData(color: _uglyFg, size: 30),
toolbarHeight: 80,
),
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
backgroundColor: _uglyError,
foregroundColor: Colors.black,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(0),
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(0),
),
),
side: const BorderSide(color: _uglyPrimary, width: 3),
padding: const EdgeInsets.all(16),
),
),
cardTheme: CardThemeData(
color: _uglySurface,
elevation: 8,
shadowColor: _uglyPrimary,
margin: const EdgeInsets.symmetric(horizontal: 0, vertical: 18),
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(30),
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(4),
),
side: const BorderSide(color: _uglySecondary, width: 4),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
elevation: 4,
backgroundColor: _uglyPrimary,
foregroundColor: const Color(0xFF0000FF),
shadowColor: _uglyError,
disabledBackgroundColor: _uglyBg,
disabledForegroundColor: _uglyError,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 22),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2),
side: const BorderSide(color: _uglySecondary, width: 3),
),
textStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
letterSpacing: 3,
fontFamily: 'monospace',
),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 6,
backgroundColor: _uglyError,
foregroundColor: _uglyBg,
shadowColor: _uglyPrimary,
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: _uglyFg, width: 4),
),
textStyle: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w900,
fontFamily: 'monospace',
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: _uglyFg,
side: const BorderSide(color: _uglySecondary, width: 5),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
fontFamily: 'monospace',
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: _uglyError,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
textStyle: const TextStyle(fontWeight: FontWeight.w900, fontSize: 16),
),
),
inputDecorationTheme: const InputDecorationTheme(
filled: true,
fillColor: Color(0xFFAAFFAA),
hintStyle: TextStyle(
color: Color(0xFFFF0000),
fontWeight: FontWeight.w900,
fontStyle: FontStyle.italic,
fontFamily: 'monospace',
),
contentPadding: EdgeInsets.symmetric(horizontal: 24, vertical: 20),
border: OutlineInputBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
borderSide: BorderSide(color: _uglySecondary, width: 4),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
borderSide: BorderSide(color: _uglyError, width: 4),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
borderSide: BorderSide(color: _uglyPrimary, width: 6),
),
),
dialogTheme: DialogThemeData(
backgroundColor: _uglySurface,
elevation: 16,
shadowColor: _uglyFg,
surfaceTintColor: _uglyError,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
side: const BorderSide(color: _uglyPrimary, width: 5),
),
titleTextStyle: const TextStyle(
color: _uglyFg,
fontSize: 26,
fontWeight: FontWeight.w900,
fontFamily: 'monospace',
),
contentTextStyle: const TextStyle(
color: _uglyBg,
fontWeight: FontWeight.w700,
height: 1.8,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: _uglySecondary,
foregroundColor: _uglyFg,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
side: BorderSide(color: _uglyPrimary, width: 4),
),
),
listTileTheme: ListTileThemeData(
tileColor: _uglyFg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
side: const BorderSide(color: _uglySecondary, width: 3),
),
iconColor: _uglyBg,
textColor: _uglySurface,
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: _uglyPrimary,
behavior: SnackBarBehavior.floating,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
side: const BorderSide(color: _uglySecondary, width: 4),
),
contentTextStyle: const TextStyle(
color: Color(0xFF0000FF),
fontWeight: FontWeight.w900,
fontFamily: 'monospace',
),
),
dividerTheme: const DividerThemeData(
color: _uglySecondary,
thickness: 5,
space: 8,
),
chipTheme: const ChipThemeData(
backgroundColor: _uglyError,
labelStyle: TextStyle(
color: _uglyFg,
fontWeight: FontWeight.w900,
fontSize: 14,
fontFamily: 'monospace',
),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
bottomLeft: Radius.circular(20),
),
side: BorderSide(color: _uglyPrimary, width: 3),
),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
color: _uglyFg,
fontWeight: FontWeight.w900,
fontSize: 52,
letterSpacing: 5,
fontFamily: 'monospace',
),
headlineMedium: TextStyle(
color: _uglyPrimary,
fontWeight: FontWeight.w900,
fontSize: 34,
letterSpacing: 3,
fontFamily: 'monospace',
),
titleLarge: TextStyle(
color: _uglyFg,
fontWeight: FontWeight.w900,
fontSize: 26,
letterSpacing: 2,
fontFamily: 'monospace',
),
titleMedium: TextStyle(
color: _uglyPrimary,
fontWeight: FontWeight.w900,
fontSize: 20,
fontFamily: 'monospace',
),
bodyLarge: TextStyle(
color: _uglyFg,
fontSize: 18,
fontWeight: FontWeight.w700,
height: 1.8,
fontFamily: 'monospace',
),
bodyMedium: TextStyle(
color: _uglyBg,
fontWeight: FontWeight.w700,
fontSize: 14,
fontFamily: 'monospace',
),
bodySmall: TextStyle(
color: _uglyError,
fontWeight: FontWeight.w900,
fontSize: 12,
fontFamily: 'monospace',
),
labelLarge: TextStyle(
color: _uglySecondary,
fontWeight: FontWeight.w900,
fontSize: 16,
fontFamily: 'monospace',
),
),
);
+4 -4
View File
@@ -115,18 +115,18 @@ class BarScreenViewModel extends ChangeNotifier {
await _reloadTabs();
}
Future<void> closeSelectedTab() async {
Future<void> closeSelectedTab({String paymentMethod = 'cash'}) async {
final tab = selectedTab;
if (tab == null) return;
await barTabService.closeTab(tab.id);
await barTabService.closeTab(tab.id, paymentMethod: paymentMethod);
await _reloadTabs();
}
Future<void> closeTab(String tabId) async {
await barTabService.closeTab(tabId);
Future<void> closeTab(String tabId, {String paymentMethod = 'cash'}) async {
await barTabService.closeTab(tabId, paymentMethod: paymentMethod);
await _reloadTabs();
}
+3
View File
@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:drift/drift.dart';
import 'package:kooltab2/database/app_database.dart';
import 'package:kooltab2/services/bar_tab_service.dart';
import 'package:kooltab2/services/pin_lock_service.dart';
@@ -274,6 +275,7 @@ class DevMenuViewModel extends ChangeNotifier {
'Sam', 'Tina', 'Umar', 'Vera', 'Wes', 'Xena',
'Yves', 'Zara',
];
const paymentMethods = ['cash', 'payconiq'];
await database.transaction(() async {
for (var i = 0; i < count; i++) {
@@ -295,6 +297,7 @@ class DevMenuViewModel extends ChangeNotifier {
originalTabId: uuid.v4(),
customerName: customer,
closedAt: closedAt,
paymentMethod: Value(paymentMethods[(i * 3) % paymentMethods.length]),
),
);
+101 -27
View File
@@ -1,7 +1,9 @@
import 'package:kooltab2/viewmodels/bar_screen_view_model.dart';
import 'package:kooltab2/views/widgets/slide_confirm.dart';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import '../../viewmodels/bar_screen_view_model.dart';
import '../widgets/slide_confirm.dart';
Future<void> confirmCloseTab(BuildContext context) async {
final viewModel = context.read<BarScreenViewModel>();
@@ -9,39 +11,111 @@ Future<void> confirmCloseTab(BuildContext context) async {
if (tab == null) return;
String paymentMethod = 'cash';
final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: Text('Close ${tab.customerName}ʼs tab?'),
content: SizedBox(
width: 360,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Current total: ${tab.formattedTotal}'),
const SizedBox(height: 20),
SlideConfirm(
onConfirmed: () {
Navigator.of(dialogContext).pop(true);
},
return StatefulBuilder(
builder: (context, setDialogState) {
return AlertDialog(
title: Text('Close ${tab.customerName}ʼs tab?'),
content: SizedBox(
width: 360,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Current total: ${tab.formattedTotal}'),
const SizedBox(height: 20),
Text(
'Payment method',
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 8),
_PaymentPicker(
selected: paymentMethod,
onChanged: (value) {
setDialogState(() => paymentMethod = value);
},
),
const SizedBox(height: 24),
SlideConfirm(
onConfirmed: () {
Navigator.of(dialogContext).pop(true);
},
),
],
),
),
actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
),
],
),
),
actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
),
],
);
},
);
},
);
if (confirmed != true) return;
await viewModel.closeSelectedTab();
await viewModel.closeSelectedTab(paymentMethod: paymentMethod);
}
class _PaymentPicker extends StatelessWidget {
final String selected;
final ValueChanged<String> onChanged;
const _PaymentPicker({
required this.selected,
required this.onChanged,
});
static const _options = [
('cash', 'Cash'),
('payconiq', 'Payconiq'),
];
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _options.map((option) {
final (value, label) = option;
final isSelected = selected == value;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: ChoiceChip(
showCheckmark: false,
selected: isSelected,
onSelected: (_) => onChanged(value),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (value == 'cash')
const Icon(
Icons.attach_money,
size: 16,
color: Colors.lightGreen,
)
else
SvgPicture.asset(
'assets/icons/payconic.svg',
width: 16,
height: 16,
colorFilter: ColorFilter.mode(Colors.pinkAccent, BlendMode.srcIn),
),
const SizedBox(width: 6),
Text(label),
],
),
),
);
}).toList(),
);
}
}
+2
View File
@@ -219,6 +219,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
AppThemeMode.system => 'System',
AppThemeMode.light => 'Light',
AppThemeMode.dark => 'Dark',
AppThemeMode.ugly => 'Ugly',
},
onTap: () async {
final selected = await showModalBottomSheet<AppThemeMode>(
@@ -234,6 +235,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
AppThemeMode.system => 'System',
AppThemeMode.light => 'Light',
AppThemeMode.dark => 'Dark',
AppThemeMode.ugly => 'Ugly',
}),
onChanged: (value) =>
Navigator.pop(context, value),
+52 -9
View File
@@ -15,6 +15,13 @@ class ClosedTabCard extends StatefulWidget {
class _ClosedTabCardState extends State<ClosedTabCard> {
bool _expanded = false;
IconData _paymentIcon(String method) {
return switch (method) {
'payconiq' => Icons.qr_code_rounded,
_ => Icons.money_rounded,
};
}
@override
Widget build(BuildContext context) {
final closedTab = widget.closedTab;
@@ -49,15 +56,51 @@ class _ClosedTabCardState extends State<ClosedTabCard> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
closedTab.customerName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: scheme.onSurface,
),
Row(
children: [
Expanded(
child: Text(
closedTab.customerName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: scheme.onSurface,
),
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
decoration: BoxDecoration(
color: scheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(999),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_paymentIcon(closedTab.paymentMethod),
size: 13,
color: scheme.primary,
),
const SizedBox(width: 4),
Text(
closedTab.formattedPaymentMethod,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: scheme.primary,
),
),
],
),
),
],
),
const SizedBox(height: 2),
Text(
+56
View File
@@ -358,6 +358,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.3"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -680,6 +688,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider:
dependency: "direct main"
description:
@@ -728,6 +744,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform:
dependency: transitive
description:
@@ -925,6 +949,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.3"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
@@ -989,6 +1037,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
yaml:
dependency: transitive
description:
+4
View File
@@ -50,6 +50,7 @@ dependencies:
package_info_plus: ^10.1.0
open_filex: ^4.7.0
ota_update: ^7.1.0
flutter_svg: ^2.3.0
# permission_handler: ^12.0.3
@@ -83,6 +84,9 @@ flutter:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/icons/payconic.svg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images