pos changes

This commit is contained in:
2026-08-06 19:26:53 +05:30
parent cb065a0f69
commit eebd10da6d
42 changed files with 3451 additions and 2531 deletions

View File

@@ -35,7 +35,6 @@ class SyncConfig {
this.password,
this.httpBaseUrl = '',
this.apiKey,
this.sessionToken,
this.ackTimeout = const Duration(seconds: 20),
this.batchSize = 50,
});
@@ -53,38 +52,8 @@ class SyncConfig {
final String? password;
final String httpBaseUrl;
/// A static key shared by every terminal at a deployment, if one is set.
///
/// Predates sign-in and says nothing about *who* is at the till, so it cannot
/// scope a request to an outlet. Kept for deployments that put one in front
/// of the endpoint.
final String? apiKey;
/// The signed session from `POST /login`, held for the trading day.
///
/// Distinct from [apiKey] because the two answer different questions. The key
/// says "this request came from our fleet"; the session says "this request
/// came from Selvapuram, signed in as Ragul, and may touch that outlet and no
/// other". Only the second can stop a till reaching another tenant's books,
/// which is why it takes precedence when both are present.
final String? sessionToken;
/// What goes in the Authorization header.
///
/// One accessor rather than the same `??` repeated at each call site, because
/// the request that forgot it would be the one silently sending no
/// credentials at all.
String? get bearerToken {
final session = sessionToken?.trim();
if (session != null && session.isNotEmpty) return session;
final key = apiKey?.trim();
if (key != null && key.isNotEmpty) return key;
return null;
}
/// How long to wait for the back office to confirm a batch before treating
/// the outcome as unknown and leaving every row pending.
///
@@ -179,7 +148,6 @@ class SyncConfig {
String? password,
String? httpBaseUrl,
String? apiKey,
String? sessionToken,
Duration? ackTimeout,
int? batchSize,
}) =>
@@ -194,7 +162,6 @@ class SyncConfig {
password: password ?? this.password,
httpBaseUrl: httpBaseUrl ?? this.httpBaseUrl,
apiKey: apiKey ?? this.apiKey,
sessionToken: sessionToken ?? this.sessionToken,
ackTimeout: ackTimeout ?? this.ackTimeout,
batchSize: batchSize ?? this.batchSize,
);

View File

@@ -1,11 +1,17 @@
/// Typed references to bundled assets.
///
/// Only sounds are bundled: product imagery uses emoji glyphs and the welcome
/// artwork is painted in code, so there are no raster or SVG assets to ship.
/// Product imagery uses emoji glyphs and the welcome artwork is painted in
/// code, so the only raster asset shipped is the mark itself.
class AssetPaths {
const AssetPaths._();
static const String _snd = 'assets/sounds';
static const String _img = 'assets/images';
/// The Nearle mark. Every place that used to draw a letter "N" in a
/// gradient box now renders this instead, so the brand cannot drift between
/// the login screen, the sidebar and the cashier header.
static const String logo = '$_img/logo.png';
static const String beepSuccess = '$_snd/beep_success.wav';
static const String beepError = '$_snd/beep_error.wav';

View File

@@ -8,6 +8,7 @@ import '../../presentation/auth/screens/login_screen.dart';
import '../../presentation/payment/screens/payment_screen.dart';
import '../../presentation/pos/screens/pos_dashboard_screen.dart';
import '../../presentation/receipt/screens/receipt_screen.dart';
import '../../presentation/shift/screens/end_shift_screen.dart';
class AppRoutes {
const AppRoutes._();
@@ -19,6 +20,10 @@ class AppRoutes {
static const String pos = '/';
static const String payment = '/payment';
static const String receipt = '/receipt';
/// Drawer count and hand-over. Reached from the session-end chooser, never
/// linked to directly, and guarded like every other signed-in route.
static const String endShift = '/end-shift';
}
/// Router with an authentication guard.
@@ -60,6 +65,11 @@ final routerProvider = Provider<GoRouter>((ref) {
pageBuilder: (context, state) =>
_fade(state, const PosDashboardScreen()),
),
GoRoute(
path: AppRoutes.endShift,
name: 'endShift',
pageBuilder: (context, state) => _slide(state, const EndShiftScreen()),
),
GoRoute(
path: AppRoutes.payment,
name: 'payment',

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import '../constants/asset_paths.dart';
import '../theme/app_colors.dart';
/// The Nearle mark on its tile.
///
/// Every surface that shows the brand — login, sidebar, cashier header —
/// renders this, so the mark cannot drift between them. It replaces the
/// hand-drawn letter "N" in a gradient box that each of those screens used to
/// build for itself.
class BrandMark extends StatelessWidget {
const BrandMark({
super.key,
this.size = 36,
this.radius,
this.onDark = false,
});
final double size;
final double? radius;
/// Set on a coloured background, where the tile needs no border to separate
/// it from what is behind.
final bool onDark;
@override
Widget build(BuildContext context) {
final corner = BorderRadius.circular(radius ?? size * 0.28);
return Container(
width: size,
height: size,
// Clipped, not padded: the mark fills the tile edge to edge and the
// rounded corner does the trimming, so nothing can spill past the box
// whatever aspect ratio the file happens to have.
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: corner,
border: onDark ? null : Border.all(color: AppColors.border),
),
child: Image.asset(
AssetPaths.logo,
fit: BoxFit.fill,
width: size,
height: size,
filterQuality: FilterQuality.medium,
// A missing or undeclared asset would otherwise blank the brand out
// of the sidebar entirely; the letterform is a poor substitute but a
// better failure than nothing.
errorBuilder: (context, _, __) => FittedBox(
fit: BoxFit.contain,
child: Text(
'N',
style: TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w800,
fontSize: size,
),
),
),
),
);
}
}