diff --git a/lib/controllers/home_controller.dart b/lib/controllers/home_controller.dart index be786c3..9923726 100644 --- a/lib/controllers/home_controller.dart +++ b/lib/controllers/home_controller.dart @@ -1,10 +1,17 @@ import 'package:get/get.dart'; -class HomeController extends GetxController { +import '../pages/list_view.dart'; - void loadFilaments() {} +class HomeController extends GetxController { + void loadFilaments() { + // Navigiere zur Listenseite + Get.toNamed(ListPage.namedRoute); + } + void downloadFilaments() { + // JSON Backup file herunterladen + } - void downloadFilaments() {} - - void restoreFilaments() {} + void restoreFilaments() { + // JSON Backup file wiederherstellen + } } diff --git a/lib/controllers/list_controller.dart b/lib/controllers/list_controller.dart new file mode 100644 index 0000000..13aaf46 --- /dev/null +++ b/lib/controllers/list_controller.dart @@ -0,0 +1,31 @@ +import 'package:get/get.dart'; +import '../../model/filament_model.dart'; +import '../../helpers/filament_repository.dart'; + +class ListController extends GetxController { + final filamentList = [].obs; + final isLoadingFilament = false.obs; + + @override + void onInit() { + _loadListFillament(); + super.onInit(); + } + + @override + void onReady() {} + + @override + void onClose() {} + + Future _loadListFillament() async { + isLoadingFilament(true); + if (filamentList.isNotEmpty) { + filamentList.clear(); + } + filamentList(FilamentRepository.to.getAllFilaments()); + isLoadingFilament(false); + } + + void addNewFilament() {} +} diff --git a/lib/helpers/filament_repository.dart b/lib/helpers/filament_repository.dart index f907a7c..c8f4cf0 100644 --- a/lib/helpers/filament_repository.dart +++ b/lib/helpers/filament_repository.dart @@ -34,14 +34,16 @@ class FilamentRepository extends GetxService { List getAllFilaments() { try { final data = _storage.read(_storageKey); - if (data == null) return []; + //Wenn nichts gespeichert ist, gebe die Mockup Liste zurück + if (data == null) return FilamentModel.mockupFilamentList; return (data as List) .map((json) => FilamentModel.fromJson(json)) .toList(); } catch (e) { print('Error reading filaments: $e'); - return []; + // Bei Fehlerfall gebe die Mockup Liste zurück + return FilamentModel.mockupFilamentList; } } diff --git a/lib/helpers/sample_bindings.dart b/lib/helpers/sample_bindings.dart index a7d0e54..93b3cfe 100644 --- a/lib/helpers/sample_bindings.dart +++ b/lib/helpers/sample_bindings.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; import '../controllers/home_controller.dart'; +import '../controllers/list_controller.dart'; @@ -9,6 +10,7 @@ class SampleBindings extends Bindings { void dependencies() { // Define your dependencies here no permanent Binding Get.lazyPut(() => HomeController()); + Get.lazyPut(() => ListController()); } } \ No newline at end of file diff --git a/lib/helpers/sample_routes.dart b/lib/helpers/sample_routes.dart index 250003f..672ba50 100644 --- a/lib/helpers/sample_routes.dart +++ b/lib/helpers/sample_routes.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; -import '../pages/home_view.dart'; import 'sample_bindings.dart'; +import '../pages/home_view.dart'; +import '../pages/list_view.dart'; @@ -13,6 +14,11 @@ class SampleRouts { page: () => const HomePage(), binding: sampleBindings, ), + GetPage( + name: ListPage.namedRoute, + page: () => const ListPage(), + binding: sampleBindings, + ), ]; diff --git a/lib/model/filament_model.dart b/lib/model/filament_model.dart index 7f344b0..4e0be15 100644 --- a/lib/model/filament_model.dart +++ b/lib/model/filament_model.dart @@ -1,4 +1,4 @@ - +import 'package:intl/intl.dart'; class FilamentModel { final String id; @@ -8,8 +8,11 @@ class FilamentModel { final double weight; // in Gramm final double price; // Preis final String? manufacturer; - final DateTime? purchaseDate; + final String? purchaseDate; final String? notes; + final int? pices; + final int? printingTemp; + final int? bedTemp; FilamentModel({ required this.id, @@ -21,6 +24,9 @@ class FilamentModel { this.manufacturer, this.purchaseDate, this.notes, + this.pices, + this.printingTemp, + this.bedTemp, }); // JSON Serialisierung @@ -33,8 +39,11 @@ class FilamentModel { 'weight': weight, 'price': price, 'manufacturer': manufacturer, - 'purchaseDate': purchaseDate?.toIso8601String(), + 'purchaseDate': purchaseDate, 'notes': notes, + 'pices': pices, + 'printingTemp': printingTemp, + 'bedTemp': bedTemp, }; } @@ -48,10 +57,11 @@ class FilamentModel { weight: (json['weight'] as num).toDouble(), price: (json['price'] as num).toDouble(), manufacturer: json['manufacturer'] as String?, - purchaseDate: json['purchaseDate'] != null - ? DateTime.parse(json['purchaseDate'] as String) - : null, + purchaseDate: json['purchaseDate'] as String?, notes: json['notes'] as String?, + pices: json['pices'] as int?, + printingTemp: json['printingTemp'] as int?, + bedTemp: json['bedTemp'] as int?, ); } @@ -64,8 +74,11 @@ class FilamentModel { double? weight, double? price, String? manufacturer, - DateTime? purchaseDate, + String? purchaseDate, String? notes, + int? pices, + int? printingTemp, + int? bedTemp, }) { return FilamentModel( id: id ?? this.id, @@ -77,6 +90,59 @@ class FilamentModel { manufacturer: manufacturer ?? this.manufacturer, purchaseDate: purchaseDate ?? this.purchaseDate, notes: notes ?? this.notes, + pices: pices ?? this.pices, + printingTemp: printingTemp ?? this.printingTemp, + bedTemp: bedTemp ?? this.bedTemp, ); } + + static String formatDate(DateTime date) { + final DateFormat formatter = DateFormat('dd.MM.yyyy'); + return formatter.format(date); + } + + static List mockupFilamentList = [ + FilamentModel( + id: '1', + name: '3Djake ECO Filament', + type: 'PLA', + color: 'White', + weight: 1000.0, + price: 19.99, + manufacturer: '3Djake.at', + purchaseDate: formatDate(DateTime(2026, 1, 10)), + notes: 'Great quality filament for everyday printing.', + pices: 1, + printingTemp: 207, + bedTemp: 55, + ), + FilamentModel( + id: '2', + name: 'Geeetech', + type: 'PETG', + color: 'Black', + weight: 1000.0, + price: 9.99, + manufacturer: 'geeetech.com', + purchaseDate: formatDate(DateTime(2025, 10, 10)), + notes: 'Durable and strong, perfect for functional parts.', + pices: 8, + printingTemp: 207, + bedTemp: 55, + ), + FilamentModel( + id: '3', + name: 'Tinmorry', + type: 'ASA', + color: 'Black', + weight: 1000.0, + price: 16.01, + pices: 1, + manufacturer: 'tinmorry.com', + purchaseDate: formatDate(DateTime(2026, 1, 10)), + notes: 'Weather-resistant filament for outdoor use.', + printingTemp: 265, + bedTemp: 100, + ), + ]; } diff --git a/lib/pages/list_view.dart b/lib/pages/list_view.dart new file mode 100644 index 0000000..e707085 --- /dev/null +++ b/lib/pages/list_view.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/list_controller.dart'; +import '../widgets/filament_card.dart'; +import '../widgets/empty_state.dart'; +import '../widgets/modern_loading_indicator.dart'; + +class ListPage extends GetView { + static const String namedRoute = '/list-page'; + const ListPage({super.key}); + + @override + Widget build(BuildContext context) { + var listCtrl = controller; + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.blue.shade50, Colors.purple.shade50], + ), + ), + child: SafeArea( + child: Column( + children: [ + // Custom AppBar + Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + color: Colors.white.withAlpha(230), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(10), + blurRadius: 10, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + IconButton( + icon: Icon(Icons.arrow_back), + color: Colors.deepPurple.shade700, + onPressed: () => Get.back(), + ), + SizedBox(width: 8), + Icon( + Icons.view_list_rounded, + color: Colors.deepPurple.shade600, + size: 28, + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Filament Liste', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.deepPurple.shade700, + ), + ), + Obx( + () => Text( + '${listCtrl.filamentList.length} ${listCtrl.filamentList.length == 1 ? 'Eintrag' : 'Einträge'}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.deepPurple.shade600, + Colors.deepPurple.shade400, + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.deepPurple.shade300.withAlpha(100), + blurRadius: 8, + offset: Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => listCtrl.addNewFilament(), + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + child: Row( + children: [ + Icon(Icons.add, color: Colors.white, size: 20), + SizedBox(width: 6), + Text( + 'Neu', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + + // List Content + Expanded( + child: Obx(() { + if (listCtrl.isLoadingFilament.value) { + return ModernLoadingIndicator( + message: 'Filamente werden geladen...', + ); + } else if (listCtrl.filamentList.isEmpty) { + return EmptyState( + title: 'Keine Filamente vorhanden', + message: + 'Füge dein erstes Filament hinzu, um mit der Verwaltung zu beginnen.', + icon: Icons.inbox_outlined, + actionLabel: 'Erstes Filament hinzufügen', + onActionPressed: () => listCtrl.addNewFilament(), + ); + } else { + return ListView.builder( + padding: EdgeInsets.only(top: 8, bottom: 16), + itemCount: listCtrl.filamentList.length, + itemBuilder: (context, index) { + final filament = listCtrl.filamentList[index]; + return FilamentCard( + filament: filament, + onTap: () { + // Optional: Detail-Ansicht öffnen + }, + onEdit: () { + // Optional: Bearbeiten-Dialog öffnen + }, + onDelete: () { + // Optional: Lösch-Bestätigung anzeigen + _showDeleteConfirmation( + context, + listCtrl, + filament, + ); + }, + ); + }, + ); + } + }), + ), + ], + ), + ), + ), + ); + } + + void _showDeleteConfirmation( + BuildContext context, + ListController controller, + filament, + ) { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + title: Row( + children: [ + Icon(Icons.warning_amber_rounded, color: Colors.orange.shade600), + SizedBox(width: 12), + Text('Filament löschen?'), + ], + ), + content: Text( + 'Möchtest du "${filament.name}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.', + style: TextStyle(fontSize: 15), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('Abbrechen'), + ), + ElevatedButton( + onPressed: () { + // controller.deleteFilament(filament.id); + Navigator.pop(context); + Get.snackbar( + 'Gelöscht', + '${filament.name} wurde entfernt', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red.shade100, + colorText: Colors.red.shade900, + duration: Duration(seconds: 2), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red.shade600, + foregroundColor: Colors.white, + ), + child: Text('Löschen'), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/empty_state.dart b/lib/widgets/empty_state.dart new file mode 100644 index 0000000..92f9a56 --- /dev/null +++ b/lib/widgets/empty_state.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +class EmptyState extends StatelessWidget { + final String title; + final String message; + final IconData icon; + final VoidCallback? onActionPressed; + final String? actionLabel; + + const EmptyState({ + super.key, + required this.title, + required this.message, + this.icon = Icons.inbox_outlined, + this.onActionPressed, + this.actionLabel, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(32), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.blue.shade50, Colors.purple.shade50], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(15), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: Icon(icon, size: 80, color: Colors.deepPurple.shade300), + ), + SizedBox(height: 32), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.deepPurple.shade700, + ), + ), + SizedBox(height: 12), + Text( + message, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Colors.grey.shade600, + height: 1.5, + ), + ), + if (onActionPressed != null && actionLabel != null) ...[ + SizedBox(height: 32), + ElevatedButton.icon( + onPressed: onActionPressed, + icon: Icon(Icons.add), + label: Text(actionLabel!), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurple.shade600, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/widgets/filament_card.dart b/lib/widgets/filament_card.dart new file mode 100644 index 0000000..b6f8452 --- /dev/null +++ b/lib/widgets/filament_card.dart @@ -0,0 +1,345 @@ +import 'package:flutter/material.dart'; +import '../model/filament_model.dart'; + +class FilamentCard extends StatelessWidget { + final FilamentModel filament; + final VoidCallback? onTap; + final VoidCallback? onEdit; + final VoidCallback? onDelete; + + const FilamentCard({ + super.key, + required this.filament, + this.onTap, + this.onEdit, + this.onDelete, + }); + + Color _getColorFromString(String colorName) { + final colorMap = { + 'red': Colors.red, + 'blue': Colors.blue, + 'green': Colors.green, + 'yellow': Colors.yellow, + 'black': Colors.black, + 'white': Colors.white, + 'orange': Colors.orange, + 'purple': Colors.purple, + 'pink': Colors.pink, + 'grey': Colors.grey, + }; + + return colorMap[colorName.toLowerCase()] ?? Colors.grey; + } + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.white, Colors.blue.shade50], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(15), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(16), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header with color indicator and name + Row( + children: [ + // Color indicator + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: _getColorFromString(filament.color), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.grey.shade300, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: _getColorFromString( + filament.color, + ).withAlpha(100), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + ), + SizedBox(width: 12), + // Name and Type + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + filament.name, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.deepPurple.shade700, + ), + ), + SizedBox(height: 4), + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.deepPurple.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + filament.type, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.deepPurple.shade700, + ), + ), + ), + ], + ), + ), + // Action buttons + if (onEdit != null) + IconButton( + icon: Icon(Icons.edit_outlined), + color: Colors.blue.shade600, + onPressed: onEdit, + ), + if (onDelete != null) + IconButton( + icon: Icon(Icons.delete_outline), + color: Colors.red.shade400, + onPressed: onDelete, + ), + ], + ), + SizedBox(height: 16), + + // Details Grid + Wrap( + spacing: 12, + runSpacing: 12, + children: [ + if (filament.manufacturer != null) + _buildDetailChip( + icon: Icons.business, + label: filament.manufacturer!, + color: Colors.blue, + ), + _buildDetailChip( + icon: Icons.scale, + label: '${filament.weight}g', + color: Colors.green, + ), + _buildDetailChip( + icon: Icons.euro, + label: filament.price.toStringAsFixed(2), + color: Colors.orange, + ), + if (filament.pices != null && filament.pices! > 0) + _buildDetailChip( + icon: Icons.inventory_2, + label: '${filament.pices} Stk.', + color: Colors.purple, + ), + ], + ), + + // Temperature info + if (filament.printingTemp != null || filament.bedTemp != null) + Padding( + padding: EdgeInsets.only(top: 12), + child: Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.orange.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.orange.shade200, + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + if (filament.printingTemp != null) + _buildTempInfo( + icon: Icons.print, + label: 'Druck', + temp: '${filament.printingTemp}°C', + ), + if (filament.printingTemp != null && + filament.bedTemp != null) + Container( + width: 1, + height: 30, + color: Colors.orange.shade200, + ), + if (filament.bedTemp != null) + _buildTempInfo( + icon: Icons.bed, + label: 'Bett', + temp: '${filament.bedTemp}°C', + ), + ], + ), + ), + ), + + // Notes + if (filament.notes != null && filament.notes!.isNotEmpty) + Padding( + padding: EdgeInsets.only(top: 12), + child: Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.amber.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.amber.shade200, + width: 1, + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.note_alt_outlined, + size: 18, + color: Colors.amber.shade700, + ), + SizedBox(width: 8), + Expanded( + child: Text( + filament.notes!, + style: TextStyle( + fontSize: 13, + color: Colors.grey.shade700, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ), + + // Purchase date + if (filament.purchaseDate != null) + Padding( + padding: EdgeInsets.only(top: 8), + child: Row( + children: [ + Icon( + Icons.calendar_today, + size: 14, + color: Colors.grey.shade500, + ), + SizedBox(width: 6), + Text( + 'Gekauft: ${filament.purchaseDate}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildDetailChip({ + required IconData icon, + required String label, + required MaterialColor color, + }) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: color.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.shade200, width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 16, color: color.shade700), + SizedBox(width: 6), + Text( + label, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: color.shade700, + ), + ), + ], + ), + ); + } + + Widget _buildTempInfo({ + required IconData icon, + required String label, + required String temp, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: Colors.orange.shade700), + SizedBox(width: 6), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + Text( + temp, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.orange.shade800, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/widgets/modern_loading_indicator.dart b/lib/widgets/modern_loading_indicator.dart new file mode 100644 index 0000000..eff8947 --- /dev/null +++ b/lib/widgets/modern_loading_indicator.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +class ModernLoadingIndicator extends StatelessWidget { + final String? message; + + const ModernLoadingIndicator({super.key, this.message}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.blue.shade50, Colors.purple.shade50], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(15), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation( + Colors.deepPurple.shade600, + ), + ), + ), + if (message != null) ...[ + SizedBox(height: 24), + Text( + message!, + style: TextStyle( + fontSize: 16, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ], + ), + ); + } +}