first commit structure of app

This commit is contained in:
atseirjo
2026-10-27 13:15:14 +01:00
commit 864da57537
139 changed files with 6030 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controller/anwendung_controller.dart';
class AnwendungView extends GetView<AnwendungController> {
static const routeName = '/anwendung-view';
const AnwendungView({super.key, required String filamentId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Anwendung'),
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Get.back();
},
)
),
body: Container(),
);
}
}
+325
View File
@@ -0,0 +1,325 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../controller/home_controller.dart';
import '../models/kauf_filament_model.dart';
import 'anwendung_view.dart';
class HomeView extends GetView<HomeController> {
static const routeName = '/home-view';
const HomeView({super.key});
@override
Widget build(BuildContext context) {
final dateFormat = DateFormat('dd.MM.yyyy');
return Scaffold(
appBar: AppBar(
title: const Text('Filament Einkaufsverwaltung'),
centerTitle: true,
backgroundColor: Colors.blue.shade300,
),
body: Column(
children: [
// EINGABEBEREICH
Padding(
padding: const EdgeInsets.all(12),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Obx(() => ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: const BorderSide(
color: Colors.grey,
width: 1,
),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
title: const Text('Kaufdatum'),
subtitle: Text(
dateFormat.format(controller.kaufdatum.value),
),
trailing: const Icon(Icons.calendar_month),
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: controller.kaufdatum.value,
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (date != null) {
controller.kaufdatum.value = date;
}
},
)),
const SizedBox(height: 8),
TextFormField(
controller: controller.haendlerController,
decoration: const InputDecoration(
labelText: 'Wo gekauft / Händler',
border: OutlineInputBorder(),
),
validator: (value) =>
value == null || value.trim().isEmpty
? 'Händler eingeben'
: null,
),
const SizedBox(height: 8),
TextFormField(
controller: controller.markeController,
decoration: const InputDecoration(
labelText: 'Marke',
border: OutlineInputBorder(),
),
validator: (value) =>
value == null || value.trim().isEmpty
? 'Marke eingeben'
: null,
),
const SizedBox(height: 8),
Obx(() => DropdownButtonFormField<String>(
key: ValueKey(
'${controller.editingId.value ?? "new"}'
'_${controller.material.value}',
),
initialValue: controller.material.value,
decoration: const InputDecoration(
labelText: 'Material',
border: OutlineInputBorder(),
),
items: controller.materialien.map((item) {
return DropdownMenuItem(
value: item,
child: Text(item),
);
}).toList(),
onChanged: (value) {
if (value != null) {
controller.material.value = value;
}
},
)),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: TextFormField(
controller: controller.farbeController,
decoration: const InputDecoration(
labelText: 'Farbe',
border: OutlineInputBorder(),
),
validator: (value) =>
value == null || value.trim().isEmpty
? 'Farbe eingeben'
: null,
),
),
const SizedBox(width: 8),
Expanded(
child: TextFormField(
controller: controller.preisController,
keyboardType:
const TextInputType.numberWithOptions(
decimal: true,
),
decoration: const InputDecoration(
labelText: 'Preis (€)',
border: OutlineInputBorder(),
),
validator: (value) {
final price = double.tryParse(
(value ?? '').replaceAll(',', '.'),
);
if (price == null ||
!price.isFinite ||
price < 0) {
return 'Preis ungültig';
}
return null;
},
),
),
],
),
const SizedBox(height: 8),
TextFormField(
controller: controller.gewichtController,
decoration: const InputDecoration(
labelText: 'Gewicht in Gramm (g)',
border: OutlineInputBorder(),
),
validator: (value) =>
value == null || value.trim().isEmpty
? 'Gewicht eingeben g'
: null,
),
const SizedBox(height: 12),
Obx(() => Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: controller.saveFilament,
icon: Icon(
controller.editingId.value == null
? Icons.add
: Icons.save,
),
label: Text(
controller.editingId.value == null
? 'Filament speichern'
: 'Änderungen speichern',
),
),
),
if (controller.editingId.value != null)
IconButton(
tooltip: 'Bearbeitung abbrechen',
onPressed: controller.resetForm,
icon: const Icon(Icons.close),
),
],
)),
],
),
),
),
const Divider(),
// LISTENBEREICH
Expanded(
child: Obx(() {
final filamente = controller.filamente;
if (filamente.isEmpty) {
return const Center(
child: Text('Noch keine Filamente gespeichert'),
);
}
return ListView.builder(
itemCount: filamente.length,
itemBuilder: (context, index) {
final filament = filamente[index];
return Card(
margin: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
child: ListTile(
leading: const CircleAvatar(
child: Icon(Icons.inventory_2_outlined),
),
title: Text(
'${filament.material} – ${filament.farbe}',
),
subtitle: Text(
'${filament.haendler}\n'
'${dateFormat.format(filament.kaufdatum)}'
' | ${filament.preis.toStringAsFixed(2)} €',
),
isThreeLine: true,
onTap: () => Get.to(
() => AnwendungView(
filamentId: filament.filamentId,
),
),
trailing: PopupMenuButton<String>(
onSelected: (action) =>
_handleAction(context, action, filament),
itemBuilder: (context) => const [
PopupMenuItem(
value: 'edit',
child: ListTile(
leading: Icon(Icons.edit),
title: Text('Bearbeiten'),
),
),
PopupMenuItem(
value: 'duplicate',
child: ListTile(
leading: Icon(Icons.copy),
title: Text('Duplizieren'),
),
),
PopupMenuItem(
value: 'delete',
child: ListTile(
leading: Icon(Icons.delete_outline),
title: Text('Löschen'),
),
),
],
),
),
);
},
);
}),
),
],
),
);
}
Future<void> _handleAction(
BuildContext context,
String action,
KaufFilamentModel filament,
) async {
switch (action) {
case 'edit':
controller.editFilament(filament);
break;
case 'duplicate':
await controller.duplicateFilament(filament);
break;
case 'delete':
final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Filament löschen?'),
content: Text(
'${filament.material} – ${filament.farbe} löschen?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Abbrechen'),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Löschen'),
),
],
),
);
if (confirmed == true) {
await controller.deleteFilament(filament.filamentId);
}
break;
}
}
}