Skip to content

Commit c36ae4d

Browse files
committed
fix null-safety bugs
1 parent 84af69d commit c36ae4d

File tree

7 files changed

+146
-86
lines changed

7 files changed

+146
-86
lines changed

lib/habr/json_parsing.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ PostPreviews parsePostPreviewsFromJson(Map<String, dynamic> data) {
7070
corporative: article['isCorporative'],
7171
title: _prepareHtmlString(article['titleHtml']),
7272
hubs: article['hubs']
73-
.map<String>((flow) => flow['title'] as String?)
73+
.map<String>((flow) => flow['title'] as String)
7474
.toList(),
7575
htmlPreview: "<div>${article['leadData']['textHtml']}</div>",
7676
flows: article['flows']
77-
.map<String>((flow) => flow['title'] as String?)
77+
.map<String>((flow) => flow['title'] as String)
7878
.toList(),
7979
publishDate: DateTime.parse(article['timePublished']),
8080
author: parseAuthorFromJson(article['author']),

lib/pages/filters.dart

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:habr_app/widgets/adaptive_ui.dart';
77
import 'package:itertools/itertools.dart';
88
import 'package:hive/hive.dart';
99
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
10+
import 'package:tuple_dart/tuple.dart';
1011

1112
class FiltersPage extends StatefulWidget {
1213
@override
@@ -28,38 +29,46 @@ class _FiltersPageState extends State<FiltersPage> {
2829
);
2930
}
3031

32+
List<Tuple2<int, ChildT>>
33+
_filterChildType<ChildT extends Filter<PostPreview>>(
34+
Iterable<Tuple2<int, Filter<PostPreview>>> filtersWithIndex) {
35+
return filtersWithIndex
36+
.where((e) => e.item2 is ChildT)
37+
.map((e) => e.withItem2(e.item2 as ChildT))
38+
.toList(growable: false);
39+
}
40+
3141
Widget _buildBody() {
42+
final filtersStore = FiltersStorage();
43+
final removeFilter = (int i) => () => filtersStore.removeFilterAt(i);
3244
return ValueListenableBuilder<Box<Filter<PostPreview>>>(
33-
valueListenable: FiltersStorage().listenable(),
34-
builder: (context, box, child) => ListView(
35-
children: box.values
36-
.mapIndexed<Widget>((i, filter) {
37-
if (filter is NicknameAuthorFilter) {
38-
return ListTile(
39-
leading: const Icon(Icons.person_outline),
40-
title: Text(filter.nickname!),
41-
trailing: IconButton(
42-
icon: const Icon(Icons.clear),
43-
onPressed: () => FiltersStorage().removeFilterAt(i),
44-
),
45-
);
46-
} else if (filter is CompanyNameFilter) {
47-
return ListTile(
48-
leading: const Icon(Icons.groups),
49-
title: Text(filter.companyName!),
50-
trailing: IconButton(
51-
icon: const Icon(Icons.clear),
52-
onPressed: () => FiltersStorage().removeFilterAt(i),
53-
),
54-
);
55-
} else {
56-
logInfo("filter not supported");
57-
}
58-
return null;
59-
} as Widget Function(int, Filter<PostPreview>))
60-
.map((e) => DefaultConstraints(child: e))
61-
.toList(),
62-
),
45+
valueListenable: filtersStore.listenable(),
46+
builder: (context, box, child) {
47+
final filtersWithIndex = box.values.enumerate().toList();
48+
final filtersByAuthor =
49+
_filterChildType<NicknameAuthorFilter>(filtersWithIndex);
50+
final filtersByCompany =
51+
_filterChildType<CompanyNameFilter>(filtersWithIndex);
52+
53+
return ListView(
54+
children: [
55+
if (filtersByAuthor.isNotEmpty)
56+
FiltersGroup<NicknameAuthorFilter>(
57+
filters: filtersByAuthor,
58+
leading: const Icon(Icons.person_outline),
59+
titleBuilder: (filter) => Text(filter.nickname),
60+
onRemoveBuilder: removeFilter,
61+
),
62+
if (filtersByCompany.isNotEmpty)
63+
FiltersGroup<CompanyNameFilter>(
64+
filters: filtersByCompany,
65+
leading: const Icon(Icons.groups),
66+
titleBuilder: (filter) => Text(filter.companyName),
67+
onRemoveBuilder: removeFilter,
68+
),
69+
].map((e) => DefaultConstraints(child: e)).toList(growable: false),
70+
);
71+
},
6372
);
6473
}
6574

@@ -117,6 +126,40 @@ class _FiltersPageState extends State<FiltersPage> {
117126
}
118127
}
119128

129+
class FiltersGroup<FilterT extends Filter<PostPreview>>
130+
extends StatelessWidget {
131+
final Iterable<Tuple2<int, FilterT>> filters;
132+
final Widget leading;
133+
final Widget Function(FilterT) titleBuilder;
134+
final void Function() Function(int) onRemoveBuilder;
135+
136+
FiltersGroup({
137+
required this.filters,
138+
required this.leading,
139+
required this.titleBuilder,
140+
required this.onRemoveBuilder,
141+
});
142+
143+
@override
144+
Widget build(BuildContext context) {
145+
return Card(
146+
child: Column(
147+
children: filters.map((tuple) {
148+
final index = tuple.item1;
149+
final filter = tuple.item2;
150+
return ListTile(
151+
leading: leading,
152+
title: titleBuilder(filter),
153+
trailing: IconButton(
154+
icon: const Icon(Icons.clear),
155+
onPressed: onRemoveBuilder(index),
156+
));
157+
}).toList(growable: false),
158+
),
159+
);
160+
}
161+
}
162+
120163
enum _DialogType {
121164
AuthorNickname,
122165
CompanyName,

lib/utils/filters/article_preview_filters.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:habr_app/models/post_preview.dart';
44
export 'filter.dart';
55

66
class NicknameAuthorFilter extends Filter<PostPreview> {
7-
final String? nickname;
7+
final String nickname;
88

99
const NicknameAuthorFilter(this.nickname);
1010

@@ -22,19 +22,19 @@ class ScoreArticleFilter extends Filter<PostPreview> {
2222

2323
@override
2424
bool filter(PostPreview obj) {
25-
return (min != null && obj.statistics.score< min!) ||
26-
(max != null && obj.statistics.score> max!);
25+
return (min != null && obj.statistics.score < min!) ||
26+
(max != null && obj.statistics.score > max!);
2727
}
2828
}
2929

3030
class CompanyNameFilter extends Filter<PostPreview> {
31-
final String? companyName;
31+
final String companyName;
3232

3333
const CompanyNameFilter(this.companyName);
3434

3535
@override
3636
bool filter(PostPreview postPreview) {
37-
final l = companyName!.toLowerCase();
37+
final l = companyName.toLowerCase();
3838
return postPreview.hubs
3939
?.any((element) => element.toLowerCase().contains(l)) ??
4040
false;

lib/widgets/dividing_block.dart

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import 'package:flutter/material.dart';
22

33
class WrappedContainer extends StatelessWidget {
4-
final List<Widget?>? children;
4+
final List<Widget> children;
55
final double distance;
66

7-
WrappedContainer({this.children, this.distance = 20});
7+
WrappedContainer({required this.children, this.distance = 20});
88

99
@override
1010
Widget build(BuildContext context) {
11-
final wrappedChildren = <Widget?>[];
12-
for (int i = 0; i < children!.length; i++) {
13-
wrappedChildren.add(children![i]);
14-
if (i != children!.length - 1) wrappedChildren.add(SizedBox(height: distance));
11+
final wrappedChildren = <Widget>[];
12+
for (int i = 0; i < children.length; i++) {
13+
wrappedChildren.add(children[i]);
14+
if (i != children.length - 1)
15+
wrappedChildren.add(SizedBox(height: distance));
1516
}
1617
return Column(
1718
crossAxisAlignment: CrossAxisAlignment.stretch,
18-
children: wrappedChildren as List<Widget>,
19+
children: wrappedChildren,
1920
);
2021
}
21-
}
22+
}

lib/widgets/html_elements/unordered_list.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'package:flutter/material.dart';
22

33
class UnorderedList extends StatelessWidget {
4-
final List<Widget?> children;
4+
final List<Widget> children;
55
UnorderedList({required this.children});
66

77
@override
@@ -18,15 +18,16 @@ class UnorderedList extends StatelessWidget {
1818
}
1919

2020
class UnorderedItem extends StatelessWidget {
21-
final Widget? child;
21+
final Widget child;
2222

2323
UnorderedItem({required this.child});
2424

2525
@override
2626
Widget build(BuildContext context) {
2727
final theme = Theme.of(context).textTheme.bodyText2!;
2828
final bulletSize = 5;
29-
final centerBaseline = (theme.fontSize! * theme.height! - bulletSize / 2) / 2;
29+
final centerBaseline =
30+
(theme.fontSize! * theme.height! - bulletSize / 2) / 2;
3031
return Row(
3132
crossAxisAlignment: CrossAxisAlignment.start,
3233
mainAxisAlignment: MainAxisAlignment.start,
@@ -35,7 +36,7 @@ class UnorderedItem extends StatelessWidget {
3536
child: const Bullet(),
3637
padding: EdgeInsets.only(right: 10, top: centerBaseline),
3738
),
38-
Expanded(child: child!)
39+
Expanded(child: child),
3940
],
4041
);
4142
}

lib/widgets/html_view.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
22
import 'package:flutter/material.dart';
33
import 'package:habr_app/stores/app_settings.dart';
44
import 'package:provider/provider.dart';
5+
import 'package:itertools/itertools.dart';
56

67
import 'html_elements/html_elements.dart';
78
import 'dividing_block.dart';
@@ -64,8 +65,8 @@ Widget? buildTree(view.Node element, BuildContext context, BuildParams params) {
6465
widget = Text.rich(
6566
TextSpan(
6667
children: element.children
67-
.map<InlineSpan?>((child) => buildInline(child, context, params))
68-
.toList() as List<InlineSpan>?),
68+
.map<InlineSpan>((child) => buildInline(child, context, params))
69+
.toList()),
6970
textAlign: params.textAlign,
7071
);
7172
} else if (element is view.Scrollable) {
@@ -102,11 +103,15 @@ Widget? buildTree(view.Node element, BuildContext context, BuildParams params) {
102103
} else if (element is view.BlockList) {
103104
// TODO: ordered list
104105
widget = UnorderedList(
105-
children: element.children.map<Widget?>((li) => buildTree(li, context, params))
106+
children: element.children
107+
.map<Widget?>((li) => buildTree(li, context, params))
108+
.notNull
106109
.toList());
107110
} else if (element is view.BlockColumn) {
108111
widget = WrappedContainer(
109-
children: element.children.map<Widget?>((child) => buildTree(child, context, params))
112+
children: element.children
113+
.map<Widget?>((child) => buildTree(child, context, params))
114+
.notNull
110115
.toList());
111116
} else if (element is view.Details) {
112117
widget = Spoiler(
@@ -143,7 +148,7 @@ Widget? buildTree(view.Node element, BuildContext context, BuildParams params) {
143148
return widget;
144149
}
145150

146-
InlineSpan? buildInline(
151+
InlineSpan buildInline(
147152
view.Span element, BuildContext context, BuildParams params) {
148153
late InlineSpan span;
149154
if (element is view.TextSpan) {
@@ -181,7 +186,7 @@ Iterable<Widget> inlineTree(
181186
}
182187
} else if (element is view.BlockList) {
183188
for (final item in element.children) {
184-
yield UnorderedItem(child: buildTree(item, context, params));
189+
yield UnorderedItem(child: buildTree(item, context, params)!);
185190
}
186191
} else {
187192
yield buildTree(element!, context, params)!;

0 commit comments

Comments
 (0)