Restaurant App
Sellino Food — the kitchen board, the menu and the floor in your pocket
Developed By: BugBuild Labs
Introduction
The Restaurant App is what a kitchen runs on: accept orders with a prep time, cook, mark ready, 86 a dish that ran out, open and close the outlet, and — with the Restaurant module on — work the floor and the running checks.
One Flutter codebase for Android and iOS, on the same /api/v10 backend as the panel. A restaurant sees only its own data, enforced on the server, not by hiding buttons.
Everything here also exists in the web restaurant panel. The app is for the pass, where nobody has a laptop.
Requirements
- Flutter SDK (stable channel) with Dart —
flutter doctorshould pass. - Android Studio (Android SDK 21+) and/or Xcode for iOS builds (macOS only).
- A running Sellino Food backend reachable over HTTPS — set up first via Quick Start.
- An approved staff login on that backend (cashier, branch manager or admin).
- For push notifications: a Firebase project — see Common Setup.
Package & Source Code
The download's Main_Files/RestaurantAppSourceCode/ folder holds the complete Flutter project (next to WebSourceCode and the other two app folders). Open it in Android Studio or VS Code and run flutter pub get.
lib/core/— environment config, API client, theme, shared widgets and controllers.lib/features/— one folder per screen area:kitchen(board, accept/ready, menu availability, add-on groups, outlets),restaurant(floor, check, KDS),orders,products,delivery,payouts,wallet,reports,chat,notifications,settings.assets/branding/— app icon sources used by the launcher-icon generator.
App Configuration
- Open the
RestaurantAppSourceCodefolder in your IDE and runflutter pub get. - Set the API base URL in
lib/core/env/prod_env.dart(release builds) orlib/core/env/dev_env.dart(debug builds) — image URLs, websocket scheme and broadcasting auth URL are derived from it automatically. - Paste the App API key (
kAppApiKey) from Admin → Settings → API Security in the same file. Wrong or missing key → 401 on every screen. - If you use real-time features, set
kReverbAppKeyto the backend's effective Reverb key (Settings → Realtime, elseREVERB_APP_KEYfrom.env). - Configure push notification keys if used — see Common Setup.
// lib/core/env/prod_env.dart → used by RELEASE builds
class ProdEnv implements Env {
static const String kBaseUrl = 'https://yourdomain.com/api/v10'; // your API root
static const String kAppApiKey = 'PASTE-KEY-FROM-ADMIN-PANEL'; // Settings → API Security
static const String kReverbAppKey = 'sellino-app-key'; // backend .env REVERB_APP_KEY
}
// lib/core/env/dev_env.dart → used by DEBUG / PROFILE builds (your local backend)
// imageBaseUrl, reverbScheme and broadcastingAuthUrl are all derived from kBaseUrl.
Tip: always use HTTPS — plain HTTP is blocked by default on Android/iOS. Built-in guard: a release build refuses to start if the key is still empty or the URL still points at a dev host (localhost, .test…), so a forgotten swap fails on your desk, not at the counter.
Never regenerate the App API key after release unless it leaked — installed apps stop working until users update. See Web → Security → App API Key.
App Name & Icon
- Name: change the display name in
android/app/src/main/AndroidManifest.xml(android:label) and iOSInfo.plist(CFBundleDisplayName). - Package / bundle id: set your own unique id (Android
applicationIdinandroid/app/build.gradle; iOS bundle identifier in Xcode) before publishing. - Icon: replace the sources in
assets/branding/and regenerate launcher icons (flutter_launcher_icons).
Build, Signing & Release
Before either platform: confirm the release build points at your production backend — lib/core/env/prod_env.dart. The app's built-in guard throws on first launch if the key is empty or the URL is still a dev host, so a forgotten swap fails on your desk.
Android — release signing
1. Generate a keystore. Run this once and keep the file safe — Google Play ties your app to it forever; lose it and you cannot publish updates under the same listing.
keytool -genkey -v -keystore ~/sellino-food-release.jks \ -keyalg RSA -keysize 2048 -validity 10000 -alias sellino-food # keytool ships with the JDK. On Windows it is: # "%JAVA_HOME%\bin\keytool" -genkey -v -keystore %USERPROFILE%\sellino-food-release.jks ^ # -keyalg RSA -keysize 2048 -validity 10000 -alias sellino-food
It asks for a keystore password, a key password and your name/organisation details. Note both passwords down — the next step needs them.
2. Create android/key.properties (same folder as android/app/), with exactly these four keys:
storePassword=your_keystore_password keyPassword=your_key_password keyAlias=sellino-food storeFile=/Users/you/sellino-food-release.jks # storeFile — absolute path, or a path relative to the android/ folder. # Windows: use forward slashes, e.g. C:/Users/you/sellino-food-release.jks
Never commit key.properties or the .jks file to source control, and never ship them to anyone. They are the identity of your published app.
3. The Gradle wiring is already done. android/app/build.gradle.kts reads that file and applies it — nothing to edit, shown here so you know what happens:
// Loads key.properties if the buyer created it
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists()) {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = (keystoreProperties["storeFile"] as String?)?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String?
}
}
}
buildTypes {
release {
// Your keystore when key.properties exists; debug signing otherwise,
// so `flutter run --release` still works before you set one up.
signingConfig = if (keystorePropertiesFile.exists())
signingConfigs.getByName("release")
else
signingConfigs.getByName("debug")
}
}
Play Store builds must not be debug-signed. If key.properties is missing, a release build now stops with a clear error instead of silently signing with the debug key (which Google Play rejects); create the file and build again. Create the file before your first release.
4. Build.
flutter build appbundle --release --obfuscate --split-debug-info=build/symbols # .aab → Google Play flutter build apk --release --obfuscate --split-debug-info=build/symbols # .apk → direct install / other stores # --obfuscate hides Dart symbol names in the binary; keep build/symbols safe — it is what # turns an obfuscated crash log back into readable stack traces (flutter symbolize). # Verify what signed it (should NOT say "Android Debug"): keytool -printcert -jarfile build/app/outputs/bundle/release/app-release.aab
Also set your own applicationId in android/app/build.gradle.kts before publishing — the package ships as com.sellino.seller and every listing needs a unique id you own.
iOS — signing & distribution
iOS builds need macOS with Xcode and a paid Apple Developer Program membership (US$99/year — a third-party cost, not included).
- Bundle identifier. Open
ios/Runner.xcworkspacein Xcode (the workspace, not the project). Select the Runner target → General → set your own reverse-domain Bundle Identifier (e.g.com.yourbrand.pos). It must match the App ID you register with Apple, and should mirror the AndroidapplicationId. - Signing team. Xcode → Settings → Accounts → add your Apple ID. Then on the Runner target → Signing & Capabilities → tick Automatically manage signing and pick your Team. Xcode then creates the development certificate and provisioning profile for you — this is the recommended route.
- Manual signing (only if your organisation requires it): create a Distribution Certificate and an App Store provisioning profile at developer.apple.com → Certificates, Identifiers & Profiles, download both, double-click to install, then untick automatic signing and select the profile.
- Register the App ID at Identifiers using the same bundle id. If you use push, enable the Push Notifications capability here and generate the APNs key — see Common Setup.
- Create the app record in App Store Connect → My Apps → + → pick the bundle id, set name, primary language and SKU.
- Version & build number. Set them in
pubspec.yaml(version: 1.0.0+1— the part after+is the build number). Every upload to App Store Connect needs a build number higher than the last. - Archive and upload.
flutter build ipa --release --obfuscate --split-debug-info=build/symbols # then either open the generated .xcarchive in Xcode → Distribute App, # or upload build/ios/ipa/*.ipa with Transporter (free, Mac App Store)
In Xcode the equivalent is Product → Archive → Distribute App → App Store Connect. - TestFlight → review. The build appears in App Store Connect after processing (a few minutes to an hour). Test it via TestFlight, fill in the store listing, privacy details and screenshots, then submit for review.
Apple's review requires a reachable Privacy Policy URL and an in-app account deletion path. Both ship with the app — the sign-in screen links the policy (edited in Website → Pages) and Settings carries account deletion.
Store accounts (Google Play one-time US$25 / Apple Developer US$99 per year) are third-party services and are not included with this item.
For Developers — API Structure & Data Model
The app is a pure API client: it holds no business logic of its own and writes nothing the web panel cannot read. Everything goes through /api/v10/* on your Laravel backend.
Every request carries these headers
Accept: application/json X-App-Key: <the App API key from Settings → API Security> // rejected with 401 if wrong Authorization: Bearer <jwt> // authenticated calls only X-Branch-Id: <active branch id> // the multi-branch scope Accept-Language: en | bn | ar | he // drives translated responses
X-Branch-Id is the heart of the app. Stock, counters, registers, orders and reports are all scoped to it server-side, so switching branch in the UI changes what every subsequent endpoint returns. It is restored from secure storage before the first request on launch.
Response envelope
Every endpoint answers in the same shape, so the app has one parser and one error path:
{ "status": true, "message": "Request successfully executed", "data": { … } }
{ "status": false, "message": "Human readable error", "data": {} }
// Paginated lists put the rows in data.items alongside:
// current_page · last_page · per_page · total
Endpoint families
Routes live in routes/api/v10/ on the backend, one file per area. The app's back office uses the staff group (routes/api/v10/staff/, ~34 files) — each route behind auth:api, auth.staff and its own hasPermission:* gate:
auth pos cash-register counter orders products inventory warehouses branches categories brands attributes customers suppliers members accounts reports returns replacements coupon campaigns flash-sell users panel-features activity-logs archive
The same hasPermission:* names drive the app's drawer — see Web → Users & Roles. A role missing order_read gets no Orders menu item and a 403 if it calls the endpoint directly.
Core data model
| Entity | Relationship |
|---|---|
| Branch | The top-level scope. Owns warehouses, counters and staff assignments; every stock row, register and order belongs to one. |
| Warehouse → Rack | Stock lives per warehouse; racks locate it inside one. The cart's rack picker reads these. |
| Counter → Cash Register | A counter is the physical till; a register is one shift on it (open → sell → close). One open register per counter, one per user. |
| Product → Variant | Products carry attributes; the sellable unit is the variant (own SKU, barcode, price, stock). |
| Stock → Cost layers | On-hand quantity per variant per warehouse, backed by FIFO cost layers that purchases create and sales consume. |
| Order → Items → Payments | A sale, its lines, and one or more payments (cash / bank / MFS split), tied to the register it was rung on. |
| Journal → Transactions | Every money movement posts a balanced journal of ledger lines against Chart of Accounts heads — sales, expenses, pickups, purchases, returns. |
| User → Role → Permissions | Roles bundle permissions; users may also carry per-user overrides. This is what gates both menu and API. |
Auth & token lifecycle
- Login returns a JWT plus its expiry; the app stores it with
flutter_secure_storage(Keychain on iOS, EncryptedSharedPreferences on Android) — never in plain preferences. - An interceptor refreshes the token through
POST /login/refreshbefore expiry, and retries once on a401; concurrent calls share one refresh instead of racing. - Auth endpoints are rate limited server-side (
throttlemiddleware) against brute force.
Extending it
- Add a backend route under
routes/api/v10/staff/with itshasPermission:*gate, then a matchingservice+controllerin the app's feature folder — the folder layout underlib/features/mirrors these route files closely. lib/core/network/api_endpoints.dartholds every path in one place;dio_client.dartowns headers, refresh and error mapping.- Full endpoint-by-endpoint reference:
docs/api-v10-endpoints.mdin the backend source.
Login & Outlet
- Sign in with the restaurant account you approved in the panel, or with a staff user that belongs to it.
- Outlet switcher — a chain picks which kitchen it is standing in; the board, the menu and the reports all follow that choice.
- Permissions decide the menu — a chef sees the board, a manager sees payouts and reports.
- Store Open / Closed sits in the app bar: one tap stops new orders when the kitchen is overwhelmed.
Kitchen Board
The screen the kitchen actually lives on.
- Tabs — New, Preparing, Ready, Out. Each card shows the items, the add-ons, the customer note and how long it has been waiting.
- Accept with a prep time, or Reject — which cancels the order and refunds the customer.
- Start preparing and Ready, one tap each.
- New-order alert — a push notification with a sound so a busy kitchen does not miss an order.
- The board polls, so it keeps working on a weak connection instead of going blank.
Outlet Profile
- Open / Closed override — beats the schedule right now.
- Map pin and delivery radius — how far this kitchen will go.
- Opening hours per weekday, several windows a day.
- Average prep time — half of the ETA the customer sees.
Floor & Running Checks
Present when the Restaurant module is switched on.
- Floor map — table tiles with their live status; tap to seat guests or resume the check already running.
- Check screen — items, add-ons, notes and totals, with Send to kitchen, Request bill and Close.
- Move a check to another table or merge two checks into one bill.
- Guest requests — "call waiter" and "bill please" from the QR menu arrive here.
Kitchen Display in the App
- The same three-column board as the web KDS, sized for a tablet propped at the pass.
- Late tickets go red; one bump per card moves it along.
- Filter by station so the grill screen shows grill tickets only.
Orders, Payouts & Reports
- Orders — every order for this restaurant with its status, kitchen state, rider and payment.
- Riders — assign the restaurant's own rider to a ready order.
- Payouts & wallet — balance, commission deducted, withdrawal requests and their status.
- Reports — sales, best sellers and the kitchen numbers, on the phone.
- Notifications and chat with customers.