From c0608d9cfff0f71616397e0e324a7ef2d864837f Mon Sep 17 00:00:00 2001 From: reinjens Date: Tue, 19 Nov 2024 17:02:35 +0100 Subject: [PATCH] Detail Page -> responsive, collapsible, dynamic TOC, only display filled properties --- i18n/de.json | 41 +- lib/main.dart | 10 +- .../packages/ambito_theme/ambito_theme.dart | 2 +- .../ambito_theme/ambito_theme_large.dart | 2 +- lib/src/pages/actions/actions_page.dart | 26 +- lib/src/pages/actions/actions_pre_page.dart | 2 +- .../actions/detail/action_detail_page.dart | 375 +++++++++++++----- .../pages/actions/detail/cards/_cards.dart | 5 + .../actions/detail/cards/_main_card.dart | 50 +++ .../actions/detail/cards/_sidebar_card.dart | 48 +++ .../actions/detail/cards/advisor_card.dart | 64 ++- .../actions/detail/cards/background_card.dart | 54 +-- .../actions/detail/cards/creation_card.dart | 136 +++++-- .../detail/cards/description_card.dart | 35 +- .../actions/detail/cards/factsheet_card.dart | 38 +- .../detail/cards/implementation_card.dart | 52 +++ .../actions/detail/cards/location_card.dart | 25 ++ .../detail/cards/maintenance_card.dart | 136 ++++++- .../actions/detail/cards/material_card.dart | 53 +-- .../actions/detail/cards/presets_card.dart | 43 +- .../actions/detail/cards/review_card.dart | 49 ++- .../pages/actions/detail/cards/size_card.dart | 25 ++ .../actions/detail/cards/sources_card.dart | 26 ++ .../pages/actions/detail/cards/toc_card.dart | 37 ++ lib/src/widgets/buttons/text_button.dart | 40 ++ pubspec.lock | 56 ++- pubspec.yaml | 4 + 27 files changed, 1066 insertions(+), 368 deletions(-) create mode 100644 lib/src/pages/actions/detail/cards/_main_card.dart create mode 100644 lib/src/pages/actions/detail/cards/_sidebar_card.dart create mode 100644 lib/src/pages/actions/detail/cards/implementation_card.dart create mode 100644 lib/src/pages/actions/detail/cards/location_card.dart create mode 100644 lib/src/pages/actions/detail/cards/size_card.dart create mode 100644 lib/src/pages/actions/detail/cards/sources_card.dart create mode 100644 lib/src/pages/actions/detail/cards/toc_card.dart create mode 100644 lib/src/widgets/buttons/text_button.dart diff --git a/i18n/de.json b/i18n/de.json index b6b2237..b390883 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -24,6 +24,14 @@ } }, "actionDetailPage": { + "tabs": { + "info": "Informationen", + "misc": "Beratung & Materialien" + }, + "expanded": { + "collapse_all": "Alle einklappen", + "expand_all": "Alle ausklappen" + }, "description": { "title": "Beschreibung" }, @@ -41,10 +49,22 @@ }, "creation":{ "title": "Anlage", - "timeFrame": "Zeitrahmen" + "timeFrame": "Zeitrahmen", + "additional": "Zusatzinformationen", + "reminders": "Beachtenswert", + "tools": "Arbeitsmittel", + "steps": "Arbeitsschritte", + "tips": "Tipps" }, "maintenance": { - "title": "Pflege" + "title": "Pflege", + "time": "Zeitrahmen", + "additional_time": "Zusatzinformationen", + "frequency": "Frequenz", + "additional_frequency": "Zusatzinformationen", + "tools": "Arbeitsmittel", + "steps": "Arbeitsschritte", + "tips": "Tipps" }, "funding": { "title": "Förderungen" @@ -59,6 +79,23 @@ "review": { "title": "Rezensionen" }, + "size": { + "title": "Größe / Fläche" + }, + "location": { + "title": "Standort" + }, + "sources": { + "title": "Quellen" + }, + "toc": { + "title": "Inhalt" + }, + "implementation": { + "title": "Fachgerechte Umsetzung", + "costs": "Kosten", + "time": "Zeit" + }, "material": { "title": "Material", "suggested_price": "Preis ca. ", diff --git a/lib/main.dart b/lib/main.dart index b59c152..e5dc835 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,7 +41,7 @@ const BreakpointConfiguration myBreakpoints = BreakpointConfiguration( breakpoint: 320, width: double.infinity, margin: 32, - padding: 0, + padding: 10, columns: 8, ), md: Breakpoint( @@ -179,6 +179,14 @@ class Ambito extends StatelessWidget { name: '/massnahmendatenbank', page: () => const ActionsPage(), ), + GetPage( + name: '/massnahmendatenbank/:kategorie', + page: () => const ActionsPage(), + ), + GetPage( + name: '/massnahmendatenbank/:kategorie/:typ', + page: () => const ActionsPage(), + ), GetPage( name: '/dashboard', page: () => DashboardPage( diff --git a/lib/src/packages/ambito_theme/ambito_theme.dart b/lib/src/packages/ambito_theme/ambito_theme.dart index 2250d13..6aa057a 100644 --- a/lib/src/packages/ambito_theme/ambito_theme.dart +++ b/lib/src/packages/ambito_theme/ambito_theme.dart @@ -72,7 +72,7 @@ abstract class AmbitoTheme { onPrimary: Color(0xFFFFFFFF), primaryContainer: Color(0xFFDCE3C9), onPrimaryContainer: Color(0xFF001E2D), - secondary: Color(0xFFF5F5F5), + secondary: Color(0xff87A34E), onSecondary: Color(0xFFFFFFFF), secondaryContainer: Color(0xFFD2E5F4), onSecondaryContainer: Color(0xFF0A1D28), diff --git a/lib/src/packages/ambito_theme/ambito_theme_large.dart b/lib/src/packages/ambito_theme/ambito_theme_large.dart index 76398d3..8428f72 100644 --- a/lib/src/packages/ambito_theme/ambito_theme_large.dart +++ b/lib/src/packages/ambito_theme/ambito_theme_large.dart @@ -58,7 +58,7 @@ class AmbitoThemeLarge extends AmbitoTheme { letterSpacing: 0.1, ), labelLarge: GoogleFonts.openSans( - fontSize: 32, + fontSize: 28, height: 1.42, fontWeight: FontWeight.w500, letterSpacing: 0.1, diff --git a/lib/src/pages/actions/actions_page.dart b/lib/src/pages/actions/actions_page.dart index 1eb66ec..ea5587a 100644 --- a/lib/src/pages/actions/actions_page.dart +++ b/lib/src/pages/actions/actions_page.dart @@ -13,7 +13,6 @@ import 'package:get/get.dart'; import 'package:highlight_text/highlight_text.dart'; import 'package:screen_breakpoints/screen_breakpoints.dart'; -import '../../../main.dart'; import '../../entity/entities.dart'; import '../../packages/ambito_theme/ambito_theme.dart'; @@ -41,8 +40,8 @@ class ActionsPageState extends State { List? selectedMonths; List massnahmen = []; Map words = {}; - String? preselectAreaType; - String? preselectMeasureType; + String preselectAreaType = ''; + String preselectMeasureType = ''; final TextEditingController _searchController = TextEditingController(); final FocusNode _searchFocusNode = FocusNode(); @@ -51,13 +50,22 @@ class ActionsPageState extends State { void initState() { ambitoFilterNotifier.addListener(_changeListener); - setState(() { - preselectAreaType = prefs.getString('selected_areaType'); - if (preselectAreaType != null && preselectAreaType!.isNotEmpty) { + preselectAreaType = Get.parameters['kategorie'] ?? ''; + if (preselectAreaType != '') { + setState(() { filterAreaType = preselectAreaType; - ambitoFilterNotifier.setFilter('areaType', preselectAreaType!); - } - }); + ambitoFilterNotifier.setFilter('areaType', preselectAreaType); + }); + } + + preselectMeasureType = Get.parameters['typ'] ?? ''; + if (preselectMeasureType != '') { + setState(() { + filterType = preselectMeasureType; + ambitoFilterNotifier.setFilter('actionType', preselectMeasureType); + }); + } + _initializeData(); super.initState(); } diff --git a/lib/src/pages/actions/actions_pre_page.dart b/lib/src/pages/actions/actions_pre_page.dart index cdd292a..419f869 100644 --- a/lib/src/pages/actions/actions_pre_page.dart +++ b/lib/src/pages/actions/actions_pre_page.dart @@ -40,7 +40,7 @@ class ActionsPrePageState extends State { InkWell( onTap: () { prefs.setString('selected_areaType', filter.name!).then((value) { - Get.toNamed('/massnahmendatenbank'); + Get.toNamed('/massnahmendatenbank/${filter.name}'); setState(() {}); }); }, diff --git a/lib/src/pages/actions/detail/action_detail_page.dart b/lib/src/pages/actions/detail/action_detail_page.dart index 11a9985..035d537 100644 --- a/lib/src/pages/actions/detail/action_detail_page.dart +++ b/lib/src/pages/actions/detail/action_detail_page.dart @@ -1,5 +1,9 @@ +import 'package:ambito/main.dart'; import 'package:ambito/src/entity/entities.dart'; import 'package:ambito/src/entity/measure/measure_repository.dart'; +import 'package:ambito/src/extensions/extensions.dart'; +import 'package:animated_segmented_tab_control/animated_segmented_tab_control.dart'; +import 'package:expansion_tile_card/expansion_tile_card.dart'; import 'package:flutter/material.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:get/get.dart'; @@ -10,6 +14,8 @@ import '../../../widgets/appbar/ambito_appbar.dart'; import '../../ambito_page.dart'; import 'cards/_cards.dart'; +final Map> globalKeys = {}; + class ActionDetailPage extends AmbitoPage { const ActionDetailPage({super.key}); @@ -23,34 +29,128 @@ class ActionDetailPage extends AmbitoPage { } class ActionDetailPageState extends State { - late String id; - - Widget content = const SizedBox(); + late final String id; + final ScrollController scrollController = ScrollController(); + bool showBackToTopButton = false; + final Map> expansionKeys = {}; Measure? massnahme; - List contentItems = []; - List sidebarItems = []; + final Set visibleItems = {}; + bool collapsed = false; @override void initState() { + super.initState(); + id = Get.parameters['id'] ?? ''; - if (id != '') { - setState(() { - massnahme = MeasureRepository().get(int.parse(id)) as Measure; - }); + if (id.isNotEmpty) { + massnahme = MeasureRepository().get(int.parse(id)) as Measure; } - super.initState(); + scrollController.addListener(() { + const showOffset = 10.0; + setState( + () => showBackToTopButton = scrollController.offset > showOffset); + }); } @override Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); return Scaffold( appBar: AmbitoAppbar( links: const ['dashboard', 'massnahmen'], breakpoint: Breakpoint.fromContext(context), ), - body: _buildInfoPage(context, massnahme), + body: (context.breakpoint != myBreakpoints.sm) + ? _buildInfoPage(context, massnahme) + : _tabbedInfoPage(context, massnahme), + floatingActionButton: (context.breakpoint != myBreakpoints.sm) + ? AnimatedOpacity( + duration: const Duration(milliseconds: 1000), + opacity: showBackToTopButton ? 1.0 : 0.0, + child: FloatingActionButton( + shape: const CircleBorder(), + elevation: 0, + onPressed: () => scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 500), + curve: Curves.fastOutSlowIn, + ), + backgroundColor: theme.currentColorScheme.primary, + child: Icon(Icons.arrow_upward, + color: theme.currentColorScheme.onPrimary), + ), + ) + : null, + ); + } + + Widget _tabbedInfoPage(BuildContext context, Measure? massnahme) { + final AmbitoTheme theme = getTheme(context); + + if (massnahme == null) { + return const Center(child: CircularProgressIndicator()); + } + + return Padding( + padding: context.breakpoint.padding, + child: DefaultTabController( + length: 2, + child: Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + massnahme.name!, + style: theme.currentThemeData.textTheme.headlineMedium, + ), + ), + theme.verticalSpacerSmall, + SegmentedTabControl( + tabTextColor: theme.currentColorScheme.secondary, + selectedTabTextColor: theme.currentColorScheme.onSecondary, + barDecoration: BoxDecoration( + color: theme.currentColorScheme.onSecondary, + border: Border.all( + width: 1.0, + color: theme.currentColorScheme.secondary, + ), + borderRadius: const BorderRadius.all( + Radius.circular( + 8.0, + ), + ), + ), + indicatorDecoration: + BoxDecoration(color: theme.currentColorScheme.secondary), + tabs: [ + SegmentTab( + label: + context.translate('page.actionDetailPage.tabs.info')), + SegmentTab( + label: + context.translate('page.actionDetailPage.tabs.misc')), + ], + ), + theme.verticalSpacerSmall, + Expanded( + child: TabBarView( + children: [ + SingleChildScrollView( + child: Column(children: _buildContentItems(massnahme)), + ), + SingleChildScrollView( + child: Column( + children: _buildSidebarItems(context, theme, massnahme), + ), + ), + ], + ), + ), + ], + ), + ), ); } @@ -58,54 +158,21 @@ class ActionDetailPageState extends State { final AmbitoTheme theme = getTheme(context); if (massnahme == null) { - return const Center( - child: CircularProgressIndicator(), - ); + return const Center(child: CircularProgressIndicator()); } + + contentItems = _buildContentItems(massnahme); + return Column( children: [ - Align( - alignment: Alignment.centerLeft, - child: BreadCrumb( - items: [ - BreadCrumbItem( - content: TextButton( - onPressed: () { - Get.offAndToNamed('/'); - }, - child: Text( - 'Start', - style: theme.bodyMedium, - ), - ), - ), - BreadCrumbItem( - content: TextButton( - onPressed: () { - Get.offAndToNamed('/massnahmendatenbank'); - }, - child: Text( - 'Massnahmen', - style: theme.bodyMedium, - ), - ), - ), - BreadCrumbItem( - content: Text( - massnahme.name ?? '', - ), - ), - ], - divider: const Icon(Icons.chevron_right), - ), - ), + _buildBreadcrumb(context, theme, massnahme), SizedBox( - width: double.infinity, - height: 300, - child: massnahme.getFullImage(), - ), + width: double.infinity, + height: 300, + child: massnahme.getFullImage()), Expanded( child: SingleChildScrollView( + controller: scrollController, child: SizedBox( width: 1152, child: Column( @@ -123,59 +190,14 @@ class ActionDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Column(children: [ - DescriptionCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - BackgroundCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - PresetsCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - BiodiverisityCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - CreationCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - MaintenanceCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - FundingCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - ]), + child: Column(children: contentItems), ), theme.horizontalSpacer, SizedBox( width: 300, child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - AdvisorCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - MaterialCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - FactsheetCard( - massnahme: massnahme, - ), - theme.verticalSpacer, - ReviewCard( - massnahme: massnahme, - ), - ], + children: + _buildSidebarItems(context, theme, massnahme), ), ), ], @@ -188,4 +210,155 @@ class ActionDetailPageState extends State { ], ); } + + Widget _buildBreadcrumb( + BuildContext context, AmbitoTheme theme, Measure massnahme) { + return Align( + alignment: Alignment.centerLeft, + child: BreadCrumb( + items: [ + BreadCrumbItem( + content: TextButton( + onPressed: () => Get.offAndToNamed('/'), + child: Text('Massnahmenkategorien', style: theme.bodyMedium), + ), + ), + BreadCrumbItem( + content: TextButton( + onPressed: () => Get.offAndToNamed('/massnahmendatenbank'), + child: Text('Maßnamentypen', style: theme.bodyMedium), + ), + ), + BreadCrumbItem( + content: TextButton( + onPressed: () => Get.offAndToNamed('/massnahmendatenbank'), + child: + Text(massnahme.actionGroup!.value!, style: theme.bodyMedium), + ), + ), + BreadCrumbItem(content: Text(massnahme.name ?? '')), + ], + divider: const Icon(Icons.chevron_right), + ), + ); + } + + List _buildContentItems(Measure massnahme) { + final AmbitoTheme theme = getTheme(context); + + List elements = []; + + void addCard( + {required Widget card, + required String titleKey, + required bool condition}) { + if (condition) { + visibleItems.add(context.translate(titleKey)); + elements.addAll([card, theme.verticalSpacer]); + } + } + + addCard( + card: DescriptionCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.description.title', + condition: massnahme.factsheetDefinition?.isNotEmpty ?? false, + ); + + addCard( + card: BackgroundCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.background.target', + condition: massnahme.factsheetTarget?.isNotEmpty ?? false, + ); + + addCard( + card: PresetsCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.presets.title', + condition: massnahme.remarkablePresets?.isNotEmpty ?? false, + ); + + addCard( + card: SizeCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.size.title', + condition: massnahme.factsheetSizeArea?.isNotEmpty ?? false, + ); + + addCard( + card: LocationCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.location.title', + condition: massnahme.factsheetLocation?.isNotEmpty ?? false, + ); + + addCard( + card: ImplementationCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.implementation.title', + condition: (massnahme.moneyAction?.isNotEmpty ?? false) && + (massnahme.timeAction?.isNotEmpty ?? false), + ); + + addCard( + card: CreationCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.creation.title', + condition: massnahme.timeFrame?.isNotEmpty ?? false, + ); + + addCard( + card: MaintenanceCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.maintenance.title', + condition: massnahme.timeMaintenance?.isNotEmpty ?? false, + ); + + addCard( + card: SourcesCard(massnahme: massnahme), + titleKey: 'page.actionDetailPage.sources.title', + condition: massnahme.sources?.isNotEmpty ?? false, + ); + + return [ + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + globalKeys.forEach((key, value) { + if (collapsed == false) { + value.currentState?.collapse(); + } else { + value.currentState?.expand(); + } + }); + setState(() { + collapsed = !collapsed; + }); + }, + child: Text( + context.translate((collapsed == false) + ? 'page.actionDetailPage.expanded.collapse_all' + : 'page.actionDetailPage.expanded.expand_all'), + style: theme.bodyLarge.copyWith( + color: theme.currentColorScheme.onSurface, + decoration: TextDecoration.underline, + ), + ), + ), + ), + theme.verticalSpacer, + ...elements, + ]; + } + + List _buildSidebarItems( + BuildContext context, AmbitoTheme theme, Measure massnahme) { + return [ + if (visibleItems.isNotEmpty && context.breakpoint != myBreakpoints.sm) + TocCard(massnahme: massnahme, items: visibleItems.toList()), + if (visibleItems.isNotEmpty && context.breakpoint != myBreakpoints.sm) + theme.verticalSpacer, + AdvisorCard(massnahme: massnahme), + theme.verticalSpacer, + MaterialCard(massnahme: massnahme), + theme.verticalSpacer, + FactsheetCard(massnahme: massnahme), + theme.verticalSpacer, + ReviewCard(massnahme: massnahme), + ]; + } } diff --git a/lib/src/pages/actions/detail/cards/_cards.dart b/lib/src/pages/actions/detail/cards/_cards.dart index 3e91212..97c2e1a 100644 --- a/lib/src/pages/actions/detail/cards/_cards.dart +++ b/lib/src/pages/actions/detail/cards/_cards.dart @@ -5,7 +5,12 @@ export 'creation_card.dart'; export 'description_card.dart'; export 'factsheet_card.dart'; export 'funding_card.dart'; +export 'implementation_card.dart'; +export 'location_card.dart'; export 'maintenance_card.dart'; export 'material_card.dart'; export 'presets_card.dart'; export 'review_card.dart'; +export 'size_card.dart'; +export 'sources_card.dart'; +export 'toc_card.dart'; diff --git a/lib/src/pages/actions/detail/cards/_main_card.dart b/lib/src/pages/actions/detail/cards/_main_card.dart new file mode 100644 index 0000000..2cc26fc --- /dev/null +++ b/lib/src/pages/actions/detail/cards/_main_card.dart @@ -0,0 +1,50 @@ +import 'package:ambito/src/extensions/extensions.dart'; +import 'package:ambito/src/pages/actions/detail/action_detail_page.dart'; +import 'package:expansion_tile_card/expansion_tile_card.dart'; +import 'package:flutter/material.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; + +class MainCard extends StatelessWidget { + const MainCard( + {super.key, + required this.massnahme, + required this.title, + required this.content}); + + final Measure massnahme; + final String title; + final Widget content; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + globalKeys.putIfAbsent(title, () => GlobalKey()); + + return ExpansionTileCard( + key: globalKeys[title], + initiallyExpanded: true, + contentPadding: const EdgeInsets.only(left: 16), + elevation: 0, + expandedTextColor: theme.currentColorScheme.outline, + baseColor: theme.currentColorScheme.outline.withOpacity(.1), + expandedColor: theme.currentColorScheme.outline.withOpacity(.1), + title: Text( + context.translate(title), + style: theme.currentThemeData.textTheme.labelMedium + ?.copyWith(fontWeight: FontWeight.bold), + ), + children: [ + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: content, + ), + ), + ], + ); + } +} diff --git a/lib/src/pages/actions/detail/cards/_sidebar_card.dart b/lib/src/pages/actions/detail/cards/_sidebar_card.dart new file mode 100644 index 0000000..e45e1ee --- /dev/null +++ b/lib/src/pages/actions/detail/cards/_sidebar_card.dart @@ -0,0 +1,48 @@ +import 'package:ambito/src/extensions/extensions.dart'; +import 'package:flutter/material.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; + +class SideBarCard extends StatelessWidget { + const SideBarCard( + {super.key, + required this.massnahme, + required this.title, + required this.content}); + + final Measure massnahme; + final String title; + final List content; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return SizedBox( + width: double.infinity, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 0, + color: greenColors['primary']!.withOpacity(.1), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.translate(title), + style: theme.currentThemeData.textTheme.labelMedium + ?.copyWith(color: theme.currentColorScheme.primary), + ), + theme.verticalSpacerSmall, + ...content + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/pages/actions/detail/cards/advisor_card.dart b/lib/src/pages/actions/detail/cards/advisor_card.dart index fe74cb4..0f3a841 100644 --- a/lib/src/pages/actions/detail/cards/advisor_card.dart +++ b/lib/src/pages/actions/detail/cards/advisor_card.dart @@ -1,4 +1,4 @@ -import 'package:ambito/src/extensions/extensions.dart'; +import 'package:ambito/src/pages/actions/detail/cards/_sidebar_card.dart'; import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:linkify/linkify.dart'; @@ -15,48 +15,30 @@ class AdvisorCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + return SideBarCard( + massnahme: massnahme, + title: 'page.actionDetailPage.advisor.title', + content: [ + Text( + 'Max Mustermann', + style: theme.currentThemeData.textTheme.bodyLarge + ?.copyWith(fontWeight: FontWeight.bold), ), - elevation: 0, - color: greenColors['primary']!.withOpacity(.1), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.translate('page.actionDetailPage.advisor.title'), - style: theme.currentThemeData.textTheme.labelMedium - ?.copyWith(color: theme.currentColorScheme.primary), - ), - theme.verticalSpacerSmall, - Text( - 'Max Mustermann', - style: theme.currentThemeData.textTheme.bodyMedium - ?.copyWith(fontWeight: FontWeight.bold), - ), - Linkify( - text: 'EMail: max@mustermann.de', - style: theme.currentThemeData.textTheme.bodyMedium, - linkifiers: const [ - EmailLinkifier(), - ], - ), - Linkify( - text: 'Tel: +4917666554433', - style: theme.currentThemeData.textTheme.bodyMedium, - linkifiers: const [ - PhoneNumberLinkifier(), - ], - ), - ], - ), + Linkify( + text: 'EMail: max@mustermann.de', + style: theme.currentThemeData.textTheme.bodyLarge, + linkifiers: const [ + EmailLinkifier(), + ], ), - ), + Linkify( + text: 'Tel: +4917666554433', + style: theme.currentThemeData.textTheme.bodyLarge, + linkifiers: const [ + PhoneNumberLinkifier(), + ], + ), + ], ); } } diff --git a/lib/src/pages/actions/detail/cards/background_card.dart b/lib/src/pages/actions/detail/cards/background_card.dart index ff188eb..f3eb005 100644 --- a/lib/src/pages/actions/detail/cards/background_card.dart +++ b/lib/src/pages/actions/detail/cards/background_card.dart @@ -1,9 +1,8 @@ -import 'package:ambito/src/extensions/extensions.dart'; -import 'package:expansion_tile_card/expansion_tile_card.dart'; import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; class BackgroundCard extends StatelessWidget { const BackgroundCard({super.key, required this.massnahme}); @@ -14,50 +13,15 @@ class BackgroundCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: ExpansionTileCard( - initiallyExpanded: true, - elevation: 0, - expandedTextColor: theme.currentColorScheme.outline, - baseColor: theme.currentColorScheme.secondary, - expandedColor: theme.currentColorScheme.secondary, - title: Text( - context.translate('page.actionDetailPage.background.title'), - style: theme.currentThemeData.textTheme.labelMedium, - ), + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.background.target', + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Align( - alignment: Alignment.topLeft, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context - .translate('page.actionDetailPage.background.areaType'), - style: theme.currentThemeData.textTheme.labelSmall, - ), - theme.verticalSpacerSmall, - Text( - massnahme.factsheetLocation.toString(), - style: theme.currentThemeData.textTheme.bodyMedium, - ), - theme.verticalSpacer, - Text( - context - .translate('page.actionDetailPage.background.target'), - style: theme.currentThemeData.textTheme.titleSmall, - ), - theme.verticalSpacerSmall, - Text( - massnahme.factsheetTarget.toString(), - style: theme.currentThemeData.textTheme.bodyMedium, - ), - ], - ), - ), + Text( + massnahme.factsheetTarget.toString(), + style: theme.currentThemeData.textTheme.bodyLarge, ), ], ), diff --git a/lib/src/pages/actions/detail/cards/creation_card.dart b/lib/src/pages/actions/detail/cards/creation_card.dart index 60226a2..9ac5e0f 100644 --- a/lib/src/pages/actions/detail/cards/creation_card.dart +++ b/lib/src/pages/actions/detail/cards/creation_card.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; class CreationCard extends StatelessWidget { const CreationCard({super.key, required this.massnahme}); @@ -13,40 +14,117 @@ class CreationCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), + Chip buildChip(String label) { + return Chip( + labelPadding: EdgeInsets.zero, + backgroundColor: theme.currentColorScheme.secondary, + label: Text( + label, + style: theme.currentThemeData.textTheme.bodyLarge + ?.copyWith(color: theme.currentColorScheme.onSecondary), ), - elevation: 0, - color: theme.currentColorScheme.secondary, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ); + } + + List buildChips(List? items) => + items + ?.where((item) => item?.isNotEmpty ?? false) + .map((item) => buildChip(item!)) + .toList() ?? + []; + + List buildTools(List? tools) => + tools?.where((item) => item?.isNotEmpty ?? false).map((item) { + return Row( children: [ - Text( - context.translate('page.actionDetailPage.creation.title'), - style: theme.currentThemeData.textTheme.titleMedium, - ), - theme.verticalSpacer, - Text( - context.translate('page.actionDetailPage.creation.timeFrame'), - style: theme.currentThemeData.textTheme.titleSmall, - ), - theme.verticalSpacer, - Text( - massnahme.timeFrame - ?.map((item) => item.value) - .toList() - .join(', ') ?? - '', - style: theme.currentThemeData.textTheme.bodyMedium, + const Icon(Icons.fiber_manual_record, size: 8), + theme.horizontalSpacerSmall, + Expanded( + child: Text( + item!, + style: theme.currentThemeData.textTheme.bodyLarge, + ), ), ], + ); + }).toList() ?? + []; + + Widget buildSection({ + required String titleKey, + required List children, + }) { + if (children.isEmpty) return const SizedBox.shrink(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider(), + Text( + context.translate(titleKey), + style: theme.currentThemeData.textTheme.labelMedium, ), - ), + ...children, + ], + ); + } + + Widget buildTextSection(String? text, String titleKey) { + if (text == null || text.isEmpty) return const SizedBox.shrink(); + return buildSection( + titleKey: titleKey, + children: [ + Text( + text, + style: theme.currentThemeData.textTheme.bodyLarge, + ), + ], + ); + } + + final tfChips = + buildChips(massnahme.timeFrame?.map((e) => e.value).toList()); + final creationTools = + buildTools(massnahme.attachmentMaterial?.map((e) => e.value).toList()); + + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.creation.title', + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (tfChips.isNotEmpty) + buildSection( + titleKey: 'page.actionDetailPage.creation.timeFrame', + children: [ + Wrap( + runSpacing: 8, + spacing: 8, + children: tfChips, + ), + ], + ), + buildTextSection( + massnahme.timeframeActionAdditional, + 'page.actionDetailPage.creation.additional', + ), + buildTextSection( + massnahme.attachmentReminder, + 'page.actionDetailPage.creation.reminders', + ), + if (creationTools.isNotEmpty) + buildSection( + titleKey: 'page.actionDetailPage.creation.tools', + children: creationTools, + ), + buildTextSection( + massnahme.attachmentWorkSteps, + 'page.actionDetailPage.creation.steps', + ), + buildTextSection( + massnahme.attachmentTip, + 'page.actionDetailPage.creation.tips', + ), + ], ), ); } diff --git a/lib/src/pages/actions/detail/cards/description_card.dart b/lib/src/pages/actions/detail/cards/description_card.dart index 6b252ef..dd015eb 100644 --- a/lib/src/pages/actions/detail/cards/description_card.dart +++ b/lib/src/pages/actions/detail/cards/description_card.dart @@ -1,5 +1,4 @@ -import 'package:ambito/src/extensions/extensions.dart'; -import 'package:expansion_tile_card/expansion_tile_card.dart'; +import 'package:ambito/src/pages/actions/detail/cards/_main_card.dart'; import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; @@ -14,31 +13,13 @@ class DescriptionCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: ExpansionTileCard( - initiallyExpanded: true, - elevation: 0, - expandedTextColor: theme.currentColorScheme.outline, - baseColor: theme.currentColorScheme.secondary, - expandedColor: theme.currentColorScheme.secondary, - title: Text( - context.translate('page.actionDetailPage.description.title'), - style: theme.currentThemeData.textTheme.labelMedium, - ), - children: [ - Align( - alignment: Alignment.topLeft, - child: Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), - child: Text( - massnahme.factsheetDefinition ?? '', - style: theme.currentThemeData.textTheme.bodyMedium, - textAlign: TextAlign.left, - ), - ), - ), - ], + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.description.title', + content: Text( + massnahme.factsheetDefinition ?? '', + style: theme.currentThemeData.textTheme.bodyLarge, + textAlign: TextAlign.left, ), ); } diff --git a/lib/src/pages/actions/detail/cards/factsheet_card.dart b/lib/src/pages/actions/detail/cards/factsheet_card.dart index f72d6e6..bc227ed 100644 --- a/lib/src/pages/actions/detail/cards/factsheet_card.dart +++ b/lib/src/pages/actions/detail/cards/factsheet_card.dart @@ -1,8 +1,9 @@ -import 'package:ambito/src/extensions/extensions.dart'; +import 'package:ambito/src/widgets/buttons/text_button.dart'; import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_sidebar_card.dart'; class FactsheetCard extends StatelessWidget { const FactsheetCard({super.key, required this.massnahme}); @@ -13,28 +14,23 @@ class FactsheetCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), + return SideBarCard( + massnahme: massnahme, + title: 'page.actionDetailPage.factsheet.title', + content: [ + Text( + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam', + style: theme.currentThemeData.textTheme.bodyLarge + ?.copyWith(fontWeight: FontWeight.bold), ), - elevation: 0, - color: theme.currentColorScheme.tertiary, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.translate('page.actionDetailPage.factsheet.title'), - style: theme.currentThemeData.textTheme.titleMedium, - ), - theme.verticalSpacer, - ], - ), + theme.verticalSpacer, + Align( + alignment: Alignment.center, + child: TextButtonElement( + title: 'page.actionDetailPage.factsheet.download', + onPressed: () {}), ), - ), + ], ); } } diff --git a/lib/src/pages/actions/detail/cards/implementation_card.dart b/lib/src/pages/actions/detail/cards/implementation_card.dart new file mode 100644 index 0000000..a81e062 --- /dev/null +++ b/lib/src/pages/actions/detail/cards/implementation_card.dart @@ -0,0 +1,52 @@ +import 'package:ambito/src/extensions/extensions.dart'; +import 'package:flutter/material.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; + +class ImplementationCard extends StatelessWidget { + const ImplementationCard({super.key, required this.massnahme}); + + final Measure massnahme; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.implementation.title', + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (massnahme.moneyAction != null && massnahme.moneyAction != '') + Text( + context.translate('page.actionDetailPage.implementation.costs'), + style: theme.currentThemeData.textTheme.labelMedium, + ), + if (massnahme.moneyAction != null && massnahme.moneyAction != '') + Text( + massnahme.moneyAction.toString(), + style: theme.currentThemeData.textTheme.bodyLarge, + ), + if (massnahme.moneyAction != null && + massnahme.moneyAction != '' && + massnahme.timeAction != null && + massnahme.timeAction != '') + const Divider(), + if (massnahme.timeAction != null && massnahme.timeAction != '') + Text( + context.translate('page.actionDetailPage.implementation.time'), + style: theme.currentThemeData.textTheme.labelMedium, + ), + if (massnahme.timeAction != null && massnahme.timeAction != '') + Text( + massnahme.timeAction.toString(), + style: theme.currentThemeData.textTheme.bodyLarge, + ), + ], + ), + ); + } +} diff --git a/lib/src/pages/actions/detail/cards/location_card.dart b/lib/src/pages/actions/detail/cards/location_card.dart new file mode 100644 index 0000000..040da27 --- /dev/null +++ b/lib/src/pages/actions/detail/cards/location_card.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; + +class LocationCard extends StatelessWidget { + const LocationCard({super.key, required this.massnahme}); + + final Measure massnahme; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.location.title', + content: Text( + massnahme.factsheetLocation.toString(), + style: theme.currentThemeData.textTheme.bodyLarge, + ), + ); + } +} diff --git a/lib/src/pages/actions/detail/cards/maintenance_card.dart b/lib/src/pages/actions/detail/cards/maintenance_card.dart index 81b5f94..1d9a770 100644 --- a/lib/src/pages/actions/detail/cards/maintenance_card.dart +++ b/lib/src/pages/actions/detail/cards/maintenance_card.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; class MaintenanceCard extends StatelessWidget { const MaintenanceCard({super.key, required this.massnahme}); @@ -13,27 +14,130 @@ class MaintenanceCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), + Chip buildChip(String label) { + return Chip( + labelPadding: EdgeInsets.zero, + backgroundColor: theme.currentColorScheme.secondary, + label: Text( + label, + style: theme.currentThemeData.textTheme.bodyLarge + ?.copyWith(color: theme.currentColorScheme.onSecondary), ), - elevation: 0, - color: theme.currentColorScheme.secondary, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ); + } + + List buildChips(List? items) => + items + ?.where((item) => item?.isNotEmpty ?? false) + .map((item) => buildChip(item!)) + .toList() ?? + []; + + List buildTools(List? tools) => + tools?.where((item) => item?.isNotEmpty ?? false).map((item) { + return Row( children: [ - Text( - context.translate('page.actionDetailPage.maintenance.title'), - style: theme.currentThemeData.textTheme.titleMedium, + const Icon(Icons.fiber_manual_record, size: 8), + theme.horizontalSpacerSmall, + Expanded( + child: Text( + item!, + style: theme.currentThemeData.textTheme.bodyLarge, + ), ), - theme.verticalSpacer, ], + ); + }).toList() ?? + []; + + Widget buildSection({ + required String titleKey, + required List children, + }) { + if (children.isEmpty) return const SizedBox.shrink(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider(), + Text( + context.translate(titleKey), + style: theme.currentThemeData.textTheme.labelMedium, ), - ), + ...children, + ], + ); + } + + Widget buildTextSection(String? text, String titleKey) { + if (text == null || text.isEmpty) return const SizedBox.shrink(); + return buildSection( + titleKey: titleKey, + children: [ + Text( + text, + style: theme.currentThemeData.textTheme.bodyLarge, + ), + ], + ); + } + + final tfChips = + buildChips(massnahme.timeMaintenance?.map((e) => e.value).toList()); + final tfMaintenance = buildChips( + massnahme.frequencyMaintenance?.map((e) => e.value).toList()); + final creationTools = + buildTools(massnahme.maintenanceMaterial?.map((e) => e.value).toList()); + + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.maintenance.title', + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (tfChips.isNotEmpty) + buildSection( + titleKey: 'page.actionDetailPage.maintenance.time', + children: [ + Wrap( + runSpacing: 8, + spacing: 8, + children: tfChips, + ), + ], + ), + buildTextSection( + massnahme.timeMaintenanceAdditional, + 'page.actionDetailPage.maintenance.additional_time', + ), + if (tfMaintenance.isNotEmpty) + buildSection( + titleKey: 'page.actionDetailPage.maintenance.frequency', + children: [ + Wrap( + runSpacing: 8, + spacing: 8, + children: tfMaintenance, + ), + ], + ), + buildTextSection( + massnahme.frequencyMaintenanceAdditional, + 'page.actionDetailPage.maintenance.additional_frequency', + ), + if (creationTools.isNotEmpty) + buildSection( + titleKey: 'page.actionDetailPage.maintenance.tools', + children: creationTools, + ), + buildTextSection( + massnahme.workMaintenance, + 'page.actionDetailPage.maintenance.steps', + ), + buildTextSection( + massnahme.tipsMaintenance, + 'page.actionDetailPage.maintenance.tips', + ), + ], ), ); } diff --git a/lib/src/pages/actions/detail/cards/material_card.dart b/lib/src/pages/actions/detail/cards/material_card.dart index f8f4d60..88a10cb 100644 --- a/lib/src/pages/actions/detail/cards/material_card.dart +++ b/lib/src/pages/actions/detail/cards/material_card.dart @@ -1,8 +1,10 @@ import 'package:ambito/src/extensions/extensions.dart'; +import 'package:ambito/src/widgets/buttons/text_button.dart'; import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_sidebar_card.dart'; class MaterialCard extends StatelessWidget { const MaterialCard({super.key, required this.massnahme}); @@ -13,37 +15,26 @@ class MaterialCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - elevation: 0, - color: greenColors['primary']!.withOpacity(.1), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.translate('page.actionDetailPage.material.title'), - style: theme.currentThemeData.textTheme.labelMedium - ?.copyWith(color: theme.currentColorScheme.primary), - ), - theme.verticalSpacerSmall, - _getSortedMaterials(context), - theme.verticalSpacer, - if (massnahme.costsMaterial != null) - Text(context.translate( - 'page.actionDetailPage.material.suggested_price') + - massnahme.costsMaterial + - ' €'), - theme.verticalSpacer, - ], + return SideBarCard( + massnahme: massnahme, + title: 'page.actionDetailPage.material.title', + content: [ + _getSortedMaterials(context), + theme.verticalSpacer, + if (massnahme.costsMaterial != null) + Text(context + .translate('page.actionDetailPage.material.suggested_price') + + massnahme.costsMaterial + + ' €'), + theme.verticalSpacerSmall, + Align( + alignment: Alignment.center, + child: TextButtonElement( + title: 'page.actionDetailPage.material.add_to_basket', + onPressed: () {}, ), - ), - ), + ) + ], ); } @@ -70,7 +61,7 @@ class MaterialCard extends StatelessWidget { Expanded( child: Text( item, - style: theme.currentThemeData.textTheme.bodyMedium, + style: theme.currentThemeData.textTheme.bodyLarge, ), ) ], diff --git a/lib/src/pages/actions/detail/cards/presets_card.dart b/lib/src/pages/actions/detail/cards/presets_card.dart index 9cf7b2e..3b95c5d 100644 --- a/lib/src/pages/actions/detail/cards/presets_card.dart +++ b/lib/src/pages/actions/detail/cards/presets_card.dart @@ -1,8 +1,8 @@ -import 'package:ambito/src/extensions/extensions.dart'; import 'package:flutter/material.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; class PresetsCard extends StatelessWidget { const PresetsCard({super.key, required this.massnahme}); @@ -13,41 +13,12 @@ class PresetsCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), - ), - elevation: 0, - color: theme.currentColorScheme.secondary, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.translate('page.actionDetailPage.presets.title'), - style: theme.currentThemeData.textTheme.titleMedium, - ), - theme.verticalSpacer, - Text( - massnahme.remarkablePresets.toString(), - style: theme.currentThemeData.textTheme.bodyMedium, - ), - theme.verticalSpacer, - Text( - context.translate('page.actionDetailPage.presets.tips'), - style: theme.currentThemeData.textTheme.titleSmall, - ), - theme.verticalSpacerSmall, - Text( - massnahme.tipsPresets.toString(), - style: theme.currentThemeData.textTheme.bodyMedium, - ), - ], - ), - ), + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.presets.title', + content: Text( + massnahme.remarkablePresets.toString(), + style: theme.currentThemeData.textTheme.bodyLarge, ), ); } diff --git a/lib/src/pages/actions/detail/cards/review_card.dart b/lib/src/pages/actions/detail/cards/review_card.dart index 3f51991..f299d01 100644 --- a/lib/src/pages/actions/detail/cards/review_card.dart +++ b/lib/src/pages/actions/detail/cards/review_card.dart @@ -1,9 +1,11 @@ import 'package:ambito/src/extensions/extensions.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_rating/flutter_rating.dart'; import 'package:flutter_rating_stars/flutter_rating_stars.dart'; import '../../../../entity/entities.dart'; import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_sidebar_card.dart'; class ReviewCard extends StatelessWidget { const ReviewCard({super.key, required this.massnahme}); @@ -14,6 +16,49 @@ class ReviewCard extends StatelessWidget { Widget build(BuildContext context) { final AmbitoTheme theme = getTheme(context); + return SideBarCard( + massnahme: massnahme, + title: 'page.actionDetailPage.review.title', + content: [ + StarRating( + mainAxisAlignment: MainAxisAlignment.start, + size: 24, + allowHalfRating: true, + rating: 3.5, + filledIcon: Icons.star, + halfFilledIcon: Icons.star_half, + emptyIcon: Icons.star_border_outlined, + color: + Colors.yellow.shade800, // Color for filled and half-filled icons + borderColor: Colors.yellow.shade800, // Color for empty icons + onRatingChanged: null, + ), + theme.verticalSpacerSmall, + Text( + '21 Bewertungen', + style: theme.bodySmall, + ), + theme.verticalSpacer, + Text( + '"Sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam"', + style: theme.bodyLarge.copyWith(fontWeight: FontWeight.bold), + ), + Text( + 'Maxi Mustermann', + style: theme.bodyLarge.copyWith(fontWeight: FontWeight.bold), + ), + theme.verticalSpacer, + Text( + '"Lorem ipsum dolor sit amet, consetetur sadipscing elitr"', + style: theme.bodyLarge.copyWith(fontWeight: FontWeight.bold), + ), + Text( + 'Peter Parker', + style: theme.bodyLarge.copyWith(fontWeight: FontWeight.bold), + ), + ], + ); + return SizedBox( width: double.infinity, child: Card( @@ -56,8 +101,8 @@ class ReviewCard extends StatelessWidget { valueLabelPadding: const EdgeInsets.symmetric(vertical: 1, horizontal: 8), valueLabelMargin: const EdgeInsets.only(right: 8), - starOffColor: const Color(0xffe7e8ea), - starColor: Colors.grey.shade600, + starOffColor: Colors.yellow.withOpacity(.3), + starColor: Colors.yellow, ), ], ), diff --git a/lib/src/pages/actions/detail/cards/size_card.dart b/lib/src/pages/actions/detail/cards/size_card.dart new file mode 100644 index 0000000..5d1896b --- /dev/null +++ b/lib/src/pages/actions/detail/cards/size_card.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; +import '_main_card.dart'; + +class SizeCard extends StatelessWidget { + const SizeCard({super.key, required this.massnahme}); + + final Measure massnahme; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.size.title', + content: Text( + massnahme.factsheetSizeArea.toString(), + style: theme.currentThemeData.textTheme.bodyLarge, + ), + ); + } +} diff --git a/lib/src/pages/actions/detail/cards/sources_card.dart b/lib/src/pages/actions/detail/cards/sources_card.dart new file mode 100644 index 0000000..be22a48 --- /dev/null +++ b/lib/src/pages/actions/detail/cards/sources_card.dart @@ -0,0 +1,26 @@ +import 'package:ambito/src/pages/actions/detail/cards/_main_card.dart'; +import 'package:flutter/material.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; + +class SourcesCard extends StatelessWidget { + const SourcesCard({super.key, required this.massnahme}); + + final Measure massnahme; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return MainCard( + massnahme: massnahme, + title: 'page.actionDetailPage.sources.title', + content: Text( + massnahme.sources ?? '', + style: theme.currentThemeData.textTheme.bodyLarge, + textAlign: TextAlign.left, + ), + ); + } +} diff --git a/lib/src/pages/actions/detail/cards/toc_card.dart b/lib/src/pages/actions/detail/cards/toc_card.dart new file mode 100644 index 0000000..05fac12 --- /dev/null +++ b/lib/src/pages/actions/detail/cards/toc_card.dart @@ -0,0 +1,37 @@ +import 'package:ambito/src/pages/actions/detail/cards/_sidebar_card.dart'; +import 'package:flutter/material.dart'; +import 'package:super_bullet_list/bullet_list.dart'; + +import '../../../../entity/entities.dart'; +import '../../../../packages/ambito_theme/ambito_theme.dart'; + +class TocCard extends StatelessWidget { + const TocCard({super.key, required this.massnahme, required this.items}); + + final Measure massnahme; + final List items; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return SideBarCard( + massnahme: massnahme, + title: 'page.actionDetailPage.toc.title', + content: [ + SuperBulletList( + gap: 2, + crossAxisMargin: 2, + iconSize: 24, + isOrdered: true, + items: items + .map((item) => Text( + item, + style: theme.bodyLarge, + )) + .toList(), + ), + ], + ); + } +} diff --git a/lib/src/widgets/buttons/text_button.dart b/lib/src/widgets/buttons/text_button.dart new file mode 100644 index 0000000..c53e936 --- /dev/null +++ b/lib/src/widgets/buttons/text_button.dart @@ -0,0 +1,40 @@ +import 'package:ambito/src/extensions/extensions.dart'; +import 'package:flutter/material.dart'; + +import '../../packages/ambito_theme/ambito_theme.dart'; + +class TextButtonElement extends StatelessWidget { + const TextButtonElement( + {super.key, required this.title, required this.onPressed}); + + final String title; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + final AmbitoTheme theme = getTheme(context); + + return TextButton( + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all(theme.currentColorScheme.secondary), + padding: WidgetStateProperty.all( + const EdgeInsets.all(16), + ), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8.0, + ), + ))), + onPressed: onPressed, + child: Text( + context.translate(title), + style: theme.bodyLarge.copyWith( + fontWeight: FontWeight.bold, + color: theme.currentColorScheme.onPrimary, + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index ee9f7f3..e19a467 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.7.0" + animated_segmented_tab_control: + dependency: "direct main" + description: + name: animated_segmented_tab_control + sha256: "4c04f5510037b1c30dbed00efae77559cc4e11f0fe2207c2358252d1870e4ab1" + url: "https://pub.dev" + source: hosted + version: "2.0.0" args: dependency: transitive description: @@ -118,6 +126,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.2" + bulleted_list: + dependency: "direct main" + description: + name: bulleted_list + sha256: "35ddc56b7fd9977ae3932264f5b722e702748a044aa16f0a7109bf7e4886fedb" + url: "https://pub.dev" + source: hosted + version: "0.0.1+0.1a" cached_network_image: dependency: "direct main" description: @@ -154,10 +170,10 @@ packages: dependency: transitive description: name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -334,6 +350,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + flextras: + dependency: transitive + description: + name: flextras + sha256: e73b5c86dd9419569d2a48db470059b41b496012513e4e1bdc56ba2c661048d9 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter: dependency: "direct main" description: flutter @@ -432,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_rating: + dependency: "direct main" + description: + name: flutter_rating + sha256: "207bcd4a276585b8a0771a5ac03c0f3cdb27490e79a609f9a483d9794fe630b7" + url: "https://pub.dev" + source: hosted + version: "2.0.2" flutter_rating_stars: dependency: "direct main" description: @@ -458,6 +490,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + gap: + dependency: transitive + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" geolocator: dependency: "direct main" description: @@ -910,10 +950,10 @@ packages: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "6b9cb54b7135073841a35513fba39e598b421702d5f4d92319992fd6eb5532a9" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.3+4" permission_handler_platform_interface: dependency: transitive description: @@ -1207,6 +1247,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + super_bullet_list: + dependency: "direct main" + description: + name: super_bullet_list + sha256: ffe4514582c4e5a0d58b16e2131b35c25f159629488670b655255c03960e0ee5 + url: "https://pub.dev" + source: hosted + version: "0.0.3" syncfusion_flutter_calendar: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c000c10..9ecb05f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,10 @@ dependencies: expansion_tile_card: ^3.0.0 flutter_linkify: ^6.0.0 linkify: ^5.0.0 + flutter_rating: ^2.0.2 + bulleted_list: ^0.0.1+0.1a + super_bullet_list: ^0.0.3 + animated_segmented_tab_control: ^2.0.0 dev_dependencies: