diff --git a/apps/health_campaign_field_worker_app/lib/pages/login.dart b/apps/health_campaign_field_worker_app/lib/pages/login.dart index f0912efb5..1a3fdd019 100644 --- a/apps/health_campaign_field_worker_app/lib/pages/login.dart +++ b/apps/health_campaign_field_worker_app/lib/pages/login.dart @@ -1,5 +1,7 @@ import 'package:digit_components/digit_components.dart'; +import 'package:digit_components/models/privacy_notice/privacy_notice_model.dart'; import 'package:digit_components/widgets/atoms/digit_toaster.dart'; +import 'package:digit_components/widgets/privacy_notice/privacy_component.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:reactive_forms/reactive_forms.dart'; @@ -11,7 +13,7 @@ import '../router/app_router.dart'; import '../utils/environment_config.dart'; import '../utils/i18_key_constants.dart' as i18; import '../widgets/localized.dart'; -import '../widgets/privacy_notice/privacy_component.dart'; + @RoutePage() class LoginPage extends LocalizedStatefulWidget { @@ -140,7 +142,7 @@ class _LoginPageState extends LocalizedState { form.control(_privacyCheck).setValidators([Validators.requiredTrue]); form.control(_privacyCheck).updateValueAndValidity(); return PrivacyComponent( - privacyPolicy: privacyPolicyJson, + privacyPolicy: convertToPrivacyPolicyModel(privacyPolicyJson), formControlName: _privacyCheck, text: localizations .translate(i18.privacyPolicy.privacyNoticeText), @@ -237,3 +239,27 @@ class _LoginPageState extends LocalizedState { ) }); } + + +// convert to privacy notice model +PrivacyNoticeModel? convertToPrivacyPolicyModel(PrivacyPolicy? privacyPolicy) { + return PrivacyNoticeModel( + header: privacyPolicy?.header ?? '', + module: privacyPolicy?.module ?? '', + active: privacyPolicy?.active, + contents: privacyPolicy?.contents?.map((content) => ContentNoticeModel( + header: content.header, + descriptions: content.descriptions?.map((description) => DescriptionNoticeModel( + text: description.text, + type: description.type, + isBold: description.isBold, + subDescriptions: description.subDescriptions?.map((subDescription) => SubDescriptionNoticeModel( + text: subDescription.text, + type: subDescription.type, + isBold: subDescription.isBold, + isSpaceRequired: subDescription.isSpaceRequired, + )).toList(), + )).toList(), + )).toList(), + ); +} \ No newline at end of file diff --git a/apps/health_campaign_field_worker_app/lib/utils/localization_delegates.dart b/apps/health_campaign_field_worker_app/lib/utils/localization_delegates.dart index 6e62855fe..700760a8e 100644 --- a/apps/health_campaign_field_worker_app/lib/utils/localization_delegates.dart +++ b/apps/health_campaign_field_worker_app/lib/utils/localization_delegates.dart @@ -16,6 +16,7 @@ import 'package:referral_reconciliation/blocs/app_localization.dart' as referral_reconciliation_localization; import 'package:registration_delivery/blocs/app_localization.dart' as registration_delivery_localization; +import 'package:digit_components/blocs/localization.dart' as component_localization; import '../blocs/localization/app_localization.dart'; import '../data/local_store/no_sql/schema/app_configuration.dart'; @@ -59,6 +60,10 @@ getAppLocalizationDelegates({ digit_dss_localization.DashboardLocalization.getDelegate( LocalizationLocalRepository().returnLocalizationFromSQL(sql) as Future, appConfig.languages!, - ) + ), + component_localization.ComponentLocalization.getDelegate( + LocalizationLocalRepository().returnLocalizationFromSQL(sql) as Future, + appConfig.languages!, + ), ]; } diff --git a/apps/health_campaign_field_worker_app/pubspec.lock b/apps/health_campaign_field_worker_app/pubspec.lock index c96f0b5fd..371a1a8d1 100644 --- a/apps/health_campaign_field_worker_app/pubspec.lock +++ b/apps/health_campaign_field_worker_app/pubspec.lock @@ -493,10 +493,9 @@ packages: digit_components: dependency: "direct main" description: - name: digit_components - sha256: "9cca4d9a546037080afe02b6ade82fdf01574e11f5656ad12120fd6966578616" - url: "https://pub.dev" - source: hosted + path: "../../packages/digit_components" + relative: true + source: path version: "1.0.1+1" digit_data_model: dependency: "direct main" diff --git a/packages/digit_components/lib/blocs/localization.dart b/packages/digit_components/lib/blocs/localization.dart new file mode 100644 index 000000000..d393cfefa --- /dev/null +++ b/packages/digit_components/lib/blocs/localization.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import 'localization_delegates.dart'; + + +// Class responsible for handling component localization +class ComponentLocalization { + final Locale locale; + final Future localizedStrings; + final List languages; + + ComponentLocalization(this.locale, this.localizedStrings, this.languages); + + // Method to get the current localization instance from context + static ComponentLocalization of(BuildContext context) { + return Localizations.of(context, ComponentLocalization)!; + } + + static final List _localizedStrings = []; + + // Method to get the delegate for localization + static LocalizationsDelegate getDelegate( + Future localizedStrings, List languages) => + ComponentLocalizationDelegate(localizedStrings, languages); + + // Method to load localized strings + Future load() async { + _localizedStrings.clear(); + // Iterate over localized strings and filter based on locale + for (var element in await localizedStrings) { + if (element.locale == '${locale.languageCode}_${locale.countryCode}') { + _localizedStrings.add(element); + } + } + + return true; + } + + // Method to translate a given localized value + String translate(String localizedValues) { + + if (_localizedStrings.isEmpty) { + return localizedValues; + } else { + final index = _localizedStrings.indexWhere( + (medium) => medium.code == localizedValues, + ); + + return index != -1 ? _localizedStrings[index].message : localizedValues; + } + } +} \ No newline at end of file diff --git a/packages/digit_components/lib/blocs/localization_delegates.dart b/packages/digit_components/lib/blocs/localization_delegates.dart new file mode 100644 index 000000000..3d2e2dd16 --- /dev/null +++ b/packages/digit_components/lib/blocs/localization_delegates.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import 'localization.dart'; + + +class ComponentLocalizationDelegate + extends LocalizationsDelegate { + final Future localizedStrings; + final List languages; + + const ComponentLocalizationDelegate(this.localizedStrings, this.languages); + + @override + bool isSupported(Locale locale) { + return languages.map((e) { + final results = e.value.split('_'); + if (results.isNotEmpty) return results.first; + }).contains(locale.languageCode); + } + + @override + Future load(Locale locale) async { + ComponentLocalization localization = + ComponentLocalization(locale, localizedStrings, languages); + await localization.load(); + return localization; + } + + @override + bool shouldReload(covariant LocalizationsDelegate old) { + return true; + } +} \ No newline at end of file diff --git a/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.dart b/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.dart new file mode 100644 index 000000000..f2d1f896c --- /dev/null +++ b/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.dart @@ -0,0 +1,50 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'privacy_notice_model.freezed.dart'; +part 'privacy_notice_model.g.dart'; + +@freezed +class PrivacyNoticeModel with _$PrivacyNoticeModel { + const factory PrivacyNoticeModel({ + required String header, + required String module, + bool? active, + List? contents, + }) = _PrivacyNoticeModel; + + factory PrivacyNoticeModel.fromJson(Map json) => _$PrivacyNoticeModelFromJson(json); +} + +@freezed +class ContentNoticeModel with _$ContentNoticeModel { + const factory ContentNoticeModel({ + String? header, + List? descriptions, + }) = _ContentNoticeModel; + + factory ContentNoticeModel.fromJson(Map json) => _$ContentNoticeModelFromJson(json); +} + +@freezed +class DescriptionNoticeModel with _$DescriptionNoticeModel { + const factory DescriptionNoticeModel({ + String? text, + String? type, + bool? isBold, + List? subDescriptions, + }) = _DescriptionNoticeModel; + + factory DescriptionNoticeModel.fromJson(Map json) => _$DescriptionNoticeModelFromJson(json); +} + +@freezed +class SubDescriptionNoticeModel with _$SubDescriptionNoticeModel { + const factory SubDescriptionNoticeModel({ + String? text, + String? type, + bool? isBold, + bool? isSpaceRequired, + }) = _SubDescriptionNoticeModel; + + factory SubDescriptionNoticeModel.fromJson(Map json) => _$SubDescriptionNoticeModelFromJson(json); +} diff --git a/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.freezed.dart b/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.freezed.dart new file mode 100644 index 000000000..2090ee866 --- /dev/null +++ b/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.freezed.dart @@ -0,0 +1,808 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'privacy_notice_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +PrivacyNoticeModel _$PrivacyNoticeModelFromJson(Map json) { + return _PrivacyNoticeModel.fromJson(json); +} + +/// @nodoc +mixin _$PrivacyNoticeModel { + String get header => throw _privateConstructorUsedError; + String get module => throw _privateConstructorUsedError; + bool? get active => throw _privateConstructorUsedError; + List? get contents => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PrivacyNoticeModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PrivacyNoticeModelCopyWith<$Res> { + factory $PrivacyNoticeModelCopyWith( + PrivacyNoticeModel value, $Res Function(PrivacyNoticeModel) then) = + _$PrivacyNoticeModelCopyWithImpl<$Res, PrivacyNoticeModel>; + @useResult + $Res call( + {String header, + String module, + bool? active, + List? contents}); +} + +/// @nodoc +class _$PrivacyNoticeModelCopyWithImpl<$Res, $Val extends PrivacyNoticeModel> + implements $PrivacyNoticeModelCopyWith<$Res> { + _$PrivacyNoticeModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? header = null, + Object? module = null, + Object? active = freezed, + Object? contents = freezed, + }) { + return _then(_value.copyWith( + header: null == header + ? _value.header + : header // ignore: cast_nullable_to_non_nullable + as String, + module: null == module + ? _value.module + : module // ignore: cast_nullable_to_non_nullable + as String, + active: freezed == active + ? _value.active + : active // ignore: cast_nullable_to_non_nullable + as bool?, + contents: freezed == contents + ? _value.contents + : contents // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PrivacyNoticeModelImplCopyWith<$Res> + implements $PrivacyNoticeModelCopyWith<$Res> { + factory _$$PrivacyNoticeModelImplCopyWith(_$PrivacyNoticeModelImpl value, + $Res Function(_$PrivacyNoticeModelImpl) then) = + __$$PrivacyNoticeModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String header, + String module, + bool? active, + List? contents}); +} + +/// @nodoc +class __$$PrivacyNoticeModelImplCopyWithImpl<$Res> + extends _$PrivacyNoticeModelCopyWithImpl<$Res, _$PrivacyNoticeModelImpl> + implements _$$PrivacyNoticeModelImplCopyWith<$Res> { + __$$PrivacyNoticeModelImplCopyWithImpl(_$PrivacyNoticeModelImpl _value, + $Res Function(_$PrivacyNoticeModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? header = null, + Object? module = null, + Object? active = freezed, + Object? contents = freezed, + }) { + return _then(_$PrivacyNoticeModelImpl( + header: null == header + ? _value.header + : header // ignore: cast_nullable_to_non_nullable + as String, + module: null == module + ? _value.module + : module // ignore: cast_nullable_to_non_nullable + as String, + active: freezed == active + ? _value.active + : active // ignore: cast_nullable_to_non_nullable + as bool?, + contents: freezed == contents + ? _value._contents + : contents // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PrivacyNoticeModelImpl implements _PrivacyNoticeModel { + const _$PrivacyNoticeModelImpl( + {required this.header, + required this.module, + this.active, + final List? contents}) + : _contents = contents; + + factory _$PrivacyNoticeModelImpl.fromJson(Map json) => + _$$PrivacyNoticeModelImplFromJson(json); + + @override + final String header; + @override + final String module; + @override + final bool? active; + final List? _contents; + @override + List? get contents { + final value = _contents; + if (value == null) return null; + if (_contents is EqualUnmodifiableListView) return _contents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'PrivacyNoticeModel(header: $header, module: $module, active: $active, contents: $contents)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PrivacyNoticeModelImpl && + (identical(other.header, header) || other.header == header) && + (identical(other.module, module) || other.module == module) && + (identical(other.active, active) || other.active == active) && + const DeepCollectionEquality().equals(other._contents, _contents)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, header, module, active, + const DeepCollectionEquality().hash(_contents)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PrivacyNoticeModelImplCopyWith<_$PrivacyNoticeModelImpl> get copyWith => + __$$PrivacyNoticeModelImplCopyWithImpl<_$PrivacyNoticeModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$PrivacyNoticeModelImplToJson( + this, + ); + } +} + +abstract class _PrivacyNoticeModel implements PrivacyNoticeModel { + const factory _PrivacyNoticeModel( + {required final String header, + required final String module, + final bool? active, + final List? contents}) = _$PrivacyNoticeModelImpl; + + factory _PrivacyNoticeModel.fromJson(Map json) = + _$PrivacyNoticeModelImpl.fromJson; + + @override + String get header; + @override + String get module; + @override + bool? get active; + @override + List? get contents; + @override + @JsonKey(ignore: true) + _$$PrivacyNoticeModelImplCopyWith<_$PrivacyNoticeModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ContentNoticeModel _$ContentNoticeModelFromJson(Map json) { + return _ContentNoticeModel.fromJson(json); +} + +/// @nodoc +mixin _$ContentNoticeModel { + String? get header => throw _privateConstructorUsedError; + List? get descriptions => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ContentNoticeModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ContentNoticeModelCopyWith<$Res> { + factory $ContentNoticeModelCopyWith( + ContentNoticeModel value, $Res Function(ContentNoticeModel) then) = + _$ContentNoticeModelCopyWithImpl<$Res, ContentNoticeModel>; + @useResult + $Res call({String? header, List? descriptions}); +} + +/// @nodoc +class _$ContentNoticeModelCopyWithImpl<$Res, $Val extends ContentNoticeModel> + implements $ContentNoticeModelCopyWith<$Res> { + _$ContentNoticeModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? header = freezed, + Object? descriptions = freezed, + }) { + return _then(_value.copyWith( + header: freezed == header + ? _value.header + : header // ignore: cast_nullable_to_non_nullable + as String?, + descriptions: freezed == descriptions + ? _value.descriptions + : descriptions // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ContentNoticeModelImplCopyWith<$Res> + implements $ContentNoticeModelCopyWith<$Res> { + factory _$$ContentNoticeModelImplCopyWith(_$ContentNoticeModelImpl value, + $Res Function(_$ContentNoticeModelImpl) then) = + __$$ContentNoticeModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? header, List? descriptions}); +} + +/// @nodoc +class __$$ContentNoticeModelImplCopyWithImpl<$Res> + extends _$ContentNoticeModelCopyWithImpl<$Res, _$ContentNoticeModelImpl> + implements _$$ContentNoticeModelImplCopyWith<$Res> { + __$$ContentNoticeModelImplCopyWithImpl(_$ContentNoticeModelImpl _value, + $Res Function(_$ContentNoticeModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? header = freezed, + Object? descriptions = freezed, + }) { + return _then(_$ContentNoticeModelImpl( + header: freezed == header + ? _value.header + : header // ignore: cast_nullable_to_non_nullable + as String?, + descriptions: freezed == descriptions + ? _value._descriptions + : descriptions // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ContentNoticeModelImpl implements _ContentNoticeModel { + const _$ContentNoticeModelImpl( + {this.header, final List? descriptions}) + : _descriptions = descriptions; + + factory _$ContentNoticeModelImpl.fromJson(Map json) => + _$$ContentNoticeModelImplFromJson(json); + + @override + final String? header; + final List? _descriptions; + @override + List? get descriptions { + final value = _descriptions; + if (value == null) return null; + if (_descriptions is EqualUnmodifiableListView) return _descriptions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'ContentNoticeModel(header: $header, descriptions: $descriptions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ContentNoticeModelImpl && + (identical(other.header, header) || other.header == header) && + const DeepCollectionEquality() + .equals(other._descriptions, _descriptions)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, header, const DeepCollectionEquality().hash(_descriptions)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ContentNoticeModelImplCopyWith<_$ContentNoticeModelImpl> get copyWith => + __$$ContentNoticeModelImplCopyWithImpl<_$ContentNoticeModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ContentNoticeModelImplToJson( + this, + ); + } +} + +abstract class _ContentNoticeModel implements ContentNoticeModel { + const factory _ContentNoticeModel( + {final String? header, + final List? descriptions}) = + _$ContentNoticeModelImpl; + + factory _ContentNoticeModel.fromJson(Map json) = + _$ContentNoticeModelImpl.fromJson; + + @override + String? get header; + @override + List? get descriptions; + @override + @JsonKey(ignore: true) + _$$ContentNoticeModelImplCopyWith<_$ContentNoticeModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + +DescriptionNoticeModel _$DescriptionNoticeModelFromJson( + Map json) { + return _DescriptionNoticeModel.fromJson(json); +} + +/// @nodoc +mixin _$DescriptionNoticeModel { + String? get text => throw _privateConstructorUsedError; + String? get type => throw _privateConstructorUsedError; + bool? get isBold => throw _privateConstructorUsedError; + List? get subDescriptions => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $DescriptionNoticeModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DescriptionNoticeModelCopyWith<$Res> { + factory $DescriptionNoticeModelCopyWith(DescriptionNoticeModel value, + $Res Function(DescriptionNoticeModel) then) = + _$DescriptionNoticeModelCopyWithImpl<$Res, DescriptionNoticeModel>; + @useResult + $Res call( + {String? text, + String? type, + bool? isBold, + List? subDescriptions}); +} + +/// @nodoc +class _$DescriptionNoticeModelCopyWithImpl<$Res, + $Val extends DescriptionNoticeModel> + implements $DescriptionNoticeModelCopyWith<$Res> { + _$DescriptionNoticeModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? text = freezed, + Object? type = freezed, + Object? isBold = freezed, + Object? subDescriptions = freezed, + }) { + return _then(_value.copyWith( + text: freezed == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isBold: freezed == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool?, + subDescriptions: freezed == subDescriptions + ? _value.subDescriptions + : subDescriptions // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$DescriptionNoticeModelImplCopyWith<$Res> + implements $DescriptionNoticeModelCopyWith<$Res> { + factory _$$DescriptionNoticeModelImplCopyWith( + _$DescriptionNoticeModelImpl value, + $Res Function(_$DescriptionNoticeModelImpl) then) = + __$$DescriptionNoticeModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? text, + String? type, + bool? isBold, + List? subDescriptions}); +} + +/// @nodoc +class __$$DescriptionNoticeModelImplCopyWithImpl<$Res> + extends _$DescriptionNoticeModelCopyWithImpl<$Res, + _$DescriptionNoticeModelImpl> + implements _$$DescriptionNoticeModelImplCopyWith<$Res> { + __$$DescriptionNoticeModelImplCopyWithImpl( + _$DescriptionNoticeModelImpl _value, + $Res Function(_$DescriptionNoticeModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? text = freezed, + Object? type = freezed, + Object? isBold = freezed, + Object? subDescriptions = freezed, + }) { + return _then(_$DescriptionNoticeModelImpl( + text: freezed == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isBold: freezed == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool?, + subDescriptions: freezed == subDescriptions + ? _value._subDescriptions + : subDescriptions // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$DescriptionNoticeModelImpl implements _DescriptionNoticeModel { + const _$DescriptionNoticeModelImpl( + {this.text, + this.type, + this.isBold, + final List? subDescriptions}) + : _subDescriptions = subDescriptions; + + factory _$DescriptionNoticeModelImpl.fromJson(Map json) => + _$$DescriptionNoticeModelImplFromJson(json); + + @override + final String? text; + @override + final String? type; + @override + final bool? isBold; + final List? _subDescriptions; + @override + List? get subDescriptions { + final value = _subDescriptions; + if (value == null) return null; + if (_subDescriptions is EqualUnmodifiableListView) return _subDescriptions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'DescriptionNoticeModel(text: $text, type: $type, isBold: $isBold, subDescriptions: $subDescriptions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DescriptionNoticeModelImpl && + (identical(other.text, text) || other.text == text) && + (identical(other.type, type) || other.type == type) && + (identical(other.isBold, isBold) || other.isBold == isBold) && + const DeepCollectionEquality() + .equals(other._subDescriptions, _subDescriptions)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, text, type, isBold, + const DeepCollectionEquality().hash(_subDescriptions)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DescriptionNoticeModelImplCopyWith<_$DescriptionNoticeModelImpl> + get copyWith => __$$DescriptionNoticeModelImplCopyWithImpl< + _$DescriptionNoticeModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$DescriptionNoticeModelImplToJson( + this, + ); + } +} + +abstract class _DescriptionNoticeModel implements DescriptionNoticeModel { + const factory _DescriptionNoticeModel( + {final String? text, + final String? type, + final bool? isBold, + final List? subDescriptions}) = + _$DescriptionNoticeModelImpl; + + factory _DescriptionNoticeModel.fromJson(Map json) = + _$DescriptionNoticeModelImpl.fromJson; + + @override + String? get text; + @override + String? get type; + @override + bool? get isBold; + @override + List? get subDescriptions; + @override + @JsonKey(ignore: true) + _$$DescriptionNoticeModelImplCopyWith<_$DescriptionNoticeModelImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SubDescriptionNoticeModel _$SubDescriptionNoticeModelFromJson( + Map json) { + return _SubDescriptionNoticeModel.fromJson(json); +} + +/// @nodoc +mixin _$SubDescriptionNoticeModel { + String? get text => throw _privateConstructorUsedError; + String? get type => throw _privateConstructorUsedError; + bool? get isBold => throw _privateConstructorUsedError; + bool? get isSpaceRequired => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SubDescriptionNoticeModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SubDescriptionNoticeModelCopyWith<$Res> { + factory $SubDescriptionNoticeModelCopyWith(SubDescriptionNoticeModel value, + $Res Function(SubDescriptionNoticeModel) then) = + _$SubDescriptionNoticeModelCopyWithImpl<$Res, SubDescriptionNoticeModel>; + @useResult + $Res call({String? text, String? type, bool? isBold, bool? isSpaceRequired}); +} + +/// @nodoc +class _$SubDescriptionNoticeModelCopyWithImpl<$Res, + $Val extends SubDescriptionNoticeModel> + implements $SubDescriptionNoticeModelCopyWith<$Res> { + _$SubDescriptionNoticeModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? text = freezed, + Object? type = freezed, + Object? isBold = freezed, + Object? isSpaceRequired = freezed, + }) { + return _then(_value.copyWith( + text: freezed == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isBold: freezed == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool?, + isSpaceRequired: freezed == isSpaceRequired + ? _value.isSpaceRequired + : isSpaceRequired // ignore: cast_nullable_to_non_nullable + as bool?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SubDescriptionNoticeModelImplCopyWith<$Res> + implements $SubDescriptionNoticeModelCopyWith<$Res> { + factory _$$SubDescriptionNoticeModelImplCopyWith( + _$SubDescriptionNoticeModelImpl value, + $Res Function(_$SubDescriptionNoticeModelImpl) then) = + __$$SubDescriptionNoticeModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? text, String? type, bool? isBold, bool? isSpaceRequired}); +} + +/// @nodoc +class __$$SubDescriptionNoticeModelImplCopyWithImpl<$Res> + extends _$SubDescriptionNoticeModelCopyWithImpl<$Res, + _$SubDescriptionNoticeModelImpl> + implements _$$SubDescriptionNoticeModelImplCopyWith<$Res> { + __$$SubDescriptionNoticeModelImplCopyWithImpl( + _$SubDescriptionNoticeModelImpl _value, + $Res Function(_$SubDescriptionNoticeModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? text = freezed, + Object? type = freezed, + Object? isBold = freezed, + Object? isSpaceRequired = freezed, + }) { + return _then(_$SubDescriptionNoticeModelImpl( + text: freezed == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String?, + type: freezed == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String?, + isBold: freezed == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool?, + isSpaceRequired: freezed == isSpaceRequired + ? _value.isSpaceRequired + : isSpaceRequired // ignore: cast_nullable_to_non_nullable + as bool?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SubDescriptionNoticeModelImpl implements _SubDescriptionNoticeModel { + const _$SubDescriptionNoticeModelImpl( + {this.text, this.type, this.isBold, this.isSpaceRequired}); + + factory _$SubDescriptionNoticeModelImpl.fromJson(Map json) => + _$$SubDescriptionNoticeModelImplFromJson(json); + + @override + final String? text; + @override + final String? type; + @override + final bool? isBold; + @override + final bool? isSpaceRequired; + + @override + String toString() { + return 'SubDescriptionNoticeModel(text: $text, type: $type, isBold: $isBold, isSpaceRequired: $isSpaceRequired)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SubDescriptionNoticeModelImpl && + (identical(other.text, text) || other.text == text) && + (identical(other.type, type) || other.type == type) && + (identical(other.isBold, isBold) || other.isBold == isBold) && + (identical(other.isSpaceRequired, isSpaceRequired) || + other.isSpaceRequired == isSpaceRequired)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, text, type, isBold, isSpaceRequired); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SubDescriptionNoticeModelImplCopyWith<_$SubDescriptionNoticeModelImpl> + get copyWith => __$$SubDescriptionNoticeModelImplCopyWithImpl< + _$SubDescriptionNoticeModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SubDescriptionNoticeModelImplToJson( + this, + ); + } +} + +abstract class _SubDescriptionNoticeModel implements SubDescriptionNoticeModel { + const factory _SubDescriptionNoticeModel( + {final String? text, + final String? type, + final bool? isBold, + final bool? isSpaceRequired}) = _$SubDescriptionNoticeModelImpl; + + factory _SubDescriptionNoticeModel.fromJson(Map json) = + _$SubDescriptionNoticeModelImpl.fromJson; + + @override + String? get text; + @override + String? get type; + @override + bool? get isBold; + @override + bool? get isSpaceRequired; + @override + @JsonKey(ignore: true) + _$$SubDescriptionNoticeModelImplCopyWith<_$SubDescriptionNoticeModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.g.dart b/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.g.dart new file mode 100644 index 000000000..2e10758a5 --- /dev/null +++ b/packages/digit_components/lib/models/privacy_notice/privacy_notice_model.g.dart @@ -0,0 +1,83 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'privacy_notice_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$PrivacyNoticeModelImpl _$$PrivacyNoticeModelImplFromJson( + Map json) => + _$PrivacyNoticeModelImpl( + header: json['header'] as String, + module: json['module'] as String, + active: json['active'] as bool?, + contents: (json['contents'] as List?) + ?.map((e) => ContentNoticeModel.fromJson(e as Map)) + .toList(), + ); + +Map _$$PrivacyNoticeModelImplToJson( + _$PrivacyNoticeModelImpl instance) => + { + 'header': instance.header, + 'module': instance.module, + 'active': instance.active, + 'contents': instance.contents, + }; + +_$ContentNoticeModelImpl _$$ContentNoticeModelImplFromJson( + Map json) => + _$ContentNoticeModelImpl( + header: json['header'] as String?, + descriptions: (json['descriptions'] as List?) + ?.map( + (e) => DescriptionNoticeModel.fromJson(e as Map)) + .toList(), + ); + +Map _$$ContentNoticeModelImplToJson( + _$ContentNoticeModelImpl instance) => + { + 'header': instance.header, + 'descriptions': instance.descriptions, + }; + +_$DescriptionNoticeModelImpl _$$DescriptionNoticeModelImplFromJson( + Map json) => + _$DescriptionNoticeModelImpl( + text: json['text'] as String?, + type: json['type'] as String?, + isBold: json['isBold'] as bool?, + subDescriptions: (json['subDescriptions'] as List?) + ?.map((e) => + SubDescriptionNoticeModel.fromJson(e as Map)) + .toList(), + ); + +Map _$$DescriptionNoticeModelImplToJson( + _$DescriptionNoticeModelImpl instance) => + { + 'text': instance.text, + 'type': instance.type, + 'isBold': instance.isBold, + 'subDescriptions': instance.subDescriptions, + }; + +_$SubDescriptionNoticeModelImpl _$$SubDescriptionNoticeModelImplFromJson( + Map json) => + _$SubDescriptionNoticeModelImpl( + text: json['text'] as String?, + type: json['type'] as String?, + isBold: json['isBold'] as bool?, + isSpaceRequired: json['isSpaceRequired'] as bool?, + ); + +Map _$$SubDescriptionNoticeModelImplToJson( + _$SubDescriptionNoticeModelImpl instance) => + { + 'text': instance.text, + 'type': instance.type, + 'isBold': instance.isBold, + 'isSpaceRequired': instance.isSpaceRequired, + }; diff --git a/packages/digit_components/lib/models/privacy_policy_model.dart b/packages/digit_components/lib/models/privacy_policy_model.dart new file mode 100644 index 000000000..834e44c60 --- /dev/null +++ b/packages/digit_components/lib/models/privacy_policy_model.dart @@ -0,0 +1,95 @@ +class PrivacyPolicy { + final String? header; // Changed to nullable + final String? module; // Changed to nullable + final bool? active; + final List? contents; + + PrivacyPolicy({ + this.header, + this.module, + this.active, + this.contents, + }); + + // Factory constructor to create PrivacyPolicy from JSON + factory PrivacyPolicy.fromJson(Map json) { + return PrivacyPolicy( + header: json['header'] as String?, + module: json['module'] as String?, + active: json['active'] as bool?, + contents: (json['contents'] as List?) + ?.map((item) => Content.fromJson(item as Map)) + .toList(), + ); + } +} + +class Content { + final String? header; // Changed to nullable + final List? descriptions; + + Content({ + this.header, + this.descriptions, + }); + + // Factory constructor to create Content from JSON + factory Content.fromJson(Map json) { + return Content( + header: json['header'] as String?, + descriptions: (json['descriptions'] as List?) + ?.map((item) => Description.fromJson(item as Map)) + .toList(), + ); + } +} + +class Description { + final String? text; // Changed to nullable + final String? type; // Changed to nullable + final bool? isBold; + final List? subDescriptions; + + Description({ + this.text, + this.type, + this.isBold, + this.subDescriptions, + }); + + // Factory constructor to create Description from JSON + factory Description.fromJson(Map json) { + return Description( + text: json['text'] as String?, + type: json['type'] as String?, + isBold: json['isBold'] as bool?, + subDescriptions: (json['subDescriptions'] as List?) + ?.map((item) => SubDescription.fromJson(item as Map)) + .toList(), + ); + } +} + +class SubDescription { + final String? text; // Changed to nullable + final String? type; // Changed to nullable + final bool? isBold; + final bool? isSpaceRequired; + + SubDescription({ + this.text, + this.type, + this.isBold, + this.isSpaceRequired, + }); + + // Factory constructor to create SubDescription from JSON + factory SubDescription.fromJson(Map json) { + return SubDescription( + text: json['text'] as String?, + type: json['type'] as String?, + isBold: json['isBold'] as bool?, + isSpaceRequired: json['isSpaceRequired'] as bool?, + ); + } +} diff --git a/packages/digit_components/lib/utils/i18_key_constants.dart b/packages/digit_components/lib/utils/i18_key_constants.dart new file mode 100644 index 000000000..1d7e1fcca --- /dev/null +++ b/packages/digit_components/lib/utils/i18_key_constants.dart @@ -0,0 +1,20 @@ +library i18; + +const privacyPolicy = PrivacyPolicy(); + +class PrivacyPolicy { + const PrivacyPolicy(); + + String get acceptText { + return 'PRIVACY_POLICY_ACCEPT_TEXT'; + } + + String get declineText { + return 'PRIVACY_POLICY_DECLINE_TEXT'; + } + + String get privacyNoticeText => 'PRIVACY_POLICY_TEXT'; + String get privacyPolicyLinkText => 'PRIVACY_POLICY_LINK_TEXT'; + String get privacyPolicyValidationText => 'PRIVACY_POLICY_VALIDATION_TEXT'; + +} \ No newline at end of file diff --git a/packages/digit_components/lib/widgets/localized.dart b/packages/digit_components/lib/widgets/localized.dart new file mode 100644 index 000000000..2d3d6a479 --- /dev/null +++ b/packages/digit_components/lib/widgets/localized.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import '../blocs/localization.dart'; + +abstract class LocalizedStatefulWidget extends StatefulWidget { + final ComponentLocalization? appLocalizations; + + const LocalizedStatefulWidget({ + super.key, + this.appLocalizations, + }); +} + +abstract class LocalizedState + extends State { + late ComponentLocalization _localizations; + + ComponentLocalization get localizations => _localizations; + + set localizations(ComponentLocalization localizations) { + if (mounted) { + setState(() { + _localizations = localizations; + }); + } + } + + @override + @mustCallSuper + void didChangeDependencies() { + _localizations = widget.appLocalizations ?? ComponentLocalization.of(context); + super.didChangeDependencies(); + } +} \ No newline at end of file diff --git a/apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_component.dart b/packages/digit_components/lib/widgets/privacy_notice/privacy_component.dart similarity index 96% rename from apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_component.dart rename to packages/digit_components/lib/widgets/privacy_notice/privacy_component.dart index e246dc30a..f2d9aa0f0 100644 --- a/apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_component.dart +++ b/packages/digit_components/lib/widgets/privacy_notice/privacy_component.dart @@ -1,7 +1,7 @@ import 'package:digit_components/digit_components.dart'; +import 'package:digit_components/models/privacy_notice/privacy_notice_model.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import '../../data/local_store/no_sql/schema/app_configuration.dart'; import '../localized.dart'; import 'privacy_notice_dialog.dart'; import 'package:reactive_forms/reactive_forms.dart'; @@ -10,7 +10,7 @@ class PrivacyComponent extends LocalizedStatefulWidget { final String formControlName; final String text; final String linkText; - final PrivacyPolicy? privacyPolicy; + final PrivacyNoticeModel? privacyPolicy; final String? trailingText; final String validationMessage; @@ -21,7 +21,7 @@ class PrivacyComponent extends LocalizedStatefulWidget { required this.text, required this.linkText, this.trailingText, - this.privacyPolicy, + this.privacyPolicy, required this.validationMessage, }); @@ -54,6 +54,7 @@ class _PrivacyComponentState extends LocalizedState { return ValueListenableBuilder( valueListenable: checkboxStateNotifier, builder: (context, value, child) { + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -120,7 +121,7 @@ class _PrivacyComponentState extends LocalizedState { builder: (context) { return FullPageDialog( - privacyPolicy: widget.privacyPolicy ?? PrivacyPolicy(), + privacyPolicy: widget.privacyPolicy ?? const PrivacyNoticeModel(header: '', module: ''), onAccept: () { checkboxStateNotifier.value = true; field.didChange(true); diff --git a/apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_notice_dialog.dart b/packages/digit_components/lib/widgets/privacy_notice/privacy_notice_dialog.dart similarity index 95% rename from apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_notice_dialog.dart rename to packages/digit_components/lib/widgets/privacy_notice/privacy_notice_dialog.dart index 55421cfc2..d6d85db70 100644 --- a/apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_notice_dialog.dart +++ b/packages/digit_components/lib/widgets/privacy_notice/privacy_notice_dialog.dart @@ -1,17 +1,18 @@ +import 'package:digit_components/models/privacy_notice/privacy_notice_model.dart'; import 'package:digit_components/theme/colors.dart'; import 'package:digit_components/widgets/digit_card.dart'; import 'package:digit_components/widgets/digit_elevated_button.dart'; import 'package:digit_components/widgets/digit_outline_button.dart'; import 'package:flutter/material.dart'; -import '../../data/local_store/no_sql/schema/app_configuration.dart'; +import '../../models/privacy_policy_model.dart'; +import '../../theme/digit_theme.dart'; import '../localized.dart'; import 'privacy_notice_expand_component.dart'; import '../../utils/i18_key_constants.dart' as i18; -import '../../models/privacy_notice/privacy_notice_model.dart'; -import '../showcase/showcase_wrappers.dart'; + class FullPageDialog extends LocalizedStatefulWidget { - final PrivacyPolicy privacyPolicy; + final PrivacyNoticeModel privacyPolicy; final VoidCallback onAccept; final VoidCallback onDecline; @@ -49,7 +50,7 @@ class _FullPageDialogState extends LocalizedState { Padding( padding: const EdgeInsets.only(top: kPadding*3, left: 0), child: Text( - localizations.translate(widget.privacyPolicy.header), + localizations.translate(widget.privacyPolicy.header ?? ''), maxLines: 3, style: Theme.of(context).textTheme.displayMedium?.copyWith( color: const DigitColors().woodsmokeBlack, diff --git a/apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_notice_expand_component.dart b/packages/digit_components/lib/widgets/privacy_notice/privacy_notice_expand_component.dart similarity index 96% rename from apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_notice_expand_component.dart rename to packages/digit_components/lib/widgets/privacy_notice/privacy_notice_expand_component.dart index 6f3f6006a..8f95d491a 100644 --- a/apps/health_campaign_field_worker_app/lib/widgets/privacy_notice/privacy_notice_expand_component.dart +++ b/packages/digit_components/lib/widgets/privacy_notice/privacy_notice_expand_component.dart @@ -1,11 +1,12 @@ -import 'package:closed_household/widgets/showcase/showcase_wrappers.dart'; import 'package:digit_components/theme/colors.dart'; import 'package:flutter/material.dart'; -import '../../data/local_store/no_sql/schema/app_configuration.dart'; +import '../../models/privacy_notice/privacy_notice_model.dart'; +import '../../models/privacy_policy_model.dart'; +import '../../theme/digit_theme.dart'; import '../localized.dart'; class ExpandableSection extends LocalizedStatefulWidget { - final Content content; + final ContentNoticeModel content; const ExpandableSection({ super.key, @@ -75,7 +76,7 @@ class _ExpandableSectionState extends LocalizedState { children: widget.content.descriptions!.asMap().entries.map((entry) { int index = entry.key; - Description desc = entry.value; + DescriptionNoticeModel desc = entry.value; int? stepNumber = desc.type == 'step' ? index + 1 : null; return DescriptionWidget( description: desc, @@ -91,7 +92,7 @@ class _ExpandableSectionState extends LocalizedState { } class DescriptionWidget extends LocalizedStatefulWidget { - final Description description; + final DescriptionNoticeModel description; final int? stepNumber; const DescriptionWidget({ @@ -201,7 +202,7 @@ class _DescriptionWidgetState extends LocalizedState { } class SubDescriptionWidget extends LocalizedStatefulWidget { - final SubDescription subDescription; + final SubDescriptionNoticeModel subDescription; final int? stepNumber; const SubDescriptionWidget({