Skip to content

Commit 0e42437

Browse files
authored
подсветка кода, переработка родительских комментариев (#64)
* подсветка кода - изменен базовый размер текста при чтении публикации - изменение отступов между элементами на карточке публикации и на экране чтения * переработаны родительские комментарии - отображение в форматированном виде - не сохраняем в историю скролла, если родительский комментарий находится близко - кнопка просмотра родительского комментария справа - чтобы перейти на родительский коммент, надо нажать на "ответ на сообщение от ..."
1 parent 2f41571 commit 0e42437

File tree

13 files changed

+436
-241
lines changed

13 files changed

+436
-241
lines changed

lib/core/component/logger/console.dart

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,21 @@ import 'dart:developer' as dev;
33
final Logger logger = ConsoleLogger();
44

55
abstract interface class Logger {
6-
void info(String message, {String? title});
6+
void info(dynamic message, {String? title});
77

88
void error(Object exception, StackTrace trace);
99
}
1010

1111
class ConsoleLogger implements Logger {
1212
@override
13-
void info(String message, {String? title}) {
13+
void info(dynamic message, {String? title}) {
1414
if (title != null) {
15-
dev.log(
16-
'$title >',
17-
name: 'INFO',
18-
);
15+
dev.log('$title >', name: 'INFO');
1916

2017
message = '\t $message';
2118
}
2219

23-
dev.log(
24-
message,
25-
name: 'INFO',
26-
);
20+
dev.log('$message', name: 'INFO');
2721
}
2822

2923
@override

lib/data/model/offset_history.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ class OffsetHistory {
3030
return value;
3131
}
3232

33-
String? lessThan(double value) {
34-
final entry = _data.entries.firstWhereOrNull(
35-
(e) => value > e.value,
36-
);
33+
String? lessThan(double pixels) {
34+
final entry = _data.entries.firstWhereOrNull((e) => pixels > e.value);
3735
return entry?.key;
3836
}
3937

lib/presentation/page/publications/widget/card/common_card_widget.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class CommonCardWidget extends StatelessWidget {
6060
PublicationHubsWidget(hubs: publication.hubs),
6161
if (publication.format != null)
6262
PublicationFormatWidget(publication.format!),
63+
const SizedBox(),
6364
],
6465
),
6566
),

lib/presentation/page/publications/widget/card/components/hubs_widget.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import '../../../../../../core/component/router/app_router.dart';
55
import '../../../../../../data/model/hub/hub.dart';
66
import '../../../../../../data/model/publication/publication.dart';
77
import '../../../../../../di/di.dart';
8+
import '../../../../../extension/context.dart';
89
import '../../../../../theme/theme.dart';
910

1011
class PublicationHubsWidget extends StatelessWidget {
@@ -15,7 +16,8 @@ class PublicationHubsWidget extends StatelessWidget {
1516
@override
1617
Widget build(BuildContext context) {
1718
return Wrap(
18-
spacing: 14,
19+
spacing: 4,
20+
runSpacing: 6,
1921
children: hubs.map((hub) => _PublicationHub(hub: hub)).toList(),
2022
);
2123
}
@@ -32,9 +34,7 @@ class _PublicationHub extends StatelessWidget {
3234

3335
@override
3436
Widget build(BuildContext context) {
35-
var style = Theme.of(
36-
context,
37-
).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w500);
37+
var style = context.theme.textTheme.labelMedium;
3838

3939
if ((hub.relatedData as HubRelatedData).isSubscribed) {
4040
style = style?.copyWith(color: Colors.green.shade300);
@@ -56,7 +56,7 @@ class _PublicationHub extends StatelessWidget {
5656
onTap: () => getIt<AppRouter>().navigate(route),
5757
borderRadius: AppStyles.borderRadius,
5858
child: Padding(
59-
padding: const EdgeInsets.symmetric(vertical: 8.0),
59+
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
6060
child: Text(title, style: style, softWrap: true),
6161
),
6262
);

lib/presentation/page/publications/widget/comment_list_view.dart

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,29 @@ class _CommentTreeWidgetState extends State<CommentTreeWidget> {
9393
super.dispose();
9494
}
9595

96-
void _moveToParent(String id) async {
97-
final key = _parentKeys[id];
96+
/// добавляем в историю текущий оффсет скролла
97+
void _saveToHistory({
98+
required String id,
99+
required double offset,
100+
required String parentId,
101+
}) {
102+
final key = _parentKeys[parentId];
103+
final box = key?.currentContext?.findRenderObject();
104+
if (box != null) {
105+
final double yPosition = (box as RenderBox).localToGlobal(Offset.zero).dy;
106+
107+
/// если родительский комментарий не очень далеко - не сохраняем в историю
108+
if (yPosition > -400) {
109+
return;
110+
}
111+
}
112+
113+
_history.push(id, scrollController.offset);
114+
}
115+
116+
/// скролл к родительскому комментарию
117+
void _moveToParent(String parentId) async {
118+
final key = _parentKeys[parentId];
98119
if (key == null) {
99120
return;
100121
}
@@ -111,7 +132,7 @@ class _CommentTreeWidgetState extends State<CommentTreeWidget> {
111132
duration: const Duration(milliseconds: 50),
112133
curve: scrollCurve,
113134
);
114-
return _moveToParent(id);
135+
return _moveToParent(parentId);
115136
}
116137

117138
Scrollable.ensureVisible(
@@ -193,20 +214,17 @@ class _CommentTreeWidgetState extends State<CommentTreeWidget> {
193214
crossAxisAlignment: CrossAxisAlignment.stretch,
194215
children: [
195216
if (entry.node.parent != null)
196-
GestureDetector(
197-
onTap: () {
198-
/// добавляем в историю текущий оффсет скролла
199-
_history.push(
200-
entry.node.id,
201-
scrollController.offset,
217+
CommentParent(
218+
parent: entry.node.parent!,
219+
onParentTapped: () {
220+
_saveToHistory(
221+
id: entry.node.id,
222+
offset: scrollController.offset,
223+
parentId: entry.node.parent!.id,
202224
);
203225

204-
/// перемещаемся к родительскому комментарию
205226
_moveToParent(entry.node.parentId);
206227
},
207-
child: CommentParent(
208-
parent: entry.node.parent!,
209-
),
210228
),
211229
CommentWidget(entry.node),
212230
],

lib/presentation/page/publications/widget/comment_parent_widget.dart

Lines changed: 148 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,90 +9,178 @@ import '../../../extension/extension.dart';
99
import '../../../theme/theme.dart';
1010
import '../../../widget/html_view_widget.dart';
1111

12-
class CommentParent extends StatelessWidget {
13-
const CommentParent({super.key, required this.parent});
12+
class CommentParent extends StatefulWidget {
13+
const CommentParent({super.key, required this.parent, this.onParentTapped});
1414

1515
final CommentBase parent;
16+
final VoidCallback? onParentTapped;
1617

1718
@override
18-
Widget build(BuildContext context) {
19-
final text = parse(parent.message).documentElement?.text ?? '';
19+
State<CommentParent> createState() => _CommentParentState();
20+
}
21+
22+
class _CommentParentState extends State<CommentParent> {
23+
final tag = UniqueKey();
24+
25+
late TextStyle textStyle = DefaultTextStyle.of(context).style;
26+
late Color bgColor = context.theme.colors.cardHighlight;
27+
late final parentHtml = HtmlView(
28+
textHtml: widget.parent.message,
29+
textStyle: textStyle,
30+
renderMode: RenderMode.column,
31+
padding: EdgeInsets.zero,
32+
);
33+
34+
@override
35+
void didChangeDependencies() {
36+
bgColor = context.theme.colors.cardHighlight;
37+
textStyle = DefaultTextStyle.of(context).style;
38+
39+
super.didChangeDependencies();
40+
}
41+
42+
PageRoute buildPageRoute(BuildContext context) {
43+
return PageRouteBuilder(
44+
opaque: false,
45+
barrierDismissible: true,
46+
pageBuilder:
47+
(_, __, ___) => Center(
48+
child: BackdropFilter(
49+
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
50+
child: ConstrainedBox(
51+
constraints: BoxConstraints(
52+
maxWidth: Device.getWidth(context) * .9,
53+
maxHeight: Device.getHeight(context) * .5,
54+
),
55+
child: Hero(
56+
tag: tag,
57+
child: DecoratedBox(
58+
decoration: BoxDecoration(
59+
borderRadius: AppStyles.borderRadius,
60+
color: bgColor,
61+
),
62+
child: SingleChildScrollView(
63+
padding: const EdgeInsets.all(16),
64+
child: parentHtml,
65+
),
66+
),
67+
),
68+
),
69+
),
70+
),
71+
);
72+
}
2073

74+
@override
75+
Widget build(BuildContext context) {
76+
final text = parse(widget.parent.message).documentElement?.text ?? '';
2177
if (text.isEmpty) {
2278
return const SizedBox();
2379
}
2480

25-
final tag = UniqueKey();
26-
final textStyle = DefaultTextStyle.of(context).style;
27-
final bgColor = context.theme.colors.cardHighlight;
28-
2981
return Padding(
30-
padding: const EdgeInsets.all(10),
82+
// TODO: bottom padding Для iconbutton?
83+
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 6),
3184
child: Hero(
3285
tag: tag,
3386
child: DecoratedBox(
3487
decoration: BoxDecoration(
35-
borderRadius: AppStyles.borderRadius,
88+
borderRadius: AppStyles.borderRadiusSm,
3689
color: bgColor,
3790
),
38-
child: Padding(
39-
padding: const EdgeInsets.all(8),
40-
child: Column(
41-
crossAxisAlignment: CrossAxisAlignment.start,
42-
children: [
43-
Text(
44-
text,
45-
maxLines: 2,
46-
overflow: TextOverflow.ellipsis,
47-
style: textStyle,
48-
),
49-
IconButton.outlined(
50-
visualDensity: VisualDensity.compact,
51-
icon: const Icon(
52-
Icons.remove_red_eye_sharp,
53-
size: 16,
54-
),
55-
onPressed: () => Navigator.of(
56-
context,
57-
rootNavigator: true,
58-
).push(
59-
PageRouteBuilder(
60-
opaque: false,
61-
barrierDismissible: true,
62-
pageBuilder: (_, __, ___) => Center(
63-
child: BackdropFilter(
64-
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
65-
child: ConstrainedBox(
66-
constraints: BoxConstraints(
67-
maxWidth: Device.getWidth(context) * .9,
68-
maxHeight: Device.getHeight(context) * .5,
69-
),
70-
child: Hero(
71-
tag: tag,
72-
child: DecoratedBox(
73-
decoration: BoxDecoration(
74-
borderRadius: AppStyles.borderRadius,
75-
color: bgColor,
76-
),
77-
child: SingleChildScrollView(
78-
padding: const EdgeInsets.all(16),
79-
child: HtmlView(
80-
textHtml: parent.message,
81-
textStyle: textStyle,
82-
renderMode: RenderMode.column,
83-
padding: EdgeInsets.zero,
84-
),
91+
child: Stack(
92+
alignment: Alignment.bottomCenter,
93+
children: [
94+
ConstrainedBox(
95+
constraints: const BoxConstraints(maxHeight: 100),
96+
child: SingleChildScrollView(
97+
padding: const EdgeInsets.fromLTRB(10, 0, 10, 40),
98+
child: Column(
99+
crossAxisAlignment: CrossAxisAlignment.stretch,
100+
children: [
101+
GestureDetector(
102+
onTap: widget.onParentTapped,
103+
behavior: HitTestBehavior.translucent,
104+
child: Padding(
105+
padding: const EdgeInsets.only(top: 10, bottom: 8),
106+
child: Row(
107+
spacing: 4,
108+
children: [
109+
Icon(
110+
Icons.arrow_upward,
111+
size: 18,
112+
color: context.theme.colors.primary,
113+
),
114+
SelectableText.rich(
115+
TextSpan(
116+
text: 'ответил на ',
117+
style: textStyle,
118+
children: [
119+
TextSpan(
120+
text: 'сообщение от ',
121+
style: textStyle.copyWith(
122+
color: context.theme.colors.primary,
123+
),
124+
children: [
125+
TextSpan(
126+
text: widget.parent.author.alias,
127+
style: textStyle.copyWith(
128+
color: context.theme.colors.primary,
129+
fontWeight: FontWeight.w500,
130+
),
131+
),
132+
],
133+
),
134+
const TextSpan(text: ':'),
135+
],
85136
),
137+
onTap: widget.onParentTapped,
86138
),
87-
),
139+
],
88140
),
89141
),
90142
),
143+
parentHtml,
144+
],
145+
),
146+
),
147+
),
148+
Positioned.fill(
149+
top: 60,
150+
child: IgnorePointer(
151+
child: DecoratedBox(
152+
decoration: BoxDecoration(
153+
borderRadius: AppStyles.borderRadius,
154+
gradient: LinearGradient(
155+
begin: Alignment.topCenter,
156+
end: Alignment.bottomCenter,
157+
colors: [
158+
bgColor.withValues(alpha: .1),
159+
bgColor.withValues(alpha: .3),
160+
bgColor.withValues(alpha: .6),
161+
bgColor.withValues(alpha: .9),
162+
bgColor,
163+
],
164+
),
91165
),
92166
),
93167
),
94-
],
95-
),
168+
),
169+
Positioned(
170+
bottom: 0,
171+
right: 0,
172+
child: IconButton.filled(
173+
visualDensity: VisualDensity.compact,
174+
icon: const Icon(Icons.remove_red_eye_sharp, size: 16),
175+
tooltip: 'Показать полностью',
176+
onPressed:
177+
() => Navigator.of(
178+
context,
179+
rootNavigator: true,
180+
).push(buildPageRoute(context)),
181+
),
182+
),
183+
],
96184
),
97185
),
98186
),

0 commit comments

Comments
 (0)