Add full app source

Adds the Flutter inventory app source, cleaned up for a shared repo:
generic package name, no hardcoded ERP endpoints (moved to gitignored
local config), no dead auth code, no debug logging of session data.
This commit is contained in:
BACHIR SOULDI
2026-07-16 14:37:28 +01:00
parent 37b0762921
commit 5a483da01d
110 changed files with 8081 additions and 0 deletions

View File

@@ -0,0 +1,558 @@
import 'dart:typed_data';
import 'dart:ui' show ImageFilter;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:inventory_app/constants.dart';
// ── Gradient background ───────────────────────────────────────────────────────
class FancyBackground extends StatelessWidget {
const FancyBackground({super.key});
@override
Widget build(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
return DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors:
dark
? [
const Color(0xFF0A0A0A),
const Color(0xFF121212),
const Color(0xFF1E1E1E),
]
: [
const Color(0xFFEEF2FF),
const Color(0xFFE0F2FE),
const Color(0xFFE6FFFA),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Stack(
children: [
Positioned(
top: -60,
right: -40,
child: _Blob(
color:
dark
? Colors.blue.withOpacity(0.1)
: Colors.blue.withOpacity(0.18),
size: 170,
),
),
Positioned(
bottom: -40,
left: -40,
child: _Blob(
color:
dark
? Colors.cyanAccent.withOpacity(0.08)
: Colors.cyan.withOpacity(0.16),
size: 150,
),
),
],
),
);
}
}
class _Blob extends StatelessWidget {
final Color color;
final double size;
const _Blob({required this.color, required this.size});
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [BoxShadow(color: color, blurRadius: 60, spreadRadius: 30)],
),
);
}
}
// ── Glass card ────────────────────────────────────────────────────────────────
class GlassCard extends StatelessWidget {
final Widget child;
final EdgeInsets? padding;
const GlassCard({super.key, required this.child, this.padding});
@override
Widget build(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors:
dark
? [
Colors.white.withOpacity(0.06),
Colors.white.withOpacity(0.03),
]
: [
Colors.white.withOpacity(0.42),
Colors.white.withOpacity(0.18),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
border: Border.all(
color:
dark
? Colors.white.withOpacity(0.08)
: Colors.white.withOpacity(0.5),
),
boxShadow: [
BoxShadow(
color:
dark
? Colors.black.withOpacity(0.4)
: Colors.blueGrey.withOpacity(0.15),
blurRadius: 18,
offset: const Offset(0, 10),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 14, sigmaY: 14),
child: Padding(
padding: padding ?? const EdgeInsets.all(16),
child: child,
),
),
),
);
}
}
// ── App bar for sub-pages (with back button) ──────────────────────────────────
class BackAppBar extends StatelessWidget {
final String title;
final IconData? icon;
final List<Widget>? actions;
const BackAppBar({super.key, required this.title, this.icon, this.actions});
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 10),
child: GlassCard(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back_rounded),
onPressed: () => Get.back(),
color: isDark ? Colors.white70 : Colors.black87,
),
if (icon != null) Icon(icon!, color: Colors.blue),
if (icon != null) const SizedBox(width: 8),
Expanded(
child: Text(
title,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800,
color: isDark ? Colors.white : Colors.blue.shade700,
),
),
),
if (actions != null) ...actions!,
],
),
),
);
}
}
// ── Glass bottom sheet ────────────────────────────────────────────────────────
class GlassBottomSheet extends StatelessWidget {
final Widget child;
const GlassBottomSheet({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12),
child: GlassCard(child: child),
);
}
}
class SheetHandle extends StatelessWidget {
final Color color;
const SheetHandle(this.color, {super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 50,
height: 6,
margin: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: color.withOpacity(.4),
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
// ── Text field ────────────────────────────────────────────────────────────────
class AppTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
final bool enabled;
final IconData? icon;
final TextInputType? type;
const AppTextField({
super.key,
required this.controller,
required this.label,
this.enabled = true,
this.icon,
this.type,
});
@override
Widget build(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
return TextField(
controller: controller,
enabled: enabled,
keyboardType: type,
style: kTextFormFieldStyle(
color: dark ? Colors.white.withOpacity(.9) : Colors.black87,
),
decoration: InputDecoration(
labelText: label,
prefixIcon:
icon != null
? Icon(icon, color: dark ? Colors.white70 : Colors.black54)
: null,
filled: true,
fillColor: dark ? Colors.white12 : Colors.white.withOpacity(.8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
),
);
}
}
class FancyTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
final String hint;
final IconData icon;
final int maxLines;
final String? Function(String?)? validator;
const FancyTextField({
super.key,
required this.controller,
required this.label,
required this.hint,
required this.icon,
this.maxLines = 1,
this.validator,
});
@override
Widget build(BuildContext context) {
final border = OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
);
final isDark = Theme.of(context).brightness == Brightness.dark;
return TextFormField(
controller: controller,
maxLines: maxLines,
validator: validator,
style: TextStyle(
color: isDark ? Colors.white.withOpacity(0.9) : Colors.black87,
),
decoration: InputDecoration(
labelText: label,
hintText: hint,
prefixIcon: Icon(icon, color: isDark ? Colors.white70 : Colors.black54),
filled: true,
fillColor:
isDark
? Colors.white.withOpacity(0.08)
: Colors.white.withOpacity(0.75),
enabledBorder: border,
focusedBorder: border,
),
);
}
}
// ── Dropdown ──────────────────────────────────────────────────────────────────
class AppDropdown<T> extends StatelessWidget {
final T? value;
final List<T> items;
final String hint;
final String Function(T) labelBuilder;
final ValueChanged<T?> onChanged;
const AppDropdown({
super.key,
required this.value,
required this.items,
required this.hint,
required this.labelBuilder,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
return DropdownButtonFormField<T>(
value: value,
isExpanded: true,
items:
items
.map(
(e) => DropdownMenuItem(
value: e,
child: Text(
labelBuilder(e),
overflow: TextOverflow.ellipsis,
style: kTextFormFieldStyle(
color: dark ? Colors.white : Colors.black87,
),
),
),
)
.toList(),
onChanged: onChanged,
decoration: InputDecoration(
labelText: hint,
filled: true,
fillColor: dark ? Colors.white12 : Colors.white.withOpacity(.8),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(16)),
),
);
}
}
// ── Image loader ──────────────────────────────────────────────────────────────
class AppImage extends StatelessWidget {
final Future<Uint8List?> future;
const AppImage({super.key, required this.future});
@override
Widget build(BuildContext context) {
return FutureBuilder<Uint8List?>(
future: future,
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const GlassCard(
child: Center(
child: Padding(
padding: EdgeInsets.all(12),
child: CircularProgressIndicator(),
),
),
);
}
if (!snapshot.hasData) return const SizedBox.shrink();
return GlassCard(
padding: EdgeInsets.zero,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.memory(
snapshot.data!,
height: 200,
width: double.infinity,
fit: BoxFit.cover,
),
),
);
},
);
}
}
// ── Section header ────────────────────────────────────────────────────────────
class SectionHeader extends StatelessWidget {
final IconData icon;
final String title;
final Color accent;
const SectionHeader({
super.key,
required this.icon,
required this.title,
required this.accent,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: accent.withOpacity(.15),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: accent),
),
const SizedBox(width: 10),
Text(
title,
style: kTextFormFieldStyle(
fontSize: 18.0,
fontWeight: FontWeight.w700,
color: Colors.blue.shade700,
),
),
],
);
}
}
class SubLabel extends StatelessWidget {
final String text;
final MaterialColor color;
const SubLabel(this.text, this.color, {super.key});
@override
Widget build(BuildContext context) {
return Text(
text,
style: kTextFormFieldStyle(
fontSize: 14.0,
fontWeight: FontWeight.w600,
color: color.shade700,
),
);
}
}
// ── Radio tile ────────────────────────────────────────────────────────────────
class TileRadio extends StatelessWidget {
final String value;
final String? groupValue;
final String label;
final IconData icon;
final ValueChanged<String?> onChanged;
const TileRadio({
super.key,
required this.value,
required this.groupValue,
required this.label,
required this.icon,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final selected = value == groupValue;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
color:
selected
? Colors.white.withOpacity(.85)
: Colors.white.withOpacity(.6),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color:
selected
? Colors.blue.withOpacity(.6)
: Colors.white.withOpacity(.4),
),
),
child: ListTile(
leading: Icon(icon, color: selected ? Colors.blue : Colors.black54),
title: Text(label, style: kTextFormFieldStyle()),
trailing: Radio<String>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
),
onTap: () => onChanged(value),
),
);
}
}
// ── FAB ───────────────────────────────────────────────────────────────────────
class FancyFAB extends StatelessWidget {
final Color primary;
final VoidCallback onTap;
const FancyFAB({super.key, required this.primary, required this.onTap});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [primary, primary.withOpacity(.8)]),
borderRadius: BorderRadius.circular(28),
boxShadow: [
BoxShadow(
color: primary.withOpacity(.35),
blurRadius: 24,
offset: const Offset(0, 10),
),
],
),
child: FloatingActionButton.extended(
backgroundColor: Colors.transparent,
elevation: 0,
onPressed: onTap,
icon: const Icon(Icons.document_scanner, color: Colors.white),
label: const Text("Scanner", style: TextStyle(color: Colors.white)),
),
);
}
}