diff --git a/lib/controller/detail_controller.dart b/lib/controller/detail_controller.dart new file mode 100644 index 0000000..7b55cad --- /dev/null +++ b/lib/controller/detail_controller.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; +import '../models/tank_model.dart'; + +class DetailController extends GetxController { + late TankModel tank; + + @override + void onInit() { + tank = Get.arguments as TankModel; + super.onInit(); + } +} diff --git a/lib/controller/home_controller.dart b/lib/controller/home_controller.dart index c5648eb..23c5926 100644 --- a/lib/controller/home_controller.dart +++ b/lib/controller/home_controller.dart @@ -2,6 +2,7 @@ import 'package:get/get.dart'; import '../models/tank_model.dart'; +import '../pages/detail_view.dart'; import '../services/appwrite_service.dart'; class HomeController extends GetxController { @@ -82,4 +83,8 @@ class HomeController extends GetxController { ); } } + + void viewTankDetails(TankModel tank) { + Get.toNamed(DetailPage.namedRoute, arguments: tank); + } } diff --git a/lib/helper/sample_bindings.dart b/lib/helper/sample_bindings.dart index 2ee4238..95a2c23 100644 --- a/lib/helper/sample_bindings.dart +++ b/lib/helper/sample_bindings.dart @@ -1,5 +1,6 @@ import 'package:get/get.dart'; +import '../controller/detail_controller.dart'; import '../controller/home_controller.dart'; import '../controller/login_controller.dart'; import '../controller/signin_controller.dart'; @@ -13,6 +14,7 @@ class SampleBindings extends Bindings { Get.lazyPut(() => LoginController()); Get.lazyPut(() => SigninController()); Get.lazyPut(() => HomeController()); + Get.lazyPut(() => DetailController()); } diff --git a/lib/helper/sample_routes.dart b/lib/helper/sample_routes.dart index d22f7f2..bf77658 100644 --- a/lib/helper/sample_routes.dart +++ b/lib/helper/sample_routes.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import '../pages/detail_view.dart'; import 'sample_bindings.dart'; import '../pages/home_view.dart'; import '../pages/signin_view.dart'; @@ -22,6 +23,11 @@ class SampleRouts { page: () => const HomePage(), binding: sampleBindings, ), + GetPage( + name: DetailPage.namedRoute, + page: () => const DetailPage(), + binding: sampleBindings, + ), ]; } diff --git a/lib/pages/detail_view.dart b/lib/pages/detail_view.dart new file mode 100644 index 0000000..87bc6f2 --- /dev/null +++ b/lib/pages/detail_view.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controller/detail_controller.dart'; +import '../widgets/detail_header_widget.dart'; +import '../widgets/detail_info_card_widget.dart'; +import '../widgets/detail_stat_widget.dart'; + +class DetailPage extends GetView { + static const String namedRoute = '/tank-detail-page'; + const DetailPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.blueGrey, + foregroundColor: Colors.white, + title: const Text('Tank Details'), + centerTitle: true, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Get.back(), + ), + ), + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.blueGrey[800]!, + Colors.blueGrey[600]!, + Colors.blueGrey[300]!, + Colors.blue[100]!, + ], + ), + ), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Card mit Datum und Gesamtpreis + DetailHeaderWidget(tank: controller.tank), + + const SizedBox(height: 16), + + // Hauptinformationen + DetailInfoCardWidget( + title: 'Tankstelleninformationen', + children: [ + DetailStatWidget( + icon: Icons.location_on, + label: 'Standort', + value: controller.tank.szLocation.isNotEmpty + ? controller.tank.szLocation + : 'Nicht angegeben', + iconColor: Colors.red, + ), + const SizedBox(height: 16), + DetailStatWidget( + icon: Icons.calendar_today, + label: 'Datum', + value: controller.tank.szDate, + iconColor: Colors.blueGrey, + ), + ], + ), + + const SizedBox(height: 16), + + // Tankdaten + DetailInfoCardWidget( + title: 'Tankdaten', + children: [ + DetailStatWidget( + icon: Icons.local_gas_station, + label: 'Liter getankt', + value: '${controller.tank.szLiters} L', + iconColor: Colors.orange, + valueSize: 24, + valueWeight: FontWeight.bold, + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: DetailStatWidget( + icon: Icons.euro, + label: 'Preis pro Liter', + value: '${controller.tank.szPricePerLiter}€', + iconColor: Colors.green, + ), + ), + const SizedBox(width: 16), + Expanded( + child: DetailStatWidget( + icon: Icons.receipt, + label: 'Gesamtpreis', + value: '${controller.tank.szPriceTotal}€', + iconColor: Colors.green[700]!, + valueWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ), + + const SizedBox(height: 16), + + // Fahrzeugdaten + DetailInfoCardWidget( + title: 'Fahrzeugdaten', + children: [ + DetailStatWidget( + icon: Icons.speed, + label: 'Kilometerstand', + value: '${controller.tank.szOdometer} km', + iconColor: Colors.blue, + valueSize: 24, + valueWeight: FontWeight.bold, + ), + ], + ), + + const SizedBox(height: 24), + + // Aktionsbuttons + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + // Bearbeiten Funktion + }, + icon: const Icon(Icons.edit), + label: const Text('Bearbeiten'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueGrey[700], + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton.icon( + onPressed: () { + // Löschen Funktion + }, + icon: const Icon(Icons.delete), + label: const Text('Löschen'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[700], + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/home_view.dart b/lib/pages/home_view.dart index e216320..15aa52a 100644 --- a/lib/pages/home_view.dart +++ b/lib/pages/home_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controller/home_controller.dart'; +import '../widgets/my_styled_loading_indicator.dart'; class HomePage extends GetView { static const String namedRoute = '/home-page'; @@ -31,26 +32,178 @@ class HomePage extends GetView { ), ], ), - body: Obx( - () => homCtrl.isLoading.value == false - ? ListView.builder( - itemBuilder: (context, index) { - var tank = homCtrl.listTankModel[index]; - return ListTile( - title: Text( - '${tank.szDate} - ${tank.szLiters}L - ${tank.szPricePerLiter}€/L', - ), - subtitle: Text( - 'Total: ${tank.szPriceTotal}€ - Odometer: ${tank.szOdometer}km', - ), - ); - }, - itemCount: homCtrl.listTankModel.length, - ) - : Center(child: CircularProgressIndicator()), + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.blueGrey[800]!, + Colors.blueGrey[600]!, + Colors.blueGrey[300]!, + Colors.blue[100]!, + ], + ), + ), + child: Obx( + () => homCtrl.isLoading.value == false + ? homCtrl.listTankModel.isEmpty + ? MyStyledLoadingIndicator() + : ListView.builder( + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + var tank = homCtrl.listTankModel[index]; + return Card( + elevation: 2, + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + // Tap action if needed + homCtrl.viewTankDetails(tank); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all( + 8, + ), + decoration: BoxDecoration( + color: Colors.blueGrey[50], + borderRadius: + BorderRadius.circular( + 8, + ), + ), + child: Icon( + Icons.local_gas_station, + color: Colors.blueGrey[700], + size: 24, + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + tank.szDate, + style: const TextStyle( + fontSize: 16, + fontWeight: + FontWeight.w600, + ), + ), + const SizedBox(height: 2), + Text( + '${tank.szLiters} Liter', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + Container( + padding: + const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.green[50], + borderRadius: + BorderRadius.circular(20), + ), + child: Text( + '${tank.szPriceTotal}€', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.green[700], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Divider( + color: Colors.grey[300], + height: 1, + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + _buildInfoChip( + icon: Icons.euro, + label: 'Preis/L', + value: '${tank.szPricePerLiter}€', + ), + _buildInfoChip( + icon: Icons.speed, + label: 'Km-Stand', + value: '${tank.szOdometer} km', + ), + ], + ), + ], + ), + ), + ), + ); + }, + itemCount: homCtrl.listTankModel.length, + ) + : Center( + child: CircularProgressIndicator(color: Colors.blueGrey), + ), + ), ), ), ), ); } + + Widget _buildInfoChip({ + required IconData icon, + required String label, + required String value, + }) { + return Row( + children: [ + Icon(icon, size: 16, color: Colors.blueGrey[600]), + const SizedBox(width: 4), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle(fontSize: 11, color: Colors.grey[600]), + ), + Text( + value, + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600), + ), + ], + ), + ], + ); + } } diff --git a/lib/widgets/detail_header_widget.dart b/lib/widgets/detail_header_widget.dart new file mode 100644 index 0000000..fa1ffb1 --- /dev/null +++ b/lib/widgets/detail_header_widget.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import '../models/tank_model.dart'; + +class DetailHeaderWidget extends StatelessWidget { + final TankModel tank; + + const DetailHeaderWidget({super.key, required this.tank}); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.green[600]!, Colors.green[800]!], + ), + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Tankvorgang', + style: TextStyle( + color: Colors.white.withAlpha(230), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + tank.szDate, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Gesamtpreis', + style: TextStyle( + color: Colors.white.withAlpha(230), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + '${tank.szPriceTotal}€', + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/detail_info_card_widget.dart b/lib/widgets/detail_info_card_widget.dart new file mode 100644 index 0000000..11715b0 --- /dev/null +++ b/lib/widgets/detail_info_card_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +class DetailInfoCardWidget extends StatelessWidget { + final String title; + final List children; + + const DetailInfoCardWidget({ + super.key, + required this.title, + required this.children, + }); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 4, + height: 20, + decoration: BoxDecoration( + color: Colors.blueGrey[700], + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blueGrey[800], + ), + ), + ], + ), + const SizedBox(height: 16), + ...children, + ], + ), + ), + ); + } +} diff --git a/lib/widgets/detail_stat_widget.dart b/lib/widgets/detail_stat_widget.dart new file mode 100644 index 0000000..482c6c0 --- /dev/null +++ b/lib/widgets/detail_stat_widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +class DetailStatWidget extends StatelessWidget { + final IconData icon; + final String label; + final String value; + final Color iconColor; + final double valueSize; + final FontWeight valueWeight; + + const DetailStatWidget({ + super.key, + required this.icon, + required this.label, + required this.value, + this.iconColor = Colors.blueGrey, + this.valueSize = 18, + this.valueWeight = FontWeight.w600, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: iconColor.withAlpha(100), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: iconColor, size: 28), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 13, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + value, + style: TextStyle( + fontSize: valueSize, + fontWeight: valueWeight, + color: Colors.grey[900], + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/widgets/my_styled_loading_indicator.dart b/lib/widgets/my_styled_loading_indicator.dart new file mode 100644 index 0000000..62e8782 --- /dev/null +++ b/lib/widgets/my_styled_loading_indicator.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class MyStyledLoadingIndicator extends StatelessWidget { + const MyStyledLoadingIndicator({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.local_gas_station_outlined, + size: 80, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'Keine Tankeinträge vorhanden', + style: TextStyle( + fontSize: 18, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } +} \ No newline at end of file