diff --git a/frontend/assets/bayern/body-logo.png b/frontend/assets/bayern/body-logo.png new file mode 100644 index 000000000..817add08a Binary files /dev/null and b/frontend/assets/bayern/body-logo.png differ diff --git a/frontend/assets/bayern/header-logo.png b/frontend/assets/bayern/header-logo.png new file mode 100644 index 000000000..6ce48ea16 Binary files /dev/null and b/frontend/assets/bayern/header-logo.png differ diff --git a/frontend/assets/nuernberg/background.png b/frontend/assets/nuernberg/background.png new file mode 100644 index 000000000..007ae9fe5 Binary files /dev/null and b/frontend/assets/nuernberg/background.png differ diff --git a/frontend/assets/nuernberg/body-logo.jpeg b/frontend/assets/nuernberg/body-logo.jpeg new file mode 100644 index 000000000..6f495761d Binary files /dev/null and b/frontend/assets/nuernberg/body-logo.jpeg differ diff --git a/frontend/assets/nuernberg/header-logo.png b/frontend/assets/nuernberg/header-logo.png new file mode 100644 index 000000000..ac958f9f0 Binary files /dev/null and b/frontend/assets/nuernberg/header-logo.png differ diff --git a/frontend/build-configs/bayern/index.ts b/frontend/build-configs/bayern/index.ts index bab10df32..a87a9b153 100644 --- a/frontend/build-configs/bayern/index.ts +++ b/frontend/build-configs/bayern/index.ts @@ -23,6 +23,26 @@ export const bayernCommon: CommonBuildConfigType = { showcase: "https://api.entitlementcard.app", local: "http://localhost:8000", }, + cardBranding: { + headerTextColor: "#008dc9", + headerColor: "#F5F5FFF5", + headerTitleLeft: "", + headerTitleRight: "Freistaat Bayern", + headerTextFontSize: 8, + headerLogo: "assets/bayern/header-logo.png", + headerLogoPadding: 4, + headerContainerPadding: {top: 0, right: 4, bottom: 0, left: 8}, + bodyContainerPadding: {top: 8, right: 8, bottom: 8, left: 8}, + bodyLogo: "assets/bayern/body-logo.png", + bodyLogoPosition: "center", + bodyLabel: "Bayerische Ehrenamtskarte", + bodyTextColor: "#172c82", + bodyBackgroundImage: false, + bodyBackgroundImageUrl: "", + colorStandard: "#cfeaff", + colorPremium: "#cab374", + boxDecorationRadius: 1, + }, featureFlags: {}, applicationUrl: "https://bayern.ehrenamtskarte.app/apply-for-eak", }; diff --git a/frontend/build-configs/nuernberg/index.ts b/frontend/build-configs/nuernberg/index.ts index 659e54bb1..45deecef7 100644 --- a/frontend/build-configs/nuernberg/index.ts +++ b/frontend/build-configs/nuernberg/index.ts @@ -23,6 +23,26 @@ export const nuernbergCommon: CommonBuildConfigType = { showcase: "https://api.entitlementcard.app", local: "http://localhost:7000", }, + cardBranding: { + headerTextColor: "#000000", + headerTextFontSize: 9, + headerColor: "#F9B787", + headerTitleLeft: "Amt für Existenzsicherung und soziale Integration - Sozialamt", + headerTitleRight: "", + headerLogo: "assets/nuernberg/header-logo.png", + headerLogoPadding: 0, + headerContainerPadding: {top: 0, right: 24, bottom: 0, left: 16}, + bodyContainerPadding: {top: 0, right: 24, bottom: 24, left: 16}, + bodyLogo: "assets/nuernberg/body-logo.jpeg", + bodyLogoPosition: "right", + bodyLabel: "Nürnberg-Pass", + bodyTextColor: "#000000", + bodyBackgroundImage: true, + bodyBackgroundImageUrl:"assets/nuernberg/background.png", + colorStandard: "#F9B787", + colorPremium: "#F9B787", + boxDecorationRadius: 0, + }, featureFlags: {}, applicationUrl: "https://meinkonto.nuernberg.de/intelliform/forms/osg/standard/osg/osg-kette-starten/index?lebenslageIdAuswahl=w_500_sha_d_nuernberg-pass", }; diff --git a/frontend/build-configs/types.ts b/frontend/build-configs/types.ts index 52d62ba58..883cf69a1 100644 --- a/frontend/build-configs/types.ts +++ b/frontend/build-configs/types.ts @@ -29,6 +29,26 @@ export type CommonBuildConfigType = { production: string, local: string, }, + cardBranding: { + headerTextColor: string, + headerColor: string, + headerTitleLeft: string, + headerTitleRight: string, + headerLogo: string, + headerLogoPadding: number, + headerContainerPadding: {top:number, left: number; bottom: number, right: number} + headerTextFontSize: number, + bodyTextColor: string, + colorPremium: string, + colorStandard: string, + bodyContainerPadding: {top:number, left: number; bottom: number, right: number} + bodyLogo: string, + bodyLogoPosition: string, + bodyLabel: string, + bodyBackgroundImage: boolean, + bodyBackgroundImageUrl: string + boxDecorationRadius: number, + } theme: ThemeType categories: number[] featureFlags: FeatureFlagsType diff --git a/frontend/lib/identification/card/card_content.dart b/frontend/lib/identification/card/card_content.dart new file mode 100644 index 000000000..452cad439 --- /dev/null +++ b/frontend/lib/identification/card/card_content.dart @@ -0,0 +1,174 @@ +import 'package:ehrenamtskarte/build_config/build_config.dart'; +import 'package:ehrenamtskarte/identification/base_card_details.dart'; +import 'package:ehrenamtskarte/identification/card/card_header_logo.dart'; +import 'package:ehrenamtskarte/util/color_utils.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +Color standardCardColor = getColorFromHex(buildConfig.cardBranding.colorStandard); +Color premiumCardColor = getColorFromHex(buildConfig.cardBranding.colorPremium); +Color textColor = getColorFromHex(buildConfig.cardBranding.bodyTextColor); +Color headerColor = getColorFromHex(buildConfig.cardBranding.headerColor); +String headerLogo = buildConfig.cardBranding.headerLogo; +String bodyLogo = buildConfig.cardBranding.bodyLogo; +String bodyLabel = buildConfig.cardBranding.bodyLabel; + +class PaddingStyle { + final double left; + final double right; + final double top; + final double bottom; + + PaddingStyle(this.left, this.right, this.top, this.bottom); +} + +PaddingStyle paddingBody = PaddingStyle( + buildConfig.cardBranding.bodyContainerPadding.left.toDouble(), + buildConfig.cardBranding.bodyContainerPadding.right.toDouble(), + buildConfig.cardBranding.bodyContainerPadding.top.toDouble(), + buildConfig.cardBranding.bodyContainerPadding.bottom.toDouble(), +); + +PaddingStyle paddingHeader = PaddingStyle( + buildConfig.cardBranding.headerContainerPadding.left.toDouble(), + buildConfig.cardBranding.headerContainerPadding.right.toDouble(), + buildConfig.cardBranding.headerContainerPadding.top.toDouble(), + buildConfig.cardBranding.headerContainerPadding.bottom.toDouble(), +); + +class Region with EquatableMixin { + final String prefix; + final String name; + + Region(this.prefix, this.name); + + @override + List get props => [prefix, name]; +} + +class CardContent extends StatelessWidget { + final BaseCardDetails cardDetails; + final Region? region; + + const CardContent({super.key, required this.cardDetails, this.region}); + + String get _formattedExpirationDate { + final expirationDate = cardDetails.expirationDate; + return expirationDate != null ? DateFormat('dd.MM.yyyy').format(expirationDate) : "unbegrenzt"; + } + + @override + Widget build(BuildContext context) { + final cardColor = cardDetails.cardType == CardType.gold ? premiumCardColor : standardCardColor; + return LayoutBuilder( + builder: (context, constraints) { + final scaleFactor = constraints.maxWidth / 300; + final currentRegion = region; + final headerLeftTitle = currentRegion != null + ? "${currentRegion.prefix} ${currentRegion.name}" + : buildConfig.cardBranding.headerTitleLeft; + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Header + ColoredBox( + color: headerColor, + child: Padding( + padding: EdgeInsets.only( + left: paddingHeader.left * scaleFactor, + right: paddingHeader.right * scaleFactor, + top: paddingHeader.top * scaleFactor, + bottom: paddingHeader.bottom * scaleFactor, + ), + child: AspectRatio( + aspectRatio: 6 / 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: CardHeaderLogo( + title: headerLeftTitle, + scaleFactor: scaleFactor, + alignment: CrossAxisAlignment.start, + ), + ), + Flexible( + child: CardHeaderLogo( + title: buildConfig.cardBranding.headerTitleRight, + scaleFactor: scaleFactor, + logo: Image(image: AssetImage(buildConfig.cardBranding.headerLogo), fit: BoxFit.contain), + alignment: CrossAxisAlignment.center, + ), + ), + ], + ), + ), + ), + ), + // Body + Flexible( + child: DecoratedBox( + decoration: BoxDecoration( + image: buildConfig.cardBranding.bodyBackgroundImage + ? DecorationImage( + image: AssetImage(buildConfig.cardBranding.bodyBackgroundImageUrl), + fit: BoxFit.fill, + ) + : null, + gradient: RadialGradient( + colors: [cardColor.withAlpha(100), cardColor], + radius: buildConfig.cardBranding.boxDecorationRadius.toDouble(), + ), + ), + child: Padding( + padding: EdgeInsets.only( + left: paddingBody.left * scaleFactor, + right: paddingBody.right * scaleFactor, + bottom: paddingBody.bottom * scaleFactor, + top: paddingBody.top * scaleFactor, + ), + child: Column( + children: [ + AspectRatio( + aspectRatio: 6 / 1.2, + child: Align( + alignment: buildConfig.cardBranding.bodyLogoPosition == 'center' + ? Alignment.center + : Alignment.centerRight, + child: Image(image: AssetImage(buildConfig.cardBranding.bodyLogo)), + ), + ), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + cardDetails.fullName, + style: TextStyle(fontSize: 14 * scaleFactor, color: textColor), + textAlign: TextAlign.start, + ), + RichText( + maxLines: 1, + text: TextSpan( + text: "Gültig bis: ", + style: TextStyle(fontSize: 12 * scaleFactor, color: textColor), + children: [TextSpan(text: _formattedExpirationDate)], + ), + ) + ], + ), + ), + ], + ), + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/frontend/lib/identification/card/card_header_logo.dart b/frontend/lib/identification/card/card_header_logo.dart new file mode 100644 index 000000000..1443076dc --- /dev/null +++ b/frontend/lib/identification/card/card_header_logo.dart @@ -0,0 +1,43 @@ +import 'package:ehrenamtskarte/build_config/build_config.dart'; +import 'package:ehrenamtskarte/util/color_utils.dart'; +import 'package:flutter/widgets.dart'; + +Color textColor = getColorFromHex(buildConfig.cardBranding.headerTextColor); +int fontSize = buildConfig.cardBranding.headerTextFontSize; +double logoPadding = buildConfig.cardBranding.headerLogoPadding.toDouble(); + +class CardHeaderLogo extends StatelessWidget { + final String title; + final Image? logo; + final double scaleFactor; + final CrossAxisAlignment alignment; + + const CardHeaderLogo({ + super.key, + required this.title, + this.logo, + required this.scaleFactor, + required this.alignment, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(logoPadding * scaleFactor), + child: Column( + crossAxisAlignment: alignment, + children: [ + Flexible( + child: logo ?? Container(), + ), + Text( + title, + maxLines: 3, + style: TextStyle(fontSize: fontSize * scaleFactor, color: textColor), + textAlign: TextAlign.start, + ) + ], + ), + ); + } +} diff --git a/frontend/lib/identification/card/eak_card.dart b/frontend/lib/identification/card/eak_card.dart deleted file mode 100644 index dce53eb5a..000000000 --- a/frontend/lib/identification/card/eak_card.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:ehrenamtskarte/identification/base_card_details.dart'; -import 'package:ehrenamtskarte/identification/card/eak_card_header_logo.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:intl/intl.dart'; - -const blueCardColor = Color(0xffcfeaff); -const goldenCardColor = Color(0xffcab374); -const textColor = Color(0xff172c82); - -class Region with EquatableMixin { - final String prefix; - final String name; - - Region(this.prefix, this.name); - - @override - List get props => [prefix, name]; -} - -class EakCard extends StatelessWidget { - final BaseCardDetails cardDetails; - final Region? region; - - const EakCard({super.key, required this.cardDetails, this.region}); - - String get _formattedExpirationDate { - final expirationDate = cardDetails.expirationDate; - return expirationDate != null ? DateFormat('dd.MM.yyyy').format(expirationDate) : "unbegrenzt"; - } - - @override - Widget build(BuildContext context) { - final cardColor = cardDetails.cardType == CardType.gold ? goldenCardColor : blueCardColor; - return LayoutBuilder( - builder: (context, constraints) { - final scaleFactor = constraints.maxWidth / 300; - final currentRegion = region; - final headerTitle = currentRegion != null ? "${currentRegion.prefix} ${currentRegion.name}" : ""; - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Header - ColoredBox( - color: const Color(0xf5f5f5ff), - child: AspectRatio( - aspectRatio: 6 / 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - EakCardHeaderLogo(title: headerTitle, scaleFactor: scaleFactor), - EakCardHeaderLogo( - title: "Freistaat Bayern", - scaleFactor: scaleFactor, - logo: const Image(image: AssetImage("assets/wappen-bavaria.png"), fit: BoxFit.contain), - ) - ], - ), - ), - ), - // Body - Flexible( - child: DecoratedBox( - decoration: - BoxDecoration(gradient: RadialGradient(colors: [cardColor.withAlpha(100), cardColor], radius: 1)), - child: Column( - children: [ - Padding( - padding: EdgeInsets.all(8 * scaleFactor), - child: AspectRatio( - aspectRatio: 6 / 1.2, - child: - SvgPicture.asset("assets/eak-lettering.svg", semanticsLabel: "Bayerische Ehrenamtskarte"), - ), - ), - Flexible( - child: Padding( - padding: EdgeInsets.all(8.0 * scaleFactor), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text( - cardDetails.fullName, - style: TextStyle(fontSize: 14 * scaleFactor, color: textColor), - textAlign: TextAlign.start, - ), - RichText( - maxLines: 1, - text: TextSpan( - text: "Gültig bis: ", - style: TextStyle(fontSize: 12 * scaleFactor, color: textColor), - children: [TextSpan(text: _formattedExpirationDate)], - ), - ) - ], - ), - ), - ) - ], - ), - ), - ) - ], - ); - }, - ); - } -} diff --git a/frontend/lib/identification/card/eak_card_header_logo.dart b/frontend/lib/identification/card/eak_card_header_logo.dart deleted file mode 100644 index 8e830a28e..000000000 --- a/frontend/lib/identification/card/eak_card_header_logo.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/widgets.dart'; - -const bavariaFontColor = Color(0xff008dc9); - -class EakCardHeaderLogo extends StatelessWidget { - final String title; - final Image? logo; - final double scaleFactor; - - const EakCardHeaderLogo({super.key, required this.title, this.logo, required this.scaleFactor}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.all(4.0 * scaleFactor), - child: Column( - children: [ - Flexible( - child: Padding( - padding: EdgeInsets.all(2 * scaleFactor), - child: logo ?? Container(), - ), - ), - Text(title, maxLines: 1, style: TextStyle(fontSize: 8 * scaleFactor, color: bavariaFontColor)) - ], - ), - ); - } -} diff --git a/frontend/lib/identification/card_detail_view/card_detail_view.dart b/frontend/lib/identification/card_detail_view/card_detail_view.dart index 414bce44b..439ea6183 100644 --- a/frontend/lib/identification/card_detail_view/card_detail_view.dart +++ b/frontend/lib/identification/card_detail_view/card_detail_view.dart @@ -1,6 +1,6 @@ import 'package:ehrenamtskarte/configuration/configuration.dart'; import 'package:ehrenamtskarte/graphql/graphql_api.dart'; -import 'package:ehrenamtskarte/identification/card/eak_card.dart'; +import 'package:ehrenamtskarte/identification/card/card_content.dart'; import 'package:ehrenamtskarte/identification/card/id_card.dart'; import 'package:ehrenamtskarte/identification/card_detail_view/more_actions_dialog.dart'; import 'package:ehrenamtskarte/identification/card_detail_view/verification_qr_code_view.dart'; @@ -42,7 +42,7 @@ class CardDetailView extends StatelessWidget { final eakCard = Padding( padding: const EdgeInsets.all(8.0), child: IdCard( - child: EakCard( + child: CardContent( cardDetails: cardDetails, region: region != null ? Region(region.prefix, region.name) : null, ), diff --git a/frontend/lib/util/color_utils.dart b/frontend/lib/util/color_utils.dart index 789606fd3..9c4a9bc2c 100644 --- a/frontend/lib/util/color_utils.dart +++ b/frontend/lib/util/color_utils.dart @@ -18,3 +18,8 @@ Color? getDarkenedColorForCategory(int categoryId) { } return categoryColorDark; } + +Color getColorFromHex(String hexColor) { + final hexCode = hexColor.replaceAll('#', ''); + return Color(int.parse('FF$hexCode', radix: 16)); +} diff --git a/frontend/lib/verification/dialogs/positive_verification_result_dialog.dart b/frontend/lib/verification/dialogs/positive_verification_result_dialog.dart index 33aa055dc..4530aede5 100644 --- a/frontend/lib/verification/dialogs/positive_verification_result_dialog.dart +++ b/frontend/lib/verification/dialogs/positive_verification_result_dialog.dart @@ -1,7 +1,7 @@ import 'package:ehrenamtskarte/configuration/configuration.dart'; import 'package:ehrenamtskarte/graphql/graphql_api.dart'; import 'package:ehrenamtskarte/identification/base_card_details.dart'; -import 'package:ehrenamtskarte/identification/card/eak_card.dart'; +import 'package:ehrenamtskarte/identification/card/card_content.dart'; import 'package:ehrenamtskarte/identification/card/id_card.dart'; import 'package:ehrenamtskarte/verification/dialogs/verification_result_dialog.dart'; import 'package:flutter/material.dart'; @@ -28,7 +28,7 @@ class PositiveVerificationResultDialog extends StatelessWidget { icon: Icons.verified_user, iconColor: Colors.green, child: IdCard( - child: EakCard( + child: CardContent( cardDetails: cardDetails, region: region != null ? Region(region.prefix, region.name) : null, ), diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 5183166ca..d7f21f275 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -101,11 +101,14 @@ flutter: assets: - assets/category_icons/ - assets/detail_headers/ - - assets/eak-lettering.svg - - assets/wappen-bavaria.png - assets/app_icon/ - assets/icon/ - assets/intro_slides/ + - assets/bayern/header-logo.png + - assets/bayern/body-logo.png + - assets/nuernberg/header-logo.png + - assets/nuernberg/body-logo.jpeg + - assets/nuernberg/background.png # An image asset can refer to one or more resolution-specific 'variants', see # https://flutter.dev/assets-and-images/#resolution-aware.