Detail Page -> responsive, collapsible, dynamic TOC, only display filled properties
This commit is contained in:
parent
886d9dcc0e
commit
c0608d9cff
27 changed files with 1066 additions and 368 deletions
41
i18n/de.json
41
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. ",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<ActionsPage> {
|
|||
List<String>? selectedMonths;
|
||||
List massnahmen = [];
|
||||
Map<String, HighlightedWord> 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<ActionsPage> {
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class ActionsPrePageState extends State<ActionsPrePage> {
|
|||
InkWell(
|
||||
onTap: () {
|
||||
prefs.setString('selected_areaType', filter.name!).then((value) {
|
||||
Get.toNamed('/massnahmendatenbank');
|
||||
Get.toNamed('/massnahmendatenbank/${filter.name}');
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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<String, GlobalKey<ExpansionTileCardState>> globalKeys = {};
|
||||
|
||||
class ActionDetailPage extends AmbitoPage {
|
||||
const ActionDetailPage({super.key});
|
||||
|
||||
|
@ -23,34 +29,128 @@ class ActionDetailPage extends AmbitoPage {
|
|||
}
|
||||
|
||||
class ActionDetailPageState extends State<ActionDetailPage> {
|
||||
late String id;
|
||||
|
||||
Widget content = const SizedBox();
|
||||
late final String id;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
bool showBackToTopButton = false;
|
||||
final Map<String, GlobalKey<ExpansionTileCardState>> expansionKeys = {};
|
||||
Measure? massnahme;
|
||||
|
||||
List<Widget> contentItems = [];
|
||||
List<Widget> sidebarItems = [];
|
||||
final Set<String> 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<ActionDetailPage> {
|
|||
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>[
|
||||
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<ActionDetailPage> {
|
|||
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<ActionDetailPage> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
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<Widget> _buildContentItems(Measure massnahme) {
|
||||
final AmbitoTheme theme = getTheme(context);
|
||||
|
||||
List<Widget> 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<Widget> _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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
50
lib/src/pages/actions/detail/cards/_main_card.dart
Normal file
50
lib/src/pages/actions/detail/cards/_main_card.dart
Normal file
|
@ -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<ExpansionTileCardState>());
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
48
lib/src/pages/actions/detail/cards/_sidebar_card.dart
Normal file
48
lib/src/pages/actions/detail/cards/_sidebar_card.dart
Normal file
|
@ -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<Widget> 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
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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<Chip> buildChips(List<String?>? items) =>
|
||||
items
|
||||
?.where((item) => item?.isNotEmpty ?? false)
|
||||
.map((item) => buildChip(item!))
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
List<Widget> buildTools(List<String?>? 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<Widget> 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',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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: () {}),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
52
lib/src/pages/actions/detail/cards/implementation_card.dart
Normal file
52
lib/src/pages/actions/detail/cards/implementation_card.dart
Normal file
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
25
lib/src/pages/actions/detail/cards/location_card.dart
Normal file
25
lib/src/pages/actions/detail/cards/location_card.dart
Normal file
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<Chip> buildChips(List<String?>? items) =>
|
||||
items
|
||||
?.where((item) => item?.isNotEmpty ?? false)
|
||||
.map((item) => buildChip(item!))
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
List<Widget> buildTools(List<String?>? 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<Widget> 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',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
25
lib/src/pages/actions/detail/cards/size_card.dart
Normal file
25
lib/src/pages/actions/detail/cards/size_card.dart
Normal file
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
26
lib/src/pages/actions/detail/cards/sources_card.dart
Normal file
26
lib/src/pages/actions/detail/cards/sources_card.dart
Normal file
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
37
lib/src/pages/actions/detail/cards/toc_card.dart
Normal file
37
lib/src/pages/actions/detail/cards/toc_card.dart
Normal file
|
@ -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<String> 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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
40
lib/src/widgets/buttons/text_button.dart
Normal file
40
lib/src/widgets/buttons/text_button.dart
Normal file
|
@ -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<EdgeInsets>(
|
||||
const EdgeInsets.all(16),
|
||||
),
|
||||
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
8.0,
|
||||
),
|
||||
))),
|
||||
onPressed: onPressed,
|
||||
child: Text(
|
||||
context.translate(title),
|
||||
style: theme.bodyLarge.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.currentColorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
56
pubspec.lock
56
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue