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
+17
View File
@@ -0,0 +1,17 @@
import 'package:get/get.dart';
class AnwendungController extends GetxController {
final count = 0.obs;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {}
@override
void onClose() {}
}
+166
View File
@@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:uuid/uuid.dart';
import '../models/kauf_filament_model.dart';
import '../services/get_storage_services.dart';
class HomeController extends GetxController {
static const storageKey = 'kauf_filamente';
final storage = Get.find<GetStorageService>();
final uuid = const Uuid();
final filamente = <KaufFilamentModel>[].obs;
final formKey = GlobalKey<FormState>();
final haendlerController = TextEditingController();
final farbeController = TextEditingController();
final preisController = TextEditingController();
final markeController = TextEditingController();
final gewichtController = TextEditingController();
final kaufdatum = DateTime.now().obs;
final material = 'PLA'.obs;
final editingId = RxnString();
final materialien = [
'PLA',
'PLA+',
'PLA light',
'PLA-CF',
'PLA+',
'PETG',
'PETG-CF',
'ABS',
'ASA',
'TPU',
'Sonstiges',
];
@override
void onInit() {
super.onInit();
loadFilamente();
}
void loadFilamente() {
final data = storage.getModelList(storageKey);
filamente.assignAll(
data.map(KaufFilamentModel.fromJson),
);
sortFilamente();
}
void sortFilamente() {
filamente.sort(
(a, b) => b.kaufdatum.compareTo(a.kaufdatum),
);
}
Future<void> persist() async {
await storage.saveModelList(
storageKey,
filamente.map((f) => f.toJson()).toList(),
);
}
Future<void> saveFilament() async {
if (!formKey.currentState!.validate()) return;
final preis = double.tryParse(
preisController.text.trim().replaceAll(',', '.'),
);
if (preis == null || !preis.isFinite || preis < 0) {
Get.snackbar('Ungültiger Preis', 'Bitte Preis überprüfen.');
return;
}
final filament = KaufFilamentModel(
filamentId: editingId.value ?? uuid.v4(),
marke: markeController.text.trim(),
kaufdatum: kaufdatum.value,
haendler: haendlerController.text.trim(),
material: material.value,
preis: preis,
farbe: farbeController.text.trim(),
gewicht: gewichtController.text.trim().isEmpty
? '1kg'
: gewichtController.text.trim(),
);
if (editingId.value == null) {
filamente.add(filament);
} else {
final index = filamente.indexWhere(
(f) => f.filamentId == editingId.value,
);
if (index < 0) {
Get.snackbar('Fehler', 'Datensatz nicht gefunden.');
return;
}
filamente[index] = filament;
}
sortFilamente();
await persist();
resetForm();
}
void editFilament(KaufFilamentModel filament) {
editingId.value = filament.filamentId;
kaufdatum.value = filament.kaufdatum;
markeController.text = filament.marke;
haendlerController.text = filament.haendler;
material.value = filament.material;
preisController.text = filament.preis.toStringAsFixed(2);
farbeController.text = filament.farbe;
gewichtController.text = filament.gewicht;
}
Future<void> duplicateFilament(
KaufFilamentModel filament,
) async {
filamente.add(
filament.copyWith(filamentId: uuid.v4()),
);
sortFilamente();
await persist();
}
Future<void> deleteFilament(String filamentId) async {
filamente.removeWhere((f) => f.filamentId == filamentId);
if (editingId.value == filamentId) resetForm();
await persist();
}
void resetForm() {
editingId.value = null;
haendlerController.clear();
markeController.clear();
farbeController.clear();
preisController.clear();
kaufdatum.value = DateTime.now();
material.value = 'PLA';
gewichtController.clear();
formKey.currentState?.reset();
}
@override
void onClose() {
haendlerController.dispose();
markeController.dispose();
farbeController.dispose();
preisController.dispose();
gewichtController.dispose();
super.onClose();
}
}
+11
View File
@@ -0,0 +1,11 @@
import 'package:get/get.dart';
import '../../controller/home_controller.dart';
import '../controller/anwendung_controller.dart';
class SampleBindings extends Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.lazyPut<AnwendungController>(() => AnwendungController());
}
}
+15
View File
@@ -0,0 +1,15 @@
import 'package:get/get.dart';
import '../pages/home_view.dart';
import 'sample_bindings.dart';
class SampleRouts {
static final sampleBindings = SampleBindings();
static List<GetPage<dynamic>> samplePages = [
GetPage(
name: HomeView.routeName,
page: () => const HomeView(),
binding: sampleBindings,
),
];
}
+34
View File
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'helper/sample_bindings.dart';
import 'helper/sample_routes.dart';
import 'pages/home_view.dart';
import 'package:get_storage/get_storage.dart';
import 'services/get_storage_services.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
Get.put<GetStorageService>(
GetStorageService(),
permanent: true,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Filament Verwaltung',
debugShowCheckedModeBanner: false,
theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: const Color.fromARGB(255, 38, 135, 184)), useMaterial3: true),
initialBinding: SampleBindings(),
initialRoute: HomeView.routeName,
getPages: SampleRouts.samplePages,
);
}
}
View File
+67
View File
@@ -0,0 +1,67 @@
class KaufFilamentModel {
final String filamentId;
final DateTime kaufdatum;
final String haendler;
final String marke;
final String material;
final double preis;
final String farbe;
final String gewicht;
const KaufFilamentModel({
required this.filamentId,
required this.kaufdatum,
required this.haendler,
required this.marke,
required this.material,
required this.preis,
required this.farbe,
required this.gewicht,
});
Map<String, dynamic> toJson() => {
'filamentId': filamentId,
'kaufdatum': kaufdatum.toIso8601String(),
'haendler': haendler,
'marke': marke,
'material': material,
'preis': preis,
'farbe': farbe,
'gewicht': gewicht,
};
factory KaufFilamentModel.fromJson(Map<String, dynamic> json) {
return KaufFilamentModel(
filamentId: json['filamentId'] as String,
kaufdatum: DateTime.parse(json['kaufdatum'] as String),
haendler: json['haendler'] as String,
marke: json['marke'] ?? '',
material: json['material'] as String,
preis: (json['preis'] as num).toDouble(),
farbe: json['farbe'] as String,
gewicht: json['gewicht'] ?? '1000'
);
}
KaufFilamentModel copyWith({
String? filamentId,
DateTime? kaufdatum,
String? haendler,
String? marke,
String? material,
double? preis,
String? farbe,
String? gewicht,
}) {
return KaufFilamentModel(
filamentId: filamentId ?? this.filamentId,
kaufdatum: kaufdatum ?? this.kaufdatum,
haendler: haendler ?? this.haendler,
marke: marke ?? this.marke,
material: material ?? this.material,
preis: preis ?? this.preis,
farbe: farbe ?? this.farbe,
gewicht: gewicht ?? this.gewicht
);
}
}
+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;
}
}
}
+91
View File
@@ -0,0 +1,91 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
class GetStorageService extends GetxService {
final GetStorage _box = GetStorage();
// CREATE / UPDATE
Future<void> save<T>(String key, T value) async {
await _box.write(key, value);
}
// READ
T? get<T>(String key) {
return _box.read<T>(key);
}
// READ mit Defaultwert
T getOrDefault<T>(String key, T defaultValue) {
return get<T>(key) ?? defaultValue;
}
// DELETE
Future<void> delete(String key) async {
await _box.remove(key);
}
// EXISTS
bool exists(String key) {
return _box.hasData(key);
}
// CLEAR ALL
Future<void> clear() async {
await _box.erase();
}
// READ LIST
List<T> getList<T>(String key) {
final data = _box.read<List<dynamic>>(key);
return data?.cast<T>() ?? <T>[];
}
// SAVE LIST
Future<void> saveList<T>(String key, List<T> items) async {
await _box.write(key, items);
}
// ADD ITEM
Future<void> addItem<T>(String key, T item) async {
final items = getList<T>(key);
items.add(item);
await saveList<T>(key, items);
}
// UPDATE ITEM
Future<void> updateItem<T>(String key, int index, T item) async {
final items = getList<T>(key);
if (index < 0 || index >= items.length) {
throw RangeError.index(index, items);
}
items[index] = item;
await saveList<T>(key, items);
}
// DELETE ITEM
Future<void> deleteItem<T>(String key, int index) async {
final items = getList<T>(key);
if (index < 0 || index >= items.length) {
throw RangeError.index(index, items);
}
items.removeAt(index);
await saveList<T>(key, items);
}
Future<void> saveModelList(
String key,
List<Map<String, dynamic>> items,
) async {
await _box.write(key, items);
}
List<Map<String, dynamic>> getModelList(String key) {
final data = _box.read<List<dynamic>>(key) ?? [];
return data.map((item) => Map<String, dynamic>.from(item as Map)).toList();
}
}