Skip to content

Commit

Permalink
1430: Extend card add params (#1803)
Browse files Browse the repository at this point in the history
* 1430: Add extendable field to the card verification result

* 1430: Extend card notification

* 1430: Refactor CardQueryService

* 1430: Adjust translations

* 1430: Adjust translations and text style

* 1430: Use FilledButton instead of ElevatedButton

* Remove extra line break in the Koblenz publisher text

* 1430: Add elevation to the extend card notification

* 1430: add queryParams to card extension buttons

* 1430: pass each key separate to avoid linting issues, add tests, add workaround to fix display of more actions button

* 1430: fix formatting

* 1430: add projectId check

* 1430: rename params, adjust getApplicationUrl function, add todo for test improvements, only add queryParams if card is extendable

* 1430: fix minor issues

---------

Co-authored-by: seluianova <[email protected]>
Co-authored-by: Tory <[email protected]>
  • Loading branch information
3 people authored Dec 2, 2024
1 parent 57bddc0 commit 014ef5d
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 41 deletions.
6 changes: 4 additions & 2 deletions administration/src/project-configs/koblenz/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { QUERY_PARAM_BIRTHDAY, QUERY_PARAM_KOBLENZ_REFERENCE_NUMBER, QUERY_PARAM_NAME } from 'build-configs'

import BirthdayExtension from '../../cards/extensions/BirthdayExtension'
import KoblenzReferenceNumberExtension from '../../cards/extensions/KoblenzReferenceNumberExtension'
import { ActivationText } from '../common/ActivationText'
Expand All @@ -11,9 +13,9 @@ const config: ProjectConfig = {
projectId: 'koblenz.sozialpass.app',
staticQrCodesEnabled: true,
card: {
nameColumnName: 'Name',
nameColumnName: QUERY_PARAM_NAME,
expiryColumnName: 'Ablaufdatum',
extensionColumnNames: ['Geburtsdatum', 'Referenznummer'],
extensionColumnNames: [QUERY_PARAM_BIRTHDAY, QUERY_PARAM_KOBLENZ_REFERENCE_NUMBER],
defaultValidity: { years: 1 },
extensions: [BirthdayExtension, KoblenzReferenceNumberExtension],
},
Expand Down
3 changes: 3 additions & 0 deletions frontend/build-configs/bayern/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export const bayernCommon: CommonBuildConfigType = {
staging: `https://${BAYERN_STAGING_ID}/beantragen`,
local : 'http://localhost:3000/beantragen'
},
applicationQueryKeyName: null,
applicationQueryKeyBirthday: null,
applicationQueryKeyReferenceNumber: null,
dataPrivacyPolicyUrl: "https://bayern.ehrenamtskarte.app/data-privacy-policy",
publisherAddress:
"Bayerisches Staatsministerium\nfür Familie, Arbeit und Soziales\nWinzererstraße 9\n80797 München",
Expand Down
6 changes: 5 additions & 1 deletion frontend/build-configs/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export const BAYERN_STAGING_ID = 'staging.bayern.ehrenamtskarte.app'
export const NUERNBERG_PRODUCTION_ID = 'nuernberg.sozialpass.app'
export const NUERNBERG_STAGING_ID = 'staging.nuernberg.sozialpass.app'
export const KOBLENZ_PRODUCTION_ID = 'koblenz.sozialpass.app'
export const KOBLENZ_STAGING_ID = 'staging.koblenz.sozialpass.app'
export const KOBLENZ_STAGING_ID = 'staging.koblenz.sozialpass.app'

export const QUERY_PARAM_NAME = 'Name'
export const QUERY_PARAM_BIRTHDAY = 'Geburtsdatum'
export const QUERY_PARAM_KOBLENZ_REFERENCE_NUMBER = 'Referenznummer'
11 changes: 10 additions & 1 deletion frontend/build-configs/koblenz/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { ACTIVATION_PATH, KOBLENZ_PRODUCTION_ID, KOBLENZ_STAGING_ID } from "../constants"
import {
ACTIVATION_PATH,
KOBLENZ_PRODUCTION_ID,
KOBLENZ_STAGING_ID,
QUERY_PARAM_BIRTHDAY, QUERY_PARAM_KOBLENZ_REFERENCE_NUMBER,
QUERY_PARAM_NAME
} from '../constants'
import BuildConfigType, { CommonBuildConfigType } from "../types"
import disclaimerText from "./disclaimerText"
import publisherText from "./publisherText"
Expand Down Expand Up @@ -78,6 +84,9 @@ export const koblenzCommon: CommonBuildConfigType = {
staging: `https://${KOBLENZ_STAGING_ID}/erstellen`,
local : 'http://localhost:3000/erstellen'
},
applicationQueryKeyName: QUERY_PARAM_NAME,
applicationQueryKeyBirthday: QUERY_PARAM_BIRTHDAY,
applicationQueryKeyReferenceNumber: QUERY_PARAM_KOBLENZ_REFERENCE_NUMBER,
dataPrivacyPolicyUrl: "https://koblenz.sozialpass.app/data-privacy-policy",
publisherAddress: "Stadt Koblenz\nWilli-Hörter-Platz 1\n56068 Koblenz",
publisherText,
Expand Down
3 changes: 3 additions & 0 deletions frontend/build-configs/nuernberg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export const nuernbergCommon: CommonBuildConfigType = {
staging: 'https://beantragen.nuernberg.sozialpass.app',
local : 'https://beantragen.nuernberg.sozialpass.app'
},
applicationQueryKeyName: null,
applicationQueryKeyBirthday: null,
applicationQueryKeyReferenceNumber: null,
publisherAddress:
"Stadt Nürnberg\nAmt für Existenzsicherung\nund soziale Integration - Sozialamt\nDietzstraße 4\n90443 Nürnberg",
dataPrivacyPolicyUrl: "https://nuernberg.sozialpass.app/data-privacy-policy",
Expand Down
3 changes: 3 additions & 0 deletions frontend/build-configs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ export type CommonBuildConfigType = {
production: string
local: string
}
applicationQueryKeyName: string | null,
applicationQueryKeyBirthday: string | null,
applicationQueryKeyReferenceNumber: string | null
dataPrivacyPolicyUrl: string
publisherAddress: string
publisherText: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:ehrenamtskarte/identification/card_detail_view/verification_code
import 'package:provider/provider.dart';

class CardDetailView extends StatefulWidget {
final String applicationUrl;
final DynamicUserCode userCode;
final VoidCallback startActivation;
final VoidCallback startVerification;
Expand All @@ -22,6 +23,7 @@ class CardDetailView extends StatefulWidget {

const CardDetailView(
{super.key,
required this.applicationUrl,
required this.userCode,
required this.startActivation,
required this.startVerification,
Expand Down Expand Up @@ -91,7 +93,7 @@ class _CardDetailViewState extends State<CardDetailView> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isCardExpired(cardInfo) && isCardExtendable(cardInfo, cardVerification))
ExtendCardNotification(),
ExtendCardNotification(applicationUrl: widget.applicationUrl),
paddedCard,
],
)),
Expand All @@ -114,7 +116,7 @@ class _CardDetailViewState extends State<CardDetailView> {
child: Column(
children: [
if (!isCardExpired(cardInfo) && isCardExtendable(cardInfo, cardVerification))
ExtendCardNotification(),
ExtendCardNotification(applicationUrl: widget.applicationUrl),
paddedCard,
const SizedBox(height: 16),
qrCodeAndStatus,
Expand Down Expand Up @@ -227,7 +229,9 @@ class QrCodeAndStatus extends StatelessWidget {
t.common.moreActions,
),
),
)
),
// TODO 1802 Fix more actions button not displayed properly
const SizedBox(height: 12),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import 'package:flutter/material.dart';

import 'package:ehrenamtskarte/build_config/build_config.dart' show buildConfig;
import 'package:ehrenamtskarte/configuration/definitions.dart';
import 'package:ehrenamtskarte/configuration/settings_model.dart';
import 'package:ehrenamtskarte/l10n/translations.g.dart';
import 'package:provider/provider.dart';
import 'package:tinycolor2/tinycolor2.dart';
import 'package:url_launcher/url_launcher_string.dart';

class ExtendCardNotification extends StatefulWidget {
final String applicationUrl;

const ExtendCardNotification({super.key, required this.applicationUrl});
@override
State<ExtendCardNotification> createState() => _ExtendCardNotificationState();
}
Expand Down Expand Up @@ -83,17 +82,7 @@ class _ExtendCardNotificationState extends State<ExtendCardNotification> {
);
}

Future<bool> _openApplication() {
// TODO add query params with card info
final isStagingEnabled = Provider.of<SettingsModel>(context, listen: false).enableStaging;
final applicationUrl = isStagingEnabled
? buildConfig.applicationUrl.staging
: isProduction()
? buildConfig.applicationUrl.production
: buildConfig.applicationUrl.local;
return launchUrlString(
applicationUrl,
mode: LaunchMode.externalApplication,
);
void _openApplication() {
launchUrlString(widget.applicationUrl, mode: LaunchMode.externalApplication);
}
}
10 changes: 2 additions & 8 deletions frontend/lib/identification/id_card/card_content.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:ehrenamtskarte/build_config/build_config.dart' show buildConfig;
import 'package:ehrenamtskarte/identification/id_card/card_header_logo.dart';
import 'package:ehrenamtskarte/identification/id_card/id_card.dart';
import 'package:ehrenamtskarte/identification/util/card_info_utils.dart';
import 'package:ehrenamtskarte/proto/card.pb.dart';
import 'package:ehrenamtskarte/util/color_utils.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -55,13 +56,6 @@ class CardContent extends StatelessWidget {
: t.identification.unlimited;
}

String? get _formattedBirthday {
final birthday = cardInfo.extensions.hasExtensionBirthday() ? cardInfo.extensions.extensionBirthday.birthday : null;
return birthday != null
? DateFormat('dd.MM.yyyy').format(DateTime.fromMillisecondsSinceEpoch(0).add(Duration(days: birthday)))
: null;
}

String? get _passId {
return cardInfo.extensions.hasExtensionNuernbergPassId()
? cardInfo.extensions.extensionNuernbergPassId.passId.toString()
Expand All @@ -87,7 +81,7 @@ class CardContent extends StatelessWidget {
final cardColor = cardInfo.extensions.extensionBavariaCardType.cardType == BavariaCardType.GOLD
? premiumCardColor
: standardCardColor;
final formattedBirthday = _formattedBirthday;
final formattedBirthday = getFormattedBirthday(cardInfo);
final passId = _passId;
final startDate = _formattedStartDate;
return LayoutBuilder(
Expand Down
25 changes: 15 additions & 10 deletions frontend/lib/identification/identification_page.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import 'package:carousel_slider/carousel_controller.dart';
import 'package:ehrenamtskarte/build_config/build_config.dart' show buildConfig;
import 'package:ehrenamtskarte/configuration/definitions.dart';
import 'package:ehrenamtskarte/configuration/settings_model.dart';
import 'package:ehrenamtskarte/identification/activation_workflow/activation_code_scanner_page.dart';
import 'package:ehrenamtskarte/identification/card_detail_view/card_carousel.dart';
import 'package:ehrenamtskarte/identification/card_detail_view/card_detail_view.dart';
import 'package:ehrenamtskarte/identification/no_card_view.dart';
import 'package:ehrenamtskarte/identification/qr_code_scanner/qr_code_camera_permission_dialog.dart';
import 'package:ehrenamtskarte/identification/user_code_model.dart';
import 'package:ehrenamtskarte/identification/util/card_info_utils.dart';
import 'package:ehrenamtskarte/identification/verification_workflow/dialogs/remove_card_confirmation_dialog.dart';
import 'package:ehrenamtskarte/identification/verification_workflow/verification_workflow.dart';
import 'package:ehrenamtskarte/l10n/translations.g.dart';
import 'package:ehrenamtskarte/proto/card.pb.dart';
import 'package:ehrenamtskarte/routing.dart';
import 'package:ehrenamtskarte/util/get_application_url.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -48,11 +49,21 @@ class IdentificationPageState extends State<IdentificationPage> {
if (userCodeModel.userCodes.isNotEmpty) {
final List<Widget> carouselCards = [];
for (var code in userCodeModel.userCodes) {
final applicationUrl = isCardExtendable(code.info, code.cardVerification)
? getApplicationUrlForCardExtension(
getApplicationUrl(context),
code.info,
buildConfig.applicationQueryKeyName,
buildConfig.applicationQueryKeyBirthday,
buildConfig.applicationQueryKeyReferenceNumber)
: getApplicationUrl(context);

carouselCards.add(CardDetailView(
applicationUrl: applicationUrl,
userCode: code,
startVerification: () => _showVerificationDialog(context, settings, userCodeModel),
startActivation: () => _startActivation(context),
startApplication: _startApplication,
startApplication: () => _startApplication(applicationUrl),
openRemoveCardDialog: () => _openRemoveCardDialog(context),
));
}
Expand All @@ -67,7 +78,7 @@ class IdentificationPageState extends State<IdentificationPage> {
return NoCardView(
startVerification: () => _showVerificationDialog(context, settings, userCodeModel),
startActivation: () => _startActivation(context),
startApplication: _startApplication,
startApplication: () => _startApplication(getApplicationUrl(context)),
);
},
);
Expand Down Expand Up @@ -106,13 +117,7 @@ class IdentificationPageState extends State<IdentificationPage> {
handleDeniedCameraPermission(context);
}

Future<bool> _startApplication() {
final isStagingEnabled = Provider.of<SettingsModel>(context, listen: false).enableStaging;
final applicationUrl = isStagingEnabled
? buildConfig.applicationUrl.staging
: isProduction()
? buildConfig.applicationUrl.production
: buildConfig.applicationUrl.local;
Future<bool> _startApplication(String applicationUrl) {
return launchUrlString(
applicationUrl,
mode: LaunchMode.externalApplication,
Expand Down
8 changes: 8 additions & 0 deletions frontend/lib/identification/util/card_info_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:ehrenamtskarte/identification/util/canonical_json.dart';
import 'package:ehrenamtskarte/proto/card.pb.dart';
import 'package:ehrenamtskarte/util/date_utils.dart';
import 'package:ehrenamtskarte/util/json_canonicalizer.dart';
import 'package:intl/intl.dart';

extension Hashing on CardInfo {
String hash(List<int> pepper) {
Expand Down Expand Up @@ -62,3 +63,10 @@ DateTime? _getExpirationDayWithTolerance(CardInfo cardInfo) {
return DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)
.add(Duration(days: expirationDay, hours: toleranceInHours));
}

String? getFormattedBirthday(CardInfo cardInfo) {
final birthday = cardInfo.extensions.hasExtensionBirthday() ? cardInfo.extensions.extensionBirthday.birthday : null;
return birthday != null
? DateFormat('dd.MM.yyyy').format(DateTime.fromMillisecondsSinceEpoch(0).add(Duration(days: birthday)))
: null;
}
34 changes: 34 additions & 0 deletions frontend/lib/util/get_application_url.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:ehrenamtskarte/build_config/build_config.dart';
import 'package:ehrenamtskarte/configuration/definitions.dart';
import 'package:ehrenamtskarte/configuration/settings_model.dart';
import 'package:ehrenamtskarte/identification/util/card_info_utils.dart';
import 'package:ehrenamtskarte/proto/card.pb.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

String getApplicationUrl(BuildContext context) {
final isStagingEnabled = Provider.of<SettingsModel>(context, listen: false).enableStaging;
final applicationUrl = buildConfig.applicationUrl;
if (isStagingEnabled) {
return applicationUrl.staging;
}
return isProduction() ? applicationUrl.production : applicationUrl.local;
}

String getApplicationUrlForCardExtension(String applicationUrl, CardInfo cardInfo, String? applicationQueryKeyName,
String? applicationQueryKeyBirthday, String? applicationQueryKeyReferenceNumber) {
if (applicationQueryKeyName != null &&
applicationQueryKeyBirthday != null &&
applicationQueryKeyReferenceNumber != null) {
final parsedUrl = Uri.parse(applicationUrl);
final queryParams = {
applicationQueryKeyName: cardInfo.fullName,
applicationQueryKeyBirthday: getFormattedBirthday(cardInfo),
applicationQueryKeyReferenceNumber: cardInfo.extensions.hasExtensionKoblenzReferenceNumber()
? cardInfo.extensions.extensionKoblenzReferenceNumber.referenceNumber
: null,
};
return parsedUrl.replace(queryParameters: queryParams).toString();
}
return applicationUrl;
}
58 changes: 58 additions & 0 deletions frontend/test/get_application_url_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:ehrenamtskarte/proto/card.pb.dart';
import 'package:ehrenamtskarte/util/get_application_url.dart';
import 'package:test/test.dart';

void main() {
group('getApplicationUrlForCardExtension', () {
test('results in url with queryParams for koblenz', () {
final applicationUrl = 'https://koblenz.sozialpass.app/erstellen';
final applicationUrlQueryKeyName = 'Name';
final applicationUrlQueryKeyBirthday = 'Geburtsdatum';
final applicationUrlQueryKeyReferenceNumber = 'Referenznummer';
final cardInfo = CardInfo()
..fullName = 'Karla Koblenz'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionBirthday = (BirthdayExtension()..birthday = -365 * 10)
..extensionKoblenzReferenceNumber = (KoblenzReferenceNumberExtension()..referenceNumber = '123K'));
final applicationUrlWithParameters = getApplicationUrlForCardExtension(applicationUrl, cardInfo,
applicationUrlQueryKeyName, applicationUrlQueryKeyBirthday, applicationUrlQueryKeyReferenceNumber);
expect(applicationUrlWithParameters,
'https://koblenz.sozialpass.app/erstellen?Name=Karla+Koblenz&Geburtsdatum=04.01.1960&Referenznummer=123K');
});

test('results in url without queryParams if no keys were provided and card info bayern', () {
final applicationUrl = 'https://bayern.ehrenamtskarte.app/beantragen';
final String? applicationUrlQueryKeyName = null;
final String? applicationUrlQueryKeyBirthday = null;
final String? applicationUrlQueryKeyReferenceNumber = null;
final cardInfo = CardInfo()
..fullName = 'Max Mustermann'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionRegion = (RegionExtension()..regionId = 16)
..extensionBavariaCardType = (BavariaCardTypeExtension()..cardType = BavariaCardType.STANDARD));
final applicationUrlWithParameters = getApplicationUrlForCardExtension(applicationUrl, cardInfo,
applicationUrlQueryKeyName, applicationUrlQueryKeyBirthday, applicationUrlQueryKeyReferenceNumber);
expect(applicationUrlWithParameters, 'https://bayern.ehrenamtskarte.app/beantragen');
});

test('results in url without queryParams if no keys were provided and card info nuernberg', () {
final applicationUrl = 'https://beantragen.nuernberg.sozialpass.app';
final String? applicationUrlQueryKeyName = null;
final String? applicationUrlQueryKeyBirthday = null;
final String? applicationUrlQueryKeyReferenceNumber = null;
final cardInfo = CardInfo()
..fullName = 'Max Mustermann'
..expirationDay = 365 * 40 // Equals 14.600
..extensions = (CardExtensions()
..extensionRegion = (RegionExtension()..regionId = 93)
..extensionBirthday = (BirthdayExtension()..birthday = -365 * 10)
..extensionNuernbergPassId = (NuernbergPassIdExtension()..passId = 99999999)
..extensionStartDay = (StartDayExtension()..startDay = 365 * 2));
final applicationUrlWithParameters = getApplicationUrlForCardExtension(applicationUrl, cardInfo,
applicationUrlQueryKeyName, applicationUrlQueryKeyBirthday, applicationUrlQueryKeyReferenceNumber);
expect(applicationUrlWithParameters, 'https://beantragen.nuernberg.sozialpass.app');
});
});
}

0 comments on commit 014ef5d

Please sign in to comment.