chore: Flutter mobile app, CI, and dev tooling
- mobile/: Flutter/Dart merchant mobile app skeleton - .github/: GitHub Actions CI workflows - .dockerignore: exclude host node_modules from build context - .cursorrules: Cursor IDE project rules - .claude/: Claude Code project settings and launch config Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# meezi_pos
|
||||
|
||||
Tablet POS app for Meezi (parallel to `meezi_app` customer app).
|
||||
|
||||
## Phase 1 (current)
|
||||
|
||||
- Flutter 3 + Riverpod + GoRouter
|
||||
- Login shell → POS shell
|
||||
- `X-Meezi-Terminal-Id` header (wire in Dio client)
|
||||
|
||||
## Phase 2
|
||||
|
||||
- Drift SQLite: menu cache, cart, sync queue
|
||||
- Full POS flow + `bluetooth_print` / `esc_pos_utils_plus`
|
||||
- Same OTP API as dashboard
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
cd mobile/meezi_pos
|
||||
flutter pub get
|
||||
flutter run
|
||||
```
|
||||
|
||||
Set API base URL via `--dart-define=API_URL=http://10.0.2.2:5080` (Android emulator).
|
||||
@@ -0,0 +1 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:meezi_pos/features/auth/login_screen.dart';
|
||||
import 'package:meezi_pos/features/pos/pos_screen.dart';
|
||||
|
||||
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
initialLocation: '/login',
|
||||
routes: [
|
||||
GoRoute(path: '/login', builder: (_, __) => const LoginScreen()),
|
||||
GoRoute(path: '/pos', builder: (_, __) => const PosScreen()),
|
||||
],
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Phase 1: online-only OTP login (wire Dio to /api/auth/*).
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final _phone = TextEditingController(text: '09121234567');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text('میزی — صندوق', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 24),
|
||||
TextField(
|
||||
controller: _phone,
|
||||
keyboardType: TextInputType.phone,
|
||||
decoration: const InputDecoration(labelText: 'موبایل', border: OutlineInputBorder()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () => context.go('/pos'),
|
||||
child: const Text('ورود (دمو)'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Phase 1: online POS shell. Phase 2: Drift cart + sync queue + bluetooth_print.
|
||||
class PosScreen extends StatelessWidget {
|
||||
const PosScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('صندوق')),
|
||||
body: const Center(
|
||||
child: Text('POS — منو و سبد در فاز بعدی (Drift + API)'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:meezi_pos/core/router/app_router.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(const ProviderScope(child: MeeziPosApp()));
|
||||
}
|
||||
|
||||
class MeeziPosApp extends ConsumerWidget {
|
||||
const MeeziPosApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final router = ref.watch(appRouterProvider);
|
||||
return MaterialApp.router(
|
||||
title: 'Meezi POS',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0F6E56)),
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
name: meezi_pos
|
||||
description: Meezi tablet POS (Riverpod, Drift, offline sync)
|
||||
publish_to: "none"
|
||||
version: 0.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=3.2.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_riverpod: ^2.6.1
|
||||
go_router: ^14.6.2
|
||||
dio: ^5.7.0
|
||||
shamsi_date: ^1.0.4
|
||||
intl: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
Reference in New Issue
Block a user