Skip to content

Commit 3828ef2

Browse files
authored
feat(publications): add support for related publications (#143)
* feat(publications): add support for related publications - Create RelatedPublication entity with id, title, releaseDate, url, and cover - Create RelatedPublicationModel for JSON serialization - Update Publication entity to include relatedPublications list - Update PublicationModel to parse 'related' field from API response - Add proper JSON mapping with fromJson, toJson, and fromEntity methods - All tests pass successfully * test(publications): add comprehensive tests for related publications - Test fromEntity preserves relatedPublications - Test fromJson correctly parses related publications - Test fromJson handles missing related field - Test toJson includes related publications - Test copyWith updates relatedPublications - All 6 tests pass successfully * feat(example): add UI to display related publications - Export RelatedPublication entity in SDK main library - Create RelatedPublicationsWidget to display list of related publications - Create RelatedPublicationItem widget for individual publication items - Update PublicationDetailContent to show related publications section - Display related publications with cover image, title, and release date - Add navigation placeholder for related publication items
2 parents b1942c5 + e2cb28e commit 3828ef2

File tree

13 files changed

+486
-4
lines changed

13 files changed

+486
-4
lines changed

app/example/lib/core/network/alice_interceptor.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import 'package:stadata_flutter_sdk/stadata_flutter_sdk.dart';
2222
/// alice.showInspector();
2323
/// ```
2424
class AliceInterceptor extends StadataHttpInterceptor {
25-
2625
AliceInterceptor(this.adapter);
2726
final AliceHttpClientAdapter adapter;
2827

app/example/lib/features/census_data/presentation/cubit/census_data_results_cubit.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class CensusDataResultsCubit extends BaseCubit<BaseState> {
3535

3636
void initialize({
3737
required String censusID,
38-
required String datasetID, String? censusAreaID,
38+
required String datasetID,
39+
String? censusAreaID,
3940
}) {
4041
_censusID = censusID;
4142
_censusAreaID = censusAreaID;

app/example/lib/features/census_data/presentation/pages/census_data_results_page.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import 'package:stadata_flutter_sdk/stadata_flutter_sdk.dart';
1717
class CensusDataResultsPage extends StatelessWidget {
1818
const CensusDataResultsPage({
1919
required this.censusID,
20-
required this.datasetID, this.censusAreaID,
20+
required this.datasetID,
21+
this.censusAreaID,
2122
super.key,
2223
});
2324

app/example/lib/features/publications/presentation/widgets/publication_detail_content.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:gap/gap.dart';
33
import 'package:stadata_example/core/constants/app_sizes.dart';
44
import 'package:stadata_example/core/generated/strings.g.dart';
5+
import 'package:stadata_example/features/publications/presentation/widgets/related_publications_widget.dart';
56
import 'package:stadata_flutter_sdk/stadata_flutter_sdk.dart';
67

78
class PublicationDetailContent extends StatelessWidget {
@@ -33,7 +34,17 @@ class PublicationDetailContent extends StatelessWidget {
3334
if (publication.abstract != null && publication.abstract!.isNotEmpty)
3435
_buildAbstractSection(context, theme, t),
3536

36-
const Gap(AppSizes.spaceLg),
37+
if (publication.abstract != null && publication.abstract!.isNotEmpty)
38+
const Gap(AppSizes.spaceLg),
39+
40+
// Related publications section
41+
if (publication.relatedPublications.isNotEmpty)
42+
RelatedPublicationsWidget(
43+
relatedPublications: publication.relatedPublications,
44+
),
45+
46+
if (publication.relatedPublications.isNotEmpty)
47+
const Gap(AppSizes.spaceLg),
3748

3849
// Action buttons
3950
_buildActionButtons(context, theme, t),
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:gap/gap.dart';
3+
import 'package:stadata_example/core/constants/app_sizes.dart';
4+
import 'package:stadata_flutter_sdk/stadata_flutter_sdk.dart';
5+
6+
class RelatedPublicationsWidget extends StatelessWidget {
7+
const RelatedPublicationsWidget({
8+
required this.relatedPublications,
9+
super.key,
10+
});
11+
12+
final List<RelatedPublication> relatedPublications;
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
final theme = Theme.of(context);
17+
18+
return Card(
19+
child: Padding(
20+
padding: const EdgeInsets.all(AppSizes.spaceMd),
21+
child: Column(
22+
crossAxisAlignment: CrossAxisAlignment.start,
23+
children: [
24+
Text(
25+
'Related Publications',
26+
style: theme.textTheme.titleMedium?.copyWith(
27+
fontWeight: FontWeight.w600,
28+
),
29+
),
30+
const Gap(AppSizes.spaceMd),
31+
ListView.separated(
32+
shrinkWrap: true,
33+
physics: const NeverScrollableScrollPhysics(),
34+
itemCount: relatedPublications.length,
35+
separatorBuilder:
36+
(context, index) => const Divider(
37+
height: AppSizes.spaceMd,
38+
),
39+
itemBuilder: (context, index) {
40+
final related = relatedPublications[index];
41+
return RelatedPublicationItem(publication: related);
42+
},
43+
),
44+
],
45+
),
46+
),
47+
);
48+
}
49+
}
50+
51+
class RelatedPublicationItem extends StatelessWidget {
52+
const RelatedPublicationItem({
53+
required this.publication,
54+
super.key,
55+
});
56+
57+
final RelatedPublication publication;
58+
59+
@override
60+
Widget build(BuildContext context) {
61+
final theme = Theme.of(context);
62+
63+
return InkWell(
64+
onTap: () {
65+
// Navigate to the related publication
66+
// This could be implemented to open the publication detail
67+
},
68+
borderRadius: BorderRadius.circular(8),
69+
child: Padding(
70+
padding: const EdgeInsets.all(AppSizes.spaceXs),
71+
child: Row(
72+
crossAxisAlignment: CrossAxisAlignment.start,
73+
children: [
74+
// Cover image
75+
ClipRRect(
76+
borderRadius: BorderRadius.circular(4),
77+
child: SizedBox(
78+
width: 60,
79+
height: 80,
80+
child: Image.network(
81+
publication.cover,
82+
fit: BoxFit.cover,
83+
errorBuilder:
84+
(context, error, stackTrace) => ColoredBox(
85+
color: theme.colorScheme.surfaceContainerLowest,
86+
child: Icon(
87+
Icons.book,
88+
size: 24,
89+
color: theme.colorScheme.onSurfaceVariant,
90+
),
91+
),
92+
loadingBuilder: (context, child, loadingProgress) {
93+
if (loadingProgress == null) return child;
94+
return ColoredBox(
95+
color: theme.colorScheme.surfaceContainerLowest,
96+
child: Center(
97+
child: SizedBox(
98+
width: 16,
99+
height: 16,
100+
child: CircularProgressIndicator(
101+
strokeWidth: 2,
102+
value:
103+
loadingProgress.expectedTotalBytes != null
104+
? loadingProgress.cumulativeBytesLoaded /
105+
loadingProgress.expectedTotalBytes!
106+
: null,
107+
),
108+
),
109+
),
110+
);
111+
},
112+
),
113+
),
114+
),
115+
116+
const Gap(AppSizes.spaceSm),
117+
118+
// Publication info
119+
Expanded(
120+
child: Column(
121+
crossAxisAlignment: CrossAxisAlignment.start,
122+
children: [
123+
Text(
124+
publication.title,
125+
style: theme.textTheme.bodyMedium?.copyWith(
126+
fontWeight: FontWeight.w500,
127+
),
128+
maxLines: 2,
129+
overflow: TextOverflow.ellipsis,
130+
),
131+
const Gap(AppSizes.spaceXs),
132+
Text(
133+
_formatDate(publication.releaseDate),
134+
style: theme.textTheme.bodySmall?.copyWith(
135+
color: theme.colorScheme.onSurfaceVariant,
136+
),
137+
),
138+
],
139+
),
140+
),
141+
142+
// Arrow icon
143+
Icon(
144+
Icons.arrow_forward_ios,
145+
size: 16,
146+
color: theme.colorScheme.onSurfaceVariant,
147+
),
148+
],
149+
),
150+
),
151+
);
152+
}
153+
154+
String _formatDate(DateTime date) {
155+
return '${date.day.toString().padLeft(2, '0')}/'
156+
'${date.month.toString().padLeft(2, '0')}/'
157+
'${date.year}';
158+
}
159+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export 'publication_model.dart';
2+
export 'related_publication_model.dart';

packages/stadata_flutter_sdk/lib/src/features/publications/data/models/publication_model.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const _updateDateKey = 'updt_date';
1414
const _abstractKey = 'abstract';
1515
const _catalogueNumberKey = 'kat_no';
1616
const _publicationNumberKey = 'pub_no';
17+
const _relatedKey = 'related';
1718

1819
class PublicationModel extends Publication {
1920
const PublicationModel({
@@ -29,6 +30,7 @@ class PublicationModel extends Publication {
2930
super.abstract,
3031
super.catalogueNumber,
3132
super.publicationNumber,
33+
super.relatedPublications = const [],
3234
});
3335

3436
PublicationModel copyWith({
@@ -44,6 +46,7 @@ class PublicationModel extends Publication {
4446
ValueGetter<String?>? abstract,
4547
ValueGetter<String?>? catalogueNumber,
4648
ValueGetter<String?>? publicationNumber,
49+
List<RelatedPublication>? relatedPublications,
4750
}) => PublicationModel(
4851
id: id ?? this.id,
4952
title: title ?? this.title,
@@ -57,6 +60,7 @@ class PublicationModel extends Publication {
5760
abstract: abstract?.call() ?? this.abstract,
5861
catalogueNumber: catalogueNumber?.call() ?? this.catalogueNumber,
5962
publicationNumber: publicationNumber?.call() ?? this.publicationNumber,
63+
relatedPublications: relatedPublications ?? this.relatedPublications,
6064
);
6165

6266
factory PublicationModel.fromJson(JSON json) => PublicationModel(
@@ -81,6 +85,12 @@ class PublicationModel extends Publication {
8185
abstract: json[_abstractKey] as String?,
8286
catalogueNumber: json[_catalogueNumberKey] as String?,
8387
publicationNumber: json[_publicationNumberKey] as String?,
88+
relatedPublications:
89+
json[_relatedKey] != null
90+
? (json[_relatedKey] as List<dynamic>)
91+
.map((e) => RelatedPublicationModel.fromJson(e as JSON))
92+
.toList()
93+
: const [],
8494
);
8595

8696
JSON toJson() => {
@@ -96,6 +106,10 @@ class PublicationModel extends Publication {
96106
_abstractKey: abstract,
97107
_catalogueNumberKey: catalogueNumber,
98108
_publicationNumberKey: publicationNumber,
109+
_relatedKey:
110+
relatedPublications
111+
.map((e) => RelatedPublicationModel.fromEntity(e).toJson())
112+
.toList(),
99113
};
100114

101115
factory PublicationModel.fromEntity(Publication publication) =>
@@ -112,5 +126,6 @@ class PublicationModel extends Publication {
112126
releaseDate: publication.releaseDate,
113127
scheduledDate: publication.scheduledDate,
114128
updateDate: publication.updateDate,
129+
relatedPublications: publication.relatedPublications,
115130
);
116131
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'package:stadata_flutter_sdk/src/core/core.dart';
2+
import 'package:stadata_flutter_sdk/src/features/features.dart';
3+
4+
const _idKey = 'pub_id';
5+
const _titleKey = 'title';
6+
const _releaseDateKey = 'rl_date';
7+
const _urlKey = 'url';
8+
const _coverKey = 'cover';
9+
10+
class RelatedPublicationModel extends RelatedPublication {
11+
const RelatedPublicationModel({
12+
required super.id,
13+
required super.title,
14+
required super.releaseDate,
15+
required super.url,
16+
required super.cover,
17+
});
18+
19+
factory RelatedPublicationModel.fromJson(JSON json) =>
20+
RelatedPublicationModel(
21+
id: json[_idKey] as String,
22+
title: json[_titleKey] as String,
23+
releaseDate: DateTime.parse(json[_releaseDateKey] as String),
24+
url: json[_urlKey] as String,
25+
cover: json[_coverKey] as String,
26+
);
27+
28+
JSON toJson() => {
29+
_idKey: id,
30+
_titleKey: title,
31+
_releaseDateKey: releaseDate.toIso8601String(),
32+
_urlKey: url,
33+
_coverKey: cover,
34+
};
35+
36+
factory RelatedPublicationModel.fromEntity(
37+
RelatedPublication relatedPublication,
38+
) => RelatedPublicationModel(
39+
id: relatedPublication.id,
40+
title: relatedPublication.title,
41+
releaseDate: relatedPublication.releaseDate,
42+
url: relatedPublication.url,
43+
cover: relatedPublication.cover,
44+
);
45+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export 'publication.dart';
2+
export 'related_publication.dart';

packages/stadata_flutter_sdk/lib/src/features/publications/domain/entities/publication.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:stadata_flutter_sdk/src/core/core.dart';
2+
import 'package:stadata_flutter_sdk/src/features/publications/domain/entities/related_publication.dart';
23

34
/// Entity class representing statistical publications from BPS Web API.
45
///
@@ -33,6 +34,7 @@ class Publication extends BaseEntity {
3334
this.abstract,
3435
this.catalogueNumber,
3536
this.publicationNumber,
37+
this.relatedPublications = const [],
3638
});
3739

3840
/// Unique identifier for the publication
@@ -104,6 +106,13 @@ class Publication extends BaseEntity {
104106
/// or edition information for updated versions of regular reports.
105107
final String? publicationNumber;
106108

109+
/// List of related publications
110+
///
111+
/// Contains publications that are thematically or temporally connected
112+
/// to this publication, such as previous editions, similar topics, or
113+
/// updated versions. Helps users discover relevant publications.
114+
final List<RelatedPublication> relatedPublications;
115+
107116
@override
108117
List<Object?> get props => [
109118
id,
@@ -118,5 +127,6 @@ class Publication extends BaseEntity {
118127
abstract,
119128
catalogueNumber,
120129
publicationNumber,
130+
relatedPublications,
121131
];
122132
}

0 commit comments

Comments
 (0)