mockup and List View in a Modern Design

This commit is contained in:
Josef Seiringer 2026-01-14 14:15:37 +01:00
parent cc849be7c4
commit c065748225
10 changed files with 841 additions and 15 deletions

View File

@ -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
}
}

View File

@ -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 = <FilamentModel>[].obs;
final isLoadingFilament = false.obs;
@override
void onInit() {
_loadListFillament();
super.onInit();
}
@override
void onReady() {}
@override
void onClose() {}
Future<void> _loadListFillament() async {
isLoadingFilament(true);
if (filamentList.isNotEmpty) {
filamentList.clear();
}
filamentList(FilamentRepository.to.getAllFilaments());
isLoadingFilament(false);
}
void addNewFilament() {}
}

View File

@ -34,14 +34,16 @@ class FilamentRepository extends GetxService {
List<FilamentModel> 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;
}
}

View File

@ -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>(() => HomeController());
Get.lazyPut<ListController>(() => ListController());
}
}

View File

@ -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,
),
];

View File

@ -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<FilamentModel> 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,
),
];
}

226
lib/pages/list_view.dart Normal file
View File

@ -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<ListController> {
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'),
),
],
),
);
}
}

View File

@ -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,
),
),
],
],
),
),
);
}
}

View File

@ -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,
),
),
],
),
],
);
}
}

View File

@ -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<Color>(
Colors.deepPurple.shade600,
),
),
),
if (message != null) ...[
SizedBox(height: 24),
Text(
message!,
style: TextStyle(
fontSize: 16,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
],
],
),
);
}
}