Detail Page -> responsive, collapsible, dynamic TOC, only display filled properties

This commit is contained in:
reinjens 2024-11-19 17:02:35 +01:00
parent 886d9dcc0e
commit c0608d9cff
27 changed files with 1066 additions and 368 deletions

View file

@ -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. ",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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';

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

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

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

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

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

View file

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

View file

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